From 5d445e1ea32ebfc2a668d555fa481a15a9266a11 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 5 Jul 2017 21:05:31 +0300 Subject: [PATCH 001/254] mlrImportFreeSurfer.m: if mri_convert is not available and freesurfer volume has already been converted, read the voxel and vlume dimensions from the converted file --- .../File/FreeSurfer/mlrImportFreeSurfer.m | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m index d4d3781aa..172c4bafa 100644 --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -52,6 +52,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 +68,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 +119,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 isfile(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 ~isfile(outFile) if fieldIsNotDefined(params,'pixelSize') disp('(mlrImportFreeSurfer) Could not determine voxel size. Assuming 1x1x1 mm.'); From bd081716a6215de6fbb07d28acf91c7679772dfb Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 24 Oct 2017 17:29:28 +0300 Subject: [PATCH 002/254] combineTransformOverlays.m: when exporting to new group in base space, splits overlays with multiple scans into multiple overlays --- .../Plugin/GLM_v2/combineTransformOverlays.m | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index a31cb0394..d031b57f5 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -615,8 +615,24 @@ 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 + cOverlay = 0; + for iOverlay = 1:size(outputData,2) + for iScan = 1:nScans + cOverlay = cOverlay+1; + outputOverlay2(cOverlay) = defaultOverlay; + outputOverlay2(cOverlay).data{1} = 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 + outputOverlay = outputOverlay2; + end + thisView = viewSet(thisView,'newoverlay',outputOverlay); - refreshMLRDisplay(thisView.viewNum); end set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; From 1698f8d0f49ea89d3c4ef2e92f5f8e6f10b025ec Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 25 Oct 2017 18:42:48 +0300 Subject: [PATCH 003/254] combineTransformOverlays.m: deals with analyses and overlays with empty scans when choosing option baseSpace --- .../Plugin/GLM_v2/combineTransformOverlays.m | 148 +++++++++--------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index d031b57f5..c6eca42e7 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -158,7 +158,9 @@ %here could probably put all overlays of a single scan in a 4D array, but the 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},[],[],baseSpaceInterp); + end end end end @@ -431,70 +433,72 @@ 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; + 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) + 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; + end end mrCloseDlg(hWaitBar); end @@ -621,12 +625,14 @@ cOverlay = 0; for iOverlay = 1:size(outputData,2) for iScan = 1:nScans - cOverlay = cOverlay+1; - outputOverlay2(cOverlay) = defaultOverlay; - outputOverlay2(cOverlay).data{1} = 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]; + if ~isempty(outputOverlay(iOverlay).data{iScan}) + cOverlay = cOverlay+1; + outputOverlay2(cOverlay) = defaultOverlay; + outputOverlay2(cOverlay).data{1} = 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; From 7de67262b25e3d7e712d0558c20796edb481ae22 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 31 Oct 2017 19:05:08 +0200 Subject: [PATCH 004/254] combineTransformOverlays.m: fixed bug when first scan of the analysis is empty --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index c6eca42e7..637f94dda 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -539,7 +539,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:length(outputData) + 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); From f4420e4a3b30e4fa17e42a5bf2ef77fcfc89f8ec Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 14 Dec 2018 11:29:05 +0200 Subject: [PATCH 005/254] getBaseSpaceOverlay.m: tried to fix mrExport2SR.m for surfaces, but not tested yet --- mrLoadRet/GUI/getBaseSpaceOverlay.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/GUI/getBaseSpaceOverlay.m b/mrLoadRet/GUI/getBaseSpaceOverlay.m index 8f8117a80..309f1e047 100644 --- a/mrLoadRet/GUI/getBaseSpaceOverlay.m +++ b/mrLoadRet/GUI/getBaseSpaceOverlay.m @@ -46,7 +46,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'); @@ -106,11 +106,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); From 8202538cda1fd16125777337568e88722bebb73f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 5 Feb 2019 00:12:30 +0200 Subject: [PATCH 006/254] mlrAdjustGUI.m: from matlab version 9.4 (and possibly before), some uimenu property names have changed and need to be replaced --- mrLoadRet/GUI/mlrAdjustGUI.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mrLoadRet/GUI/mlrAdjustGUI.m b/mrLoadRet/GUI/mlrAdjustGUI.m index 7591788a2..064329729 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))); From c426e214891c49a1df68895e715cc483e2b879ad Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 5 Feb 2019 00:39:02 +0200 Subject: [PATCH 007/254] mrLoadRetVersion.m: added matlab versions 9.0, 9.1, 9.4 and 9.5 to tested versions --- mrLoadRet/mrLoadRetVersion.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index 81de60b75..e51bbecef 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 9.0 9.1 9.4 9.5]; % expected toolbox if verLessThan('matlab','8.5') From a722ad5ab41987e11cff3d8c59efd87fe1d9a8fa Mon Sep 17 00:00:00 2001 From: jb85aub Date: Thu, 21 Mar 2019 12:50:59 +0200 Subject: [PATCH 008/254] transformROIs.m: made scriptable --- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 144 ++++++++++++++++-------- 1 file changed, 94 insertions(+), 50 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index d6e722f8c..d23042dd3 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -1,68 +1,103 @@ -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 +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end +if ieNotDefined('defaultParams'),defaultParams = 0;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 +currentBaseString = ['Current Base (' viewGet(thisView,'basename') ')']; -params.transformFunction = [{'User Defined'} transformFunctions]; -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 -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; + %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 - if 0 - %control here - %elseif - %other control here - else - askForParams = 0; - roiList = selectInList(thisView,'rois'); - if isempty(roiList) - askForParams = 1; + 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 +if ~ieNotDefined('roiList') + params.roiList = roiList; +end + +% if just getting params then return +if justGetParams,return,end + switch(params.roiSpace) case currentBaseString baseNum = viewGet(thisView,'currentbase'); @@ -85,7 +120,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') @@ -130,6 +165,15 @@ function transformROIs(thisView) return end for iRoi = 1:length(roi) + 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)); needToRefresh = 1; end From 1e8f75d073c93f6a33fa8756c131ff207b1f824f Mon Sep 17 00:00:00 2001 From: jb85aub Date: Wed, 1 May 2019 11:24:16 +0300 Subject: [PATCH 009/254] convertROICorticalDepth.m: take the current base instead of the based the ROI was created on if the latter is not a surface or a flat base + print the nunber of voxels that were added/removed from the ROI --- mrLoadRet/ROI/convertROICorticalDepth.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index b3884d456..f584b24c6 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -96,7 +96,11 @@ 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 + if viewGet(v,'basetype',baseNum)==0 + disp(sprintf('(convertROICorticalDepth) Converting %s based on base:%s because base:%s which this roi was created on is not a surface or a flat map',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); @@ -107,6 +111,7 @@ mrWarnDlg(sprintf('(convertROICorticalDepth) %s has no coordinates on this flat',roinames{roinum})); continue; end + nVoxelsOriginalROI = size(roiBaseCoords,2); % get base info baseVoxelSize = viewGet(v,'baseVoxelSize',baseNum); baseCoordMap = viewGet(v,'baseCoordMap',baseNum,params.referenceDepth); @@ -171,6 +176,7 @@ v = viewSet(v,'prevROIcoords',curROICoords); end disppercent(inf); + fprintf(1,'Number of voxels in original ROI: %d\t Number of voxels in modified ROI: %d\n',nVoxelsOriginalROI,size(roiBaseCoords,2)); end end v = viewSet(v,'currentROI',currentROI); From 9d6c91216c0fdc1a03282b57ab764edbbb822e00 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 1 May 2019 12:48:06 +0300 Subject: [PATCH 010/254] modifyROI.m: when removing coordinates, round both original coordinates and coordinates to remove, in case one of them include non-integer coordinates (which can happen if ROI coordinates have been converted from a different base) --- mrLoadRet/ROI/modifyROI.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/ROI/modifyROI.m b/mrLoadRet/ROI/modifyROI.m index 88603a6c4..e343d671a 100644 --- a/mrLoadRet/ROI/modifyROI.m +++ b/mrLoadRet/ROI/modifyROI.m @@ -88,7 +88,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; From 583055b305d9467808963e839552085bed00aa15 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Wed, 22 May 2019 11:39:36 +0300 Subject: [PATCH 011/254] glmAnalysis.m: issues a warning if all data are NaNs and does not attempt to set the overaly clipping values to avoid error --- .../Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m index 07425f4b0..74accfeaa 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m @@ -761,13 +761,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; From fb5303239c06b790c04445df6eb29fb4fddd5b3c Mon Sep 17 00:00:00 2001 From: jb85aub Date: Wed, 22 May 2019 11:56:57 +0300 Subject: [PATCH 012/254] loadAnat.m: added option to specify frame number for 4D datasets --- mrLoadRet/File/loadAnat.m | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/File/loadAnat.m b/mrLoadRet/File/loadAnat.m index e6b42e207..ce5f56785 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 @@ -93,21 +96,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 From e9cc866dc9862dc42a562e84152171c63c72526c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 5 Jun 2019 08:38:12 +0300 Subject: [PATCH 013/254] getEstimates.m: fixed a bug in computing the contrast HDR when parameter 'componentsCombination' is undefined, i.e. there is only one HRF component --- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getEstimates.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2c18489dd42ca094b36a8e28144f8df7735758fa Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 5 Jun 2019 08:41:54 +0300 Subject: [PATCH 014/254] glmPlot.m: fixed problems with errors bars that have been happening since matlab ver 8.4 when graphic object handles changed type --- .../Plugin/GLM_v2/newGlmAnalysis/glmPlot.m | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m index 91a25ea53..b4f55ba34 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)); @@ -285,16 +283,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 +320,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 @@ -756,7 +785,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 +838,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 From 168f1cd6ac3046f6954d7e63aa7b81097f40cae8 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Wed, 5 Jun 2019 12:48:24 +0300 Subject: [PATCH 015/254] glmPlot.m: starting at matlab version 9.5 (or possibly before), matlab crashed when trying to delete baseline. This was fixed by setting the baseline instead of deleting and redrawing it --- .../Plugin/GLM_v2/newGlmAnalysis/glmPlot.m | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m index b4f55ba34..147a5c39b 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m @@ -396,20 +396,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; @@ -764,12 +768,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 From c0c1fc9182fb98df2d46da906564ec60252c5514 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 20 Jul 2019 15:38:00 +0300 Subject: [PATCH 016/254] mlrExportROI.m, GLM_v2Plugin.m, viewGUIPlugin.m: adding option to export ROIs to fressrufer label format (but not finished yet) --- mrLoadRet/File/mlrExportROI.m | 28 ++++++++++++----- mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m | 31 +++++++++++++++++++ .../Nottingham/viewGUI/viewGUIPlugin.m | 2 ++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 019570240..87b96c311 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -1,7 +1,7 @@ % mlrExportROI.m % % $Id$ -% usage: mlrExportROI(v,saveFilename,) +% usage: mlrExportROI(v,saveFilename,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>,) % by: justin gardner % date: 07/14/09 % purpose: Export an ROI to a nifti image. Uses current roi @@ -17,7 +17,7 @@ function mlrExportROI(v,saveFilename,varargin) end % optional arguments -getArgs(varargin,{'hdr=[]'}); +getArgs(varargin,{'hdr=[]','exportToFreesurferLabel=0'}); % get the roi we are being asked to export roiNum = viewGet(v,'currentroi'); @@ -48,18 +48,30 @@ function mlrExportROI(v,saveFilename,varargin) 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 +if baseType ~= 2 && exportToFreesurferLabel + mrWarnDlg('Load a surface in order to export to freesurfer label format'); + return; +end +if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats, or when exporting to freesurfer label file, use basecoordmap + + switch(baseType) + case 1 + [~,baseCoords,baseCoordsHomogeneous] = getBaseSlice(v,1,3,viewGet(v,'baseRotate'),viewGet(v,'curBase'),baseType); + case 2 + + end + baseDims = size(baseCoords); baseDims = baseDims ([1 2 4]); - + if baseType==1 + 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',baseDims(1),baseDims(2),baseDims(3))); + 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(baseDims)); 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) + % 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',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 diff --git a/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m b/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m index e4ab92c51..dff981edc 100644 --- a/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m +++ b/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m @@ -258,6 +258,7 @@ mlrAdjustGUI(thisView,'set','pasteRoiMenuItem','label','Paste ROI(s)'); mlrAdjustGUI(thisView,'set','editRoiMenuItem','label','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'); @@ -665,6 +666,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/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m index d20902b4e..4925c0ed0 100644 --- a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m +++ b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m @@ -197,6 +197,8 @@ 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','exportROIfreesurferMenuItem','location','/ROI/'); + mlrAdjustGUI(thisView,'set','exportROIfreesurferMenuItem','label','Export to Freesurfer Label'); mlrAdjustGUI(thisView,'set','exportROIMenuItem','location','/ROI/'); mlrAdjustGUI(thisView,'set','Import Freesurfer Label','location','/ROI/'); mlrAdjustGUI(thisView,'set','Import Freesurfer Label','separator','off'); From a5dc13bcacfc344615796260674ad382316e295c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 20 Jul 2019 20:20:50 +0300 Subject: [PATCH 017/254] mlrExportROI.m: finished option to export ROI to Freesurfer label format (c0c1fc9) --- mrLoadRet/File/mlrExportROI.m | 145 +++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 57 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 87b96c311..8bd5bdb1e 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -48,22 +48,26 @@ function mlrExportROI(v,saveFilename,varargin) baseCoordMap = viewGet(v,'basecoordmap'); baseType = viewGet(v,'basetype'); -if baseType ~= 2 && exportToFreesurferLabel +if ~ismember(baseType,[2]) && exportToFreesurferLabel mrWarnDlg('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 - switch(baseType) - case 1 + if baseType == 1 && ~exportToFreesurferLabel [~,baseCoords,baseCoordsHomogeneous] = getBaseSlice(v,1,3,viewGet(v,'baseRotate'),viewGet(v,'curBase'),baseType); - case 2 - + 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 baseDims = size(baseCoords); baseDims = baseDims ([1 2 4]); - if baseType==1 + 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',baseDims(1),baseDims(2),baseDims(3))); end % make sure that baseCoords are rounded (they may not be if we are working with a baseCoordMap's flat map) @@ -71,81 +75,108 @@ function mlrExportROI(v,saveFilename,varargin) baseCoordsHomogeneous = round(baseCoordsHomogeneous); baseCoordsLinear = mrSub2ind(baseCoordMap.dims,baseCoordsHomogeneous(1,:),baseCoordsHomogeneous(2,:),baseCoordsHomogeneous(3,:)); - % 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',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',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; + end end - else baseDims = hdr.dim(2:4)'; end -if ~passedInHeader +if ~passedInHeader && ~exportToFreesurferLabel b = viewGet(v,'base'); 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); + fprintf('(mlrExportROI) Exporting ROI %s to %s with dimensions set to match base %s: [%i %i %i]\n',roiName, saveFilename{iRoi},viewGet(v,'baseName'),baseDims(1),baseDims(2),baseDims(3)); % get roi coordinates in base coordinates roiBaseCoords = getROICoordinates(v,roiNum(iRoi),0); - % check roiBaseCoords - if isempty(roiBaseCoords) - mrWarnDlg('(mlrExportROI) This ROI does not have any coordinates in the base'); - return - end + if ~exportToFreesurferLabel + % create a data structure that has all 0's + d = zeros(baseDims); - % 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)); - % only use ones that are in bounds - roiBaseCoords = roiBaseCoords(:,xCheck & yCheck & sCheck); + % 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)); + % only use ones that are in bounds + roiBaseCoords = roiBaseCoords(:,xCheck & yCheck & sCheck); + end + % convert to linear coordinates roiBaseCoordsLinear = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); - if ~isempty(baseCoordMap) && baseType==1 %for flats, use basecoordmap to transform ROI from canonical base to multi-depth flat map + if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats and surfaces, use basecoordmap to transform ROI from canonical base to multi-depth flat map roiBaseCoordsLinear = ismember(baseCoordsLinear,roiBaseCoordsLinear); 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); + % check roiBaseCoords + if isempty(roiBaseCoords) || ~nnz(roiBaseCoordsLinear) + mrWarnDlg(sprintf('(mlrExportROI) This ROI (%s) does not have any coordinates in the base',roiName)); + else - cbiWriteNifti(saveFilename{iRoi},d,hdr); + + if exportToFreesurferLabel + % in order to export to label format, select vertices that are within the ROI + + % reshape to vertices * depths + roiBaseCoordsLinear = reshape(roiBaseCoordsLinear, [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: + roiBaseCoordsLinear = find(any(roiBaseCoordsLinear,2)); + %actual coordinates in label file will be midway between inner and outer surface + vertexCoords = (baseCoordMap.innerVtcs(roiBaseCoordsLinear,:)+baseCoordMap.outerVtcs(roiBaseCoordsLinear,:))/2; + + % a Freesurfer label file is text file with a list of vertex numbers and coordinates + fileID = fopen(saveFilename{iRoi},'w'); + freesurferName = extractBetween(baseCoordMap.path,'subjects\','\surfRelax'); + if isempty(freesurferName) + freesurferName= '[????]'; + end + fprintf(fileID,'#!ascii label, ROI exported from subject %s using mrTools (mrLoadRet v%.1f)\n', freesurferName, mrLoadRetVersion); + fprintf(fileID,'%d %f %f %f 1\n', [roiBaseCoordsLinear-1, vertexCoords]'); + fclose(fileID); + else + % 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); + else + cbiWriteNifti(saveFilename{iRoi},d,hdr); + end + end end end From cb3fb9b9b7a08939e17bdff197a4e1062d757ff0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 20 Jul 2019 23:13:36 +0300 Subject: [PATCH 018/254] viewGUIPlugin.m: reorganized ROI export/import menus --- .../Nottingham/viewGUI/viewGUIPlugin.m | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m index 4925c0ed0..141325a8c 100644 --- a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m +++ b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m @@ -197,19 +197,24 @@ 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','exportROIfreesurferMenuItem','location','/ROI/'); - mlrAdjustGUI(thisView,'set','exportROIfreesurferMenuItem','label','Export to Freesurfer Label'); - 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','Import Freesurfer Label','location','/ROI/Import/'); mlrAdjustGUI(thisView,'set','Import Freesurfer Label','separator','off'); - mlrAdjustGUI(thisView,'set','importROIMenuItem','location','/ROI/'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','location','/ROI/Import/'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','separator','off'); 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','exportROIfreesurferMenuItem','label','to Freesurfer Label format'); + mlrAdjustGUI(thisView,'set','exportROIMenuItem','label','to NIFTI format'); + mlrAdjustGUI(thisView,'set','Import Freesurfer Label','label','from Freesurfer label file'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','label','from NIFTI file'); % mlrAdjustGUI(thisView,'set','copyRoiMenuItem','label','Copy selected'); % mlrAdjustGUI(thisView,'set','pasteRoiMenuItem','label','Paste'); mlrAdjustGUI(thisView,'set','editRoiMenu','label','Edit'); From 31dd21f2cc781d726d42adfa3a8a54cdf8301cc0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Jul 2019 13:14:18 +0300 Subject: [PATCH 019/254] mlrExportROI.m: extractBetween.m seems to now output cell arrays instead of character strings --- mrLoadRet/File/mlrExportROI.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 8bd5bdb1e..009f56351 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -153,6 +153,8 @@ function mlrExportROI(v,saveFilename,varargin) freesurferName = extractBetween(baseCoordMap.path,'subjects\','\surfRelax'); if isempty(freesurferName) freesurferName= '[????]'; + elseif iscell(freesurferName) %in newer versions of Matlab, extractBetween may reutrn a cell array + freesurferName = freesurferName{1}; end fprintf(fileID,'#!ascii label, ROI exported from subject %s using mrTools (mrLoadRet v%.1f)\n', freesurferName, mrLoadRetVersion); fprintf(fileID,'%d %f %f %f 1\n', [roiBaseCoordsLinear-1, vertexCoords]'); From 24f2828cc06083eb60816b29b3dce5fbbd07a92a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Jul 2019 15:49:36 +0300 Subject: [PATCH 020/254] mlrExportROI.m: added number of vertices in label file and tried to convert coordinates to Freesurfer reference frame (but doesn't seem correct) --- mrLoadRet/File/mlrExportROI.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 009f56351..48fbd0e23 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -49,7 +49,7 @@ function mlrExportROI(v,saveFilename,varargin) baseCoordMap = viewGet(v,'basecoordmap'); baseType = viewGet(v,'basetype'); if ~ismember(baseType,[2]) && exportToFreesurferLabel - mrWarnDlg('Load a surface in order to export to freesurfer label format'); + 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; @@ -69,6 +69,8 @@ function mlrExportROI(v,saveFilename,varargin) baseDims = baseDims ([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',baseDims(1),baseDims(2),baseDims(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(baseDims)); @@ -147,6 +149,10 @@ function mlrExportROI(v,saveFilename,varargin) roiBaseCoordsLinear = find(any(roiBaseCoordsLinear,2)); %actual coordinates in label file will be midway between inner and outer surface vertexCoords = (baseCoordMap.innerVtcs(roiBaseCoordsLinear,:)+baseCoordMap.outerVtcs(roiBaseCoordsLinear,:))/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) % a Freesurfer label file is text file with a list of vertex numbers and coordinates fileID = fopen(saveFilename{iRoi},'w'); @@ -157,6 +163,7 @@ function mlrExportROI(v,saveFilename,varargin) freesurferName = freesurferName{1}; end fprintf(fileID,'#!ascii label, ROI exported from subject %s using mrTools (mrLoadRet v%.1f)\n', freesurferName, mrLoadRetVersion); + fprintf(fileID,'%d\n', size(vertexCoords,1)); fprintf(fileID,'%d %f %f %f 1\n', [roiBaseCoordsLinear-1, vertexCoords]'); fclose(fileID); else From f75ed8db1d0bd26e1510d1279b5efadba45b992d Mon Sep 17 00:00:00 2001 From: jb85aub Date: Mon, 22 Jul 2019 22:31:13 +0300 Subject: [PATCH 021/254] mlrExportROI.m: fixed bug with saveFilename variable when called from script --- mrLoadRet/File/mlrExportROI.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 48fbd0e23..125edd61f 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -4,8 +4,8 @@ % usage: mlrExportROI(v,saveFilename,<'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 +% purpose: Export ROI(s) to a nifti image or Freesurfer label file. Uses +% current roi(s) and current base in view to export. Pass in a nifti % header as hdr argument if you want to use a different header % function mlrExportROI(v,saveFilename,varargin) @@ -27,7 +27,7 @@ function mlrExportROI(v,saveFilename,varargin) end if ischar(saveFilename) - saveFileName = {saveFilename}; + saveFilename = {saveFilename}; end if ~isequal(length(roiNum),length(saveFilename)) mrWarnDlg('(mlrExportROI) number of file names must be identical to number of ROIs'); From 3d2acacf49b391d6ca24dbac7bb52803d898b047 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Tue, 23 Jul 2019 12:29:24 +0300 Subject: [PATCH 022/254] convertROICorticalDepth.m: made it fully scriptable --- mrLoadRet/File/mlrExportROI.m | 2 +- mrLoadRet/ROI/convertROICorticalDepth.m | 235 ++++++++++++------------ 2 files changed, 119 insertions(+), 118 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 125edd61f..e78f92e44 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -1,7 +1,7 @@ % mlrExportROI.m % % $Id$ -% usage: mlrExportROI(v,saveFilename,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>,) +% usage: mlrExportROI(v,saveFilename,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>) % by: justin gardner % date: 07/14/09 % purpose: Export ROI(s) to a nifti image or Freesurfer label file. Uses diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index f584b24c6..632ebf4d8 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,25 +10,35 @@ % 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 + % 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') % get cortical depth @@ -58,130 +68,121 @@ 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}; + if defaultParams + params.roiList = viewGet(v,'curROI'); + else + params.roiList = selectInList(v,'rois'); 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'); +% now go through and convert anything the user selected +for roinum = params.roiList + roiName = viewGet(v,'roiname', roinum); + disppercent(-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 - disp(sprintf('(convertROICorticalDepth) Converting %s based on base:%s because base:%s which this roi was created on is not a surface or a flat map',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 - nVoxelsOriginalROI = size(roiBaseCoords,2); - % 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); - fprintf(1,'Number of voxels in original ROI: %d\t Number of voxels in modified ROI: %d\n',nVoxelsOriginalROI,size(roiBaseCoords,2)); + 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 - v = viewSet(v,'currentROI',currentROI); - if needToRefresh - refreshMLRDisplay(viewGet(v,'viewNum')); + % 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',roiName)); + continue; + end + nVoxelsOriginalROI = size(roiBaseCoords,2); + % 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. !!!',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)); + + 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); + fprintf(1,'(convertROICorticalDepth) Number of voxels in original ROI: %d\t Number of voxels in modified ROI: %d\n',nVoxelsOriginalROI,size(roiBaseCoords,2)); end +v = viewSet(v,'currentROI',currentROI); From 62b01584ff8eb839cbc0ff1955d8fba14553f73c Mon Sep 17 00:00:00 2001 From: jb85aub Date: Wed, 4 Sep 2019 14:08:32 +0300 Subject: [PATCH 023/254] multipleTestsAdjustment.m: added options not to run either FWE or FDR correction --- .../multipleTestsAdjustment.m | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m index faa832bd3..ba942d3fd 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m @@ -5,12 +5,20 @@ % 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 From 2564a0f145d2baea46129168657e8aff5eab7a01 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 28 Sep 2019 18:24:28 +0300 Subject: [PATCH 024/254] transformStatistic.m: added help text --- mrLoadRet/Analysis/transformStatistic.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Analysis/transformStatistic.m b/mrLoadRet/Analysis/transformStatistic.m index dca018f11..4a4276294 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'; From 41ec7f1b980acaff2e04591a11f5189fe46eef23 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 28 Sep 2019 18:26:28 +0300 Subject: [PATCH 025/254] multipleTestsAdjustment.m: added help for recently added optional arguments --- .../combineTransformOverlayFunctions/multipleTestsAdjustment.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m index ba942d3fd..a8c2721cb 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m @@ -1,6 +1,7 @@ -% [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 % From a6433de0204402d26bda5f77fd21e2bf7ed5412c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 28 Sep 2019 18:28:58 +0300 Subject: [PATCH 026/254] combineTransformOverlays.m: added option to mask overlays using a combintation (union or intersection) of ROIs --- .../Plugin/GLM_v2/combineTransformOverlays.m | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 637f94dda..7ed37ac89 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -13,10 +13,7 @@ % % $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 @@ -46,12 +43,15 @@ combineFunctionsMenu = putOnTopOfList(combineFunctionsMenu{2},combineFunctionsMenu); end params.customCombineFunction = '';%(''@(x)max(0,-norminv(x))'; + inputOutputTypeMenu = {'3D Array','4D Array','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; @@ -61,6 +61,8 @@ else baseSpaceOption='enable=1'; end + overlayList = viewGet(thisView,'curOverlay'); + roiList = viewGet(thisView,'curROI'); askForParams = 1; while askForParams @@ -75,6 +77,7 @@ {'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.'},... @@ -102,23 +105,41 @@ 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.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 ~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 @@ -155,7 +176,7 @@ 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 + %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) if ~isempty(overlayData(iOverlay).data{iScan}) @@ -183,6 +204,7 @@ 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 @@ -212,6 +234,38 @@ end end +% mask overlay using ROI(s) +if ~strcmp(params.roiMask,'None') && ~isempty(params.roiList) + for iScan = 1:nScans + 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 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 + elseif 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 + %overlay names for iOverlay = 1:length(params.overlayList) overlayNames{iOverlay} = overlayData(iOverlay).name; From 706d5b552b4869b78e82744ba4225259c2738c7b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 28 Sep 2019 21:26:35 +0300 Subject: [PATCH 027/254] (julien) replaced calls to isfile.m by calls to mlrIsFile.m in a number of functions --- mrLoadRet/File/mlrExportOFF.m | 2 +- mrLoadRet/Plugin/pRF/pRF.m | 6 +++--- mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m | 2 +- mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m | 2 +- mrUtilities/File/neuropythy/mlrImportNeuropythy.m | 6 +++--- mrUtilities/make/mlrMake.m | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mrLoadRet/File/mlrExportOFF.m b/mrLoadRet/File/mlrExportOFF.m index f50239dbc..22cc036a5 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(:); diff --git a/mrLoadRet/Plugin/pRF/pRF.m b/mrLoadRet/Plugin/pRF/pRF.m index 42e9d91f7..974046536 100644 --- 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/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m index 04388a38b..6e1d4b87f 100644 --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -126,7 +126,7 @@ end system(commandString); else - if isfile(outFile) + 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); diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m index c322568c5..c76ca6254 100644 --- a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m @@ -70,7 +70,7 @@ end % check the file -if ~isfile(filename) +if ~mlrIsFile(filename) disp(sprintf('(mlrImportFreesurferLabel) Could not find file %s',filename)); return end 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/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 From 6dc4e8be8f46d96ee4dfb1e4f17990a98a6cc0fc Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 28 Sep 2019 21:29:27 +0300 Subject: [PATCH 028/254] mlrAnatomyPlugin.m: implemented importing Freesurfer labels as ROIs from the GUI by calling mlrImportFreesurferLabel.m --- .../Plugin/mlrAnatomy/mlrAnatomyPlugin.m | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m index 3b8595ca2..d48fb70e5 100644 --- a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m +++ b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m @@ -505,7 +505,24 @@ 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'},'Select Freesurfer label file(s)', '*.*','multiselect','on'); +if isnumeric(labelFilenames) + return +elseif ischar(labelFilenames) + labelFilenames = {labelFilenames}; +end + +for iROI = 1:length(labelFilenames) + %create volume ROI from label file + roi = mlrImportFreesurferLabel(fullfile(pathname,labelFilenames{iROI})); + % Add ROI to view + v = viewSet(v,'newROI',roi); + v = viewSet(v,'currentROI',viewGet(getMLRView,'nrois')); +end + +refreshMLRDisplay(v); + return %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% From 0c363c2d6ede81e7ab0a5ecf34f1ebf81fc878f5 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 29 Sep 2019 12:22:45 +0300 Subject: [PATCH 029/254] getBaseSpaceOverlay.m: added comments about problems with the way surfaces are handled --- mrLoadRet/GUI/getBaseSpaceOverlay.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/GUI/getBaseSpaceOverlay.m b/mrLoadRet/GUI/getBaseSpaceOverlay.m index 309f1e047..aa601003c 100644 --- a/mrLoadRet/GUI/getBaseSpaceOverlay.m +++ b/mrLoadRet/GUI/getBaseSpaceOverlay.m @@ -78,7 +78,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 +91,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; From 8c873b77e10d27cf982e7b761052d1e4ceb63e36 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Mon, 30 Sep 2019 17:19:16 +0300 Subject: [PATCH 030/254] combineTransformOverlays.m: corrected bug whne passing overlayList as argument --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 7ed37ac89..9b62011ff 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -61,8 +61,12 @@ else baseSpaceOption='enable=1'; end - overlayList = viewGet(thisView,'curOverlay'); - roiList = viewGet(thisView,'curROI'); + if ieNotDefined('overlayList') + overlayList = viewGet(thisView,'curOverlay'); + end + if ieNotDefined('roiList') + roiList = viewGet(thisView,'curROI'); + end askForParams = 1; while askForParams From e3359140a5d5cc4dca4aaf4a8271ec805e09c1c7 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 1 Oct 2019 17:41:05 +0300 Subject: [PATCH 031/254] combineTransformOverlays.m,transformROIs.m: now displays extended error message when an error is caught in the call to the ROI/overlay combine/transform function --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 2 +- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 9b62011ff..a70d74328 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -411,7 +411,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 diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index d23042dd3..10687c8f4 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -161,7 +161,7 @@ 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) From 1f63ceae4c6facc9a6cccce2fb5eb0f8790f1b2c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 1 Oct 2019 17:42:13 +0300 Subject: [PATCH 032/254] added makeROIsExactlyContiguous.m to transformROI functions --- .../makeROIsExactlyContiguous.m | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m new file mode 100644 index 000000000..a455bdd8c --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m @@ -0,0 +1,59 @@ +% makeROIsExactlyContiguous.m +% +% usage: transformedRois = makeROIsExactlyContiguous(rois,margin,) +% by: julien besle +% date: 11/01/2011 +% +% purpose: attribute voxels shared by two ROIs to the closest of these two ROIs, with +% the two resulting two ROIs then becoming exactly contiguous. +% This function will run with more than two ROIs and will apply to all pairs of ROIs +% This assumes that no voxel is shared by more than two ROIs. If this is the case, +% then results will depend on the order in which the ROIs are passed + +function rois = makeROIsExactlyContiguous(rois) + +if ~ismember(nargin,[1]) + help expandROI; + 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 = iRoi+1:length(rois) + coords1 = rois(iRoi).coords'; + coords2 = rois(jRoi).coords'; + [commonCoordinates, indexROI1, indexROI2] = intersect(coords1,coords2,'rows'); + %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'))=[]; + end +end + + From 1a729a9ee6ddb788f8b60421cb0fe8958e6b433e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 2 Oct 2019 08:23:35 +0300 Subject: [PATCH 033/254] makeROIsExactlyContiguous.m: only goes through the whole loop if ROIs actually share voxels --- .../makeROIsExactlyContiguous.m | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m index a455bdd8c..55886910f 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m @@ -36,23 +36,25 @@ coords1 = rois(iRoi).coords'; coords2 = rois(jRoi).coords'; [commonCoordinates, indexROI1, indexROI2] = intersect(coords1,coords2,'rows'); - %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; + 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'))=[]; end - % delete coords that belong to the other ROI - rois(iRoi).coords(:,indexROI1(~belongsToROI1'))=[]; - rois(jRoi).coords(:,indexROI2(belongsToROI1'))=[]; end end From bc58bfac643ac098d1d2dcc7f07d35ab755fa684 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 2 Oct 2019 08:28:44 +0300 Subject: [PATCH 034/254] convertROICorticalDepth.m: removed mimimum possible value for minDepth (although mrParamsDialo does not seem to enforce this), moved removal of all voxels to the end of the function, and added some explanatory comments --- mrLoadRet/ROI/convertROICorticalDepth.m | 54 +++++++++++++------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index 632ebf4d8..8af4f682a 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -56,10 +56,10 @@ 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} = {'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 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'}; + 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'}; if defaultParams params = mrParamsDefault(paramsInfo); else @@ -118,7 +118,7 @@ continue; end nVoxelsOriginalROI = size(roiBaseCoords,2); - % get base info + % 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); baseDims = baseCoordMap.dims; @@ -127,10 +127,12 @@ 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 + % 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 @@ -140,49 +142,51 @@ 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 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; + % (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 coordinates at this depth + % get the (rounded) base 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 + % 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)); % 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); + 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 - roiBaseCoords = []; - [roiBaseCoords(1,:) roiBaseCoords(2,:) roiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsReferenceLinear); - roiBaseCoords(4,:) = 1; - v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,1); + 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 disppercent(inf); - fprintf(1,'(convertROICorticalDepth) Number of voxels in original ROI: %d\t Number of voxels in modified ROI: %d\n',nVoxelsOriginalROI,size(roiBaseCoords,2)); + 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); From 8aaf121d88f0155f66d4ee58cc723ef09a9e12da Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 2 Oct 2019 08:31:43 +0300 Subject: [PATCH 035/254] convertROICorticalDepth.m: started implementing option to exclude voxels that span two distant cortical locations (but not satisfactory yet) --- mrLoadRet/ROI/convertROICorticalDepth.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index 8af4f682a..f23ab86b4 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -60,6 +60,7 @@ 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 @@ -159,6 +160,14 @@ 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)); + roiBaseCoordsLinear = setdiff(roiBaseCoordsLinear,baseCoords(~isInROI,:)); + end % now convert back to regular coords additionalRoiBaseCoords = []; [additionalRoiBaseCoords(1,:), additionalRoiBaseCoords(2,:), additionalRoiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsLinear); From 497fbd5ae7fc47bb322dc23a8e89373420ec93ff Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 2 Oct 2019 14:51:40 +0300 Subject: [PATCH 036/254] projectCorticalDepth.m: finished implementing option to exclude ROI voxels belonging to distant locations on cortex --- mrLoadRet/ROI/convertROICorticalDepth.m | 98 +++++++++++++++++++------ 1 file changed, 77 insertions(+), 21 deletions(-) diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index f23ab86b4..7541197a6 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -32,6 +32,7 @@ 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'); @@ -41,6 +42,7 @@ end if ieNotDefined('params') + askForParams = 1; % get cortical depth corticalDepth = viewGet(v,'corticalDepth'); referenceDepth= mean(corticalDepth); @@ -53,26 +55,39 @@ 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,'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 - % now select rois - % put up a dialog with rois to select - if defaultParams - params.roiList = viewGet(v,'curROI'); - else - params.roiList = selectInList(v,'rois'); + 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 end if isempty(params),return,end @@ -109,6 +124,13 @@ 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 @@ -122,6 +144,7 @@ % 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)); @@ -145,6 +168,38 @@ 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 @@ -166,7 +221,8 @@ 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)); - roiBaseCoordsLinear = setdiff(roiBaseCoordsLinear,baseCoords(~isInROI,:)); + % 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 = []; From b2269de404ed7cba9c913503a321cea03ff96f11 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 2 Oct 2019 23:02:32 +0300 Subject: [PATCH 037/254] makeROIsExactlyContiguous.m: handles ROIs that have non-rounded and non-unique voxels --- .../makeROIsExactlyContiguous.m | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m index 55886910f..e0384769a 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m @@ -30,12 +30,12 @@ end end - for iRoi = 1:length(rois) for jRoi = iRoi+1:length(rois) - coords1 = rois(iRoi).coords'; - coords2 = rois(jRoi).coords'; - [commonCoordinates, indexROI1, indexROI2] = intersect(coords1,coords2,'rows'); + % 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'); @@ -51,9 +51,14 @@ belongsToROI1(iCoords) = true; end end - % delete coords that belong to the other ROI - rois(iRoi).coords(:,indexROI1(~belongsToROI1'))=[]; - rois(jRoi).coords(:,indexROI2(belongsToROI1'))=[]; +% % 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 duplciates removed) + rois(iRoi).coords = [coords1; commonCoordinates(belongsToROI1,:)]'; + rois(jRoi).coords = [coords2; commonCoordinates(~belongsToROI1,:)]'; end end end From 26931026a5579217c0b73d7f3237d3c76ec009bd Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 10 Oct 2019 20:24:56 +0300 Subject: [PATCH 038/254] fweAdjust.m: makes sure p values are in a column vector --- .../Plugin/GLM_v2/newGlmAnalysis/fweAdjust.m | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) 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 From 463f25f9cc1ca4036570eb38a7a070eacc9a91ae Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 14 Oct 2019 17:50:43 +0300 Subject: [PATCH 039/254] combineROIs.m: round coordinates to deal with ROIs that have non-integer coordinates --- mrLoadRet/ROI/combineROIs.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); From 469a91a606bc42cd548df77f338e96d25a5792b1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 7 Nov 2019 03:52:58 +0200 Subject: [PATCH 040/254] (julien) added hrf model hrfCustom.m to add arbitrary HRF model using numerical values --- .../GLM_v2/newGlmAnalysis/glmAnalysisGUI.m | 2 +- .../Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m index c32413da9..972992c87 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)'; diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m new file mode 100644 index 000000000..8f70a4c87 --- /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 From fb2706e6d38568eabe324b1faf24e54272a9abb5 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Tue, 21 Jan 2020 13:55:23 +0000 Subject: [PATCH 041/254] my stuff --- mrAlign/regHistogram.mexmaci64 | Bin 12924 -> 8892 bytes mrLoadRet/Edit/setFramePeriod.m | 3 +- mrLoadRet/GUI/mrPrint.m | 2 +- .../GLM_v2/newGlmAnalysis/glmPlot_edit.m | 852 ++++++ .../GLM_v2/newGlmAnalysis/hrfDoubleGamma.m | 6 + mrLoadRet/Plugin/pRF/pRF.m | 188 +- mrLoadRet/Plugin/pRF/pRFFit.m | 219 +- mrLoadRet/Plugin/pRF/pRFGUI.m | 3 + .../pRFGetSomatoStimImageFromStimfile.m | 280 ++ mrLoadRet/Plugin/pRF_somato/pRFMergeParams.m | 111 + mrLoadRet/Plugin/pRF_somato/pRF_somato.m | 632 +++++ mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 2451 +++++++++++++++++ mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m | 306 ++ mrLoadRet/Plugin/pRF_somato/pRF_somatoPlot.m | 411 +++ .../Plugin/pRF_somato/pRF_somatoPlugin.m | 62 + .../File/FreeSurfer/mlrImportFreeSurfer.m | 4 +- .../ImageProcessing/convolve.mexmaci64 | Bin 0 -> 42812 bytes mrUtilities/ImageProcessing/corrDn.mexmaci64 | Bin 30192 -> 38708 bytes .../ImageProcessing/myCinterp3.mexmaci64 | Bin 8648 -> 8728 bytes mrUtilities/ImageProcessing/upConv.mexmaci64 | Bin 30192 -> 42804 bytes mrUtilities/MatlabUtilities/mrDisp.mexmaci64 | Bin 8752 -> 8824 bytes .../mrFlatMesh/assignToNearest.mexmaci64 | Bin 8752 -> 8832 bytes mrUtilities/mrFlatMesh/dijkstra.mexmaci64 | Bin 19704 -> 20120 bytes 23 files changed, 5461 insertions(+), 69 deletions(-) mode change 100644 => 100755 mrLoadRet/GUI/mrPrint.m create mode 100755 mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot_edit.m mode change 100644 => 100755 mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRF.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFFit.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRFMergeParams.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRF_somato.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRF_somatoPlot.m create mode 100755 mrLoadRet/Plugin/pRF_somato/pRF_somatoPlugin.m mode change 100644 => 100755 mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m create mode 100755 mrUtilities/ImageProcessing/convolve.mexmaci64 diff --git a/mrAlign/regHistogram.mexmaci64 b/mrAlign/regHistogram.mexmaci64 index 01c1ff1122bfe10ac0132c627f23cf851ca7d051..2598c5f4ee008d7d6a4cbce7be2c29e5d58fa1d2 100755 GIT binary patch literal 8892 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 literal 12924 zcmeHOU2GKB6`oz2#1NBND>nygsRl6Dd0ls+D^((qc*8%WJFuRN(~7}Y`=47 zX1%-in#cBGu5{;~bI-Zo`MLM(!=2eb-TBwO2RY7F!f{*~`eW!DKn!7|_BeXAo8x4; z)z{X_rt%*`KFe7=Fu5h@4_IPZ)|7tDVld3Zt2AYA*$cpz&Z!eKn9Is?T+=(_*{MRj zx1Taa&srM9yRzDd)xxvQnRs%K66sS^=oR9PTX@YD2JuMCT$|@#Fcyu^1zd>dsWO*( zECk|d4~jKe4#qV#9PPFg3h@LB?@dc?^EO_dub7l&J$fh{?UKXMP>k{U`}=bXZ@UFR zd?AL7O(UI?ZzI97>=j$Z9Eo7jcAy13ofC@N-fLNoM&$TFZ)Yqb_i1WTJR6SATW;QH zKkYpQiS^yFxm}p=p9kxy(b=a)d?B7KBe^U`0*m=qiS^VP%z-057mwmyYRPDjQnQVm4hI^Y~O9q>UR#r zHKn({IozoR)Pee!)IhItD5ma@*NZPV*M)4hez&5=!zl53kpvv!wdLXc2%MW+1aC_~ z?TkeihBReBn2);>v#v#PW5HOU2rP-P-o)*}n2o<=45_*guBz-*)Pv!m5-$cnr@^c& zS-3vQ61M$K_@l|iInE5I%LDZ!Vl|qz+ zX6xB9@G}ROfxmiV8DG^z@+6a|q{O(}zcH!*f|PwjliHJW*bgvlJZ&(3&vaVkYg1`{ z@8vYVZ)WH-{`e(W1%EEhxBnr{uRA6E5CG+{+qV2MToI@Fit=RQo{N8DfpFiaaA8#M zyFLmEv3iY*{+cwK2h8#h@N$Fv1-{PZ5c+H+y3A<^qP~O!wf}K0n#@#&L6+RV)iUw&ouWTZqJHFMz>EvH|aIb zNF%#5#?8dXB~s!}N!-&oEn(r%F)Y0P4jxj?na1gyO+#Y&T6DG^`K@?#YUHqs^3B&? zvZqH5yZJqT!OY*F&^m>1+Yx;mwxVM1lxO5{Nr67os+1DRQfcF#^>^Tb_KbAMqb#63$VuV*DwtF}WWQH@s=~Cm8+4?}_{lBhQGxr3*kYk#Xw} zOT*tRn>~v8w3lBeob{cfLn4lNpLH3&3(`o(x!=*P_DOpE&^Rd>f1UL~w|V&J3BU1R zp{SPNhd29;v$JbKO2$O9k(m_;T;x4lsnl3KimV{t1*F&bOdM&+pdckyktg#i@-?aE zV%`j2HHbwF&HEvh)x^O^Sk*j|v$<8d^$5GIADRWZ6q;9WEX980)??C5yT$5J!ckpK zp4!;OD)IyKt66qkKk6d|}n)$c{|n?}+#d z{06b&lZK{Il-~uAR5O(=M;}(Q-xu;}?|*+Pszc#H`J7s?ACLpcU;!)aB>`^^v+?J*M*NF;%1Q+0FH!-VqynIm-+_@aIMu{&1)n&2+O3yvHI9{`OuvV~ zsZ2NdQaUdsPFKGw?iD-4eWKhkF$)2%@$6%5qvci%+g2Lyu$$*mqCj(9foD4V_a5CUzz{!A<0Vf0h%M4UJUAdd= zKhY%9C-8ybemU5)U+&feY8SWTfM>J`A0u|LkL_h#cy!_8_`zA+m9XX-51~pu_aGnxwY(2#{*f55RVUGJT z`criCLH9psba%poAA5qa?3-h-^a(R-Pve=()131 + erAnalNames = viewGet(thisView,'analysisNames'); + erAnalNum = find(buttondlg('Choose a deconvolution analysis or press OK if none is required',erAnalNames(erAnalyses))); + end + if all(~size(erAnalNum)) + return + end + erAnalNum = erAnalyses(erAnalNum); + end + if ~isempty(erAnalNum) + % get the event related data + deconvData = viewGet(thisView,'d',scanNum,erAnalNum); + deconvAnalysisParams = convertOldGlmParams(viewGet(thisView,'analysisParams',erAnalNum)); + %check that the EVs are compatible between the deconvolution and the GLM analysis + if ~isfield(deconvData,'EVnames') + mrWarnDlg('(glmPlot) Cannot plot old deconvolution analysis'); + clear('deconvData'); + elseif isequal(deconvData.EVnames,glmData.EVnames) + plotDeconvolution=1; + else + mrWarnDlg('(glmPlot) Name of EVs in deconvolution and GLM analyses are incompatible.'); + clear('deconvData'); + end + end + % + end +else + plotDeconvolution=0; + plotBetaWeights = 0; +end + + +%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& Set graph constants + +% select the window to plot into +fignum = selectGraphWin; +initializeFigure(fignum,max(numberEVs,numberContrasts)) +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; +yMargin = .01; +for iPlot = 1:length(roi)+1 + betaSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,1,subplotXgrid,subplotYgrid,xMargin,yMargin); + contrastSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,2,subplotXgrid,subplotYgrid,xMargin,yMargin); + ehdrSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,3,subplotXgrid,subplotYgrid,xMargin,yMargin); + contrastEhdrSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,4,subplotXgrid,subplotYgrid,xMargin,yMargin); + stePopupPosition(iPlot,:) = getSubplotPosition(iPlot,5,subplotXgrid,subplotYgrid,xMargin,yMargin); + tSeriesButtonPosition(iPlot,:) = getSubplotPosition(iPlot,6,subplotXgrid,subplotYgrid,xMargin,yMargin); +end +deconvButtonPosition = getSubplotPosition(3+length(roi),5,subplotXgrid,subplotYgrid,xMargin,yMargin); +ehdrButtonPosition = getSubplotPosition(3+length(roi),6,subplotXgrid,subplotYgrid,xMargin,yMargin); +legendBetaPosition = getSubplotPosition(3+length(roi),1,subplotXgrid,subplotYgrid,xMargin,yMargin); +legendContrastsPosition = getSubplotPosition(3+length(roi),2,subplotXgrid,subplotYgrid,xMargin,yMargin); +legendEhdrPosition = getSubplotPosition(3+length(roi),3,subplotXgrid,subplotYgrid,xMargin,yMargin); +legendHdrContrastPosition = getSubplotPosition(3+length(roi),4,subplotXgrid,subplotYgrid,xMargin,yMargin); + + +%&&&&&&&&&&&&&&&&&&&&&& LOOP on voxel + ROIs (if any) %&&&&&&&&&&&&&&&&&&&&&& + +if ~isempty(roi) + volumeBetas = reshape(glmData.ehdr,[numel(r2data) size(glmData.ehdr,4) size(glmData.ehdr,5)]); +% volumeBetaSte = reshape(glmData.ehdrste,[numel(r2data) size(glmData.ehdrste,4) size(glmData.ehdrste,5)]); +% if numberContrasts +% volumeRSS = glmData.rss; +% end +% if plotDeconvolution +% volumeDeconv = reshape(deconvData.ehdr,[numel(r2data) size(deconvData.ehdr,4) size(deconvData.ehdr,5)]); +% end +end + +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)); + volumeIndices = [x y s]; + e = getEstimates(glmData,analysisParams,volumeIndices); + buttonString{1} = 'estimate std error'; + + else %this is an ROI + + hWait = uicontrol('parent',fignum,'style','text','unit','normalized',... + 'string','Computing Standard Errors for ROI. Please wait...','position',ehdrSubplotPosition(iPlot,:),'backgroundcolor',get(fignum,'color')); + drawnow; + roiNum = iPlot-1; + % get roi scan coords + roi{roiNum}.scanCoords = getROICoordinates(thisView,roi{roiNum},scanNum); + %get ROI estimates + volumeIndices = sub2ind(size(r2data),roi{roiNum}.scanCoords(1,:),roi{roiNum}.scanCoords(2,:),roi{roiNum}.scanCoords(3,:)); + roiIndices = (r2data(volumeIndices)>r2clip(1)) & (r2data(volumeIndices)1 && iSte ==1 +% disp(titleString{1}); +% disp(e.contrastBetas); +% end + 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 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 iSte==1, hContrastHdr =h; hEhdr = [hEhdr;h];end; + end + + if iSte~=1 + set(hEhdrSte(:,:,iSte),'visible','off'); + end + + end + uicontrol('Parent',fignum,... + 'units','normalized',... + 'Style','popupmenu',... + 'Callback',{@makeVisible,hEhdrSte},... + 'String', [buttonString {'No error bars'}],... + 'Position',stePopupPosition(iPlot,:)); + % % % % display ehdr with out lines if we have a fit + % % % % since we also need to plot fit + % % % if isfield(glmData,'peak') & isfield(glmData.peak,'fit') & ~any(isnan(glmData.peak.amp(x,y,s,:))) + % % % h = plotEhdr(e.time,e.hdr,e.hdrSte,''); + % % % for r = 1:glmData.nhdr + % % % glmData.peak.fit{x,y,s,r}.smoothX = 1:.1:glmData.hdrlen; + % % % fitTime = glmData.tr*(glmData.peak.fit{x,y,s,r}.smoothX-0.5); + % % % plot(fitTime+glmData.tr/2,glmData.peak.fit{x,y,s,r}.smoothFit,getcolor(r,'-')); + % % % end + % % % xaxis(0,e.time(end)+framePeriod/2); + % % % end + % % % % add peaks if they exist to the legend + % % % if isfield(glmData,'peak') + % % % for i = 1:glmData.nhdr + % % % names{i} = sprintf('%s: %s=%0.2f',names{i},glmData.peak.params.method,glmData.peak.amp(x,y,s,i)); + % % % end + % % % end + + % if there is deconvolution data, display that too + %but do not plot the std deviation (it gets too cluttered) + if plotDeconvolution + if numberContrasts + deconvAnalysisParams.contrasts = analysisParams.contrasts; + end + eDeconv = getEstimates(deconvData,deconvAnalysisParams,volumeIndices); + if any(any(eDeconv.hdr)) + eDeconv.hdr = mean(eDeconv.hdr,3); + h=plotEhdr(ehdrAxes,eDeconv.time,eDeconv.hdr); + hDeconv = cat(1,hDeconv,h); + if numberContrasts + if ~isempty(eDeconv.contrastHdr) + eDeconv.contrastHdr = mean(eDeconv.contrastHdr,3); + h = plotEhdr(hdrContrastAxes,eDeconv.time,eDeconv.contrastHdr); + hDeconv = cat(1,hDeconv,h); + else + mrWarnDlg('(glmPlot) Cannot plot contrast for deconvolution analysis'); + end + end + end + else + eDeconv.time=0; + end + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Finalize axes + if ~isempty(e.betas) + %plot baselines of histograms + if plotBetaWeights + 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)))]); + 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); + maxSte = max(e.contrastBetaSte,[],3); + if isnan(maxSte) + maxSte = 0; + end + makeScaleEditButton(fignum,contrastAxes,... + [nanmin(nanmin(e.contrastBetas-maxSte)),nanmax(nanmax(e.contrastBetas+maxSte))]); + if iPlot==1 + ylabel(contrastAxes,{'Contrast' 'Estimates'}); + lhandle = legend(hContrastBeta,contrastNames,'position',legendContrastsPosition); + set(lhandle,'Interpreter','none','box','off'); + end + end + end + set(ehdrAxes,'xLim',[0,max(eDeconv.time(end),e.time(end))+framePeriod/2]); + maxSte = abs(max(e.hdrSte,[],3)); + makeScaleEditButton(fignum,ehdrAxes,... + [nanmin(nanmin((e.hdr-maxSte))),nanmax(nanmax((e.hdr+maxSte)))]); + if iPlot==1 + ylabel(ehdrAxes,{'Scaled HRF','% Signal change'}); + lhandle = legend(hHdr,EVnames,'position',legendEhdrPosition); + set(lhandle,'Interpreter','none','box','off'); + end + if numberContrasts + set(hdrContrastAxes,'xLim',[0,max(eDeconv.time(end),e.time(end))+framePeriod/2]); + maxSte = max(e.contrastHdrSte,[],3); + if isnan(maxSte) + maxSte = 0; + end + makeScaleEditButton(fignum,hdrContrastAxes,... + [nanmin(nanmin(e.contrastHdr-maxSte)),nanmax(nanmax(e.contrastHdr+maxSte))]); + if iPlot==1 + ylabel(hdrContrastAxes,{'Scaled Contrast HRF','% Signal change'}); + lhandle = legend(hContrastHdr,contrastNames,'position',legendHdrContrastPosition); + set(lhandle,'Interpreter','none','box','off'); + end + xlabel(hdrContrastAxes,'Time (sec)'); + else + xlabel(ehdrAxes,'Time (sec)'); + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% plot the time series + %put a button to plot the time series of this roi + if iPlot==1 + thisRoi = [x y s]; + else + thisRoi = roi{roiNum}; + end + uicontrol('Parent',fignum,... + 'units','normalized',... + 'Style','pushbutton',... + 'Callback',{@eventRelatedPlotTSeries, thisView, analysisParams, glmData, thisRoi},... + 'String',['Plot the time series for ' titleString{1}],... + 'Position',tSeriesButtonPosition(iPlot,:)); + + +end + + +if plotDeconvolution && ~isempty(hDeconv) + set(hDeconv,'visible','off','MarkerEdgeColor','none','MarkerFaceColor','none'); + uicontrol('Parent',fignum,... + 'units','normalized',... + 'Style','pushbutton',... + 'Callback',{@makeVisible,hDeconv},... + 'String','Show deconvoluted HDR',... + 'Position',deconvButtonPosition); + uicontrol('Parent',fignum,... + 'units','normalized',... + 'Style','pushbutton',... + 'Callback',{@makeVisible,hEhdr},... + 'String','Hide estimated HDR',... + 'Position',ehdrButtonPosition); +end + +drawnow; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% function to plot the time series for the voxel and rois % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function eventRelatedPlotTSeries(handle,eventData,thisView,analysisParams, d, roi) + +fignum = selectGraphWin(0,'Make new'); +initializeFigure(fignum,d.nhdr); +monitorPositions = getMonitorPositions; +figurePosition = get(fignum,'position'); +[whichMonitor,figurePosition]=getMonitorNumber(figurePosition,monitorPositions); +screenSize = monitorPositions(whichMonitor,:); % find which monitor the figure is displayed in +figurePosition(3) = screenSize(3); +figurePosition(4) = screenSize(3)/5; +set(fignum,'position',figurePosition); +h=uicontrol('parent',fignum,'unit','normalized','style','text',... + 'string','Loading timecourse. Please wait...',... + 'position',[0.1 0.45 .8 .1],'backgroundcolor',get(fignum,'color')); +%this removes the figure toolbar (because the figure toolbar property is set to 'auto' by default) +%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'); + +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))); + titleString = sprintf('Voxel %i,%i,%i Time Series',roi(1),roi(2),roi(3)); + ehdr = shiftdim(d.ehdr(roi(1),roi(2),roi(3),:,:),3); +else + fprintf(1,'\n'); + roi = loadROITSeries(thisView,roi); + actualTSeries = mean(roi.tSeries,1)'; + titleString = ['ROI ' roi.name ' Time Series']; + ehdr = permute(d.ehdr,[4 5 1 2 3]); + ehdr = ehdr(:,:,sub2ind(d.dim(1:3),roi.scanCoords(1,:)',roi.scanCoords(2,:)',roi.scanCoords(3,:)')); + ehdr = nanmean(ehdr,3); +end +%convert to percent signal change the way it's done in getGlmStatistics +actualTSeries = (actualTSeries - mean(actualTSeries))/mean(actualTSeries)*100; +junkFrames = viewGet(thisView, 'junkFrames'); +nFrames = viewGet(thisView,'nFrames'); +actualTSeries = actualTSeries(junkFrames+1:junkFrames+nFrames); + +% if isfield(analysisParams,'scanParams') && isfield(analysisParams.scanParams{thisView.curScan},'acquisitionSubsample')... +% && ~isempty(analysisParams.scanParams{thisView.curScan}.acquisitionSubsample) +% acquisitionSubsample = analysisParams.scanParams{thisView.curScan}.acquisitionSubsample; +% else +% acquisitionSubsample = 1; +% end +% if ~isfield(d,'estimationSupersampling') +% d.estimationSupersampling=1; +% end +% time = ((0:length(actualTSeries)-1)+(acquisitionSubsample-.5)/d.estimationSupersampling)*d.tr; +if ~isfield(d,'acquisitionDelay') + d.acquisitionDelay=d.tr/2; +end +time = rem(d.acquisitionDelay,d.tr)+d.tr*(0:length(actualTSeries)-1); + +% frequencies = 1/length(actualTSeries)/d.tr:1/length(actualTSeries)/d.tr:1/d.tr/2; +% frequencies = (1/length(actualTSeries):1/length(actualTSeries):1/2)./d.tr; +frequencies = (1:1:length(actualTSeries)/2)./length(actualTSeries)./d.tr; + + +delete(h); +set(fignum,'name',titleString) +tSeriesAxes = axes('parent',fignum,'outerposition',getSubplotPosition(1:3,1,[1 1 4],[1 1],0,0)); +hold on +fftAxes = axes('parent',fignum,'outerposition',getSubplotPosition(3,2,[1 1 4],[1 1],0,0)); +hold on +%hold(tSeriesAxes); + + +%Plot the stimulus times +set(tSeriesAxes,'Ylim',[min(actualTSeries);max(actualTSeries)]) +if ~isfield(d,'designSupersampling') + d.designSupersampling=1; +end + +colorOrder = get(tSeriesAxes,'colorOrder'); +if isfield(d,'EVmatrix') && isfield(d,'EVnames') + stimOnsets = d.EVmatrix; + stimDurations = []; + legendString = d.EVnames; +elseif isfield(d,'stimDurations') && isfield(d, 'stimvol') + stimOnsets = d.stimvol; + stimDurations = d.stimDurations; + legendString = d.stimNames; +elseif isfield(d, 'stimvol') + stimOnsets = d.stimvol; + stimDurations = []; + legendString = d.stimNames; +end +if isfield(d,'runTransitions') + runTransitions = d.runTransitions; +else + runTransitions = []; +end + +[h,hTransitions] = plotStims(stimOnsets, stimDurations, d.tr/d.designSupersampling, colorOrder, tSeriesAxes, runTransitions); +legendString = legendString(h>0); +h = h(h>0); +nEVs = length(h); + +%and the time-series +%plot a baseline +plot(tSeriesAxes,time,zeros(size(time)),'--','linewidth',1,'color',[.5 .5 .5]); +h(end+1) = plot(tSeriesAxes,time,actualTSeries,'k.-'); +hActualTSeries = h(end); + +nComponents = size(d.scm,2); +if nComponents==numel(ehdr) + %compute model time series + extendedEhdr = reshape(ehdr',numel(ehdr),1); + if fieldIsNotDefined(d,'emptyEVcomponents') + d.emptyEVcomponents = []; + else + d.scm(:,d.emptyEVcomponents)=[]; + extendedEhdr(d.emptyEVcomponents)=[]; + end + modelTSeries = d.scm*extendedEhdr; + h(end+1) = plot(tSeriesAxes,time,modelTSeries,'--r'); + hModelTSeries = h(end); +end +legendString{end+1} = 'Actual Time Series'; +legendString{end+1} = 'Model Time Series'; +if ~isempty(hTransitions) + h = [h hTransitions]; + legendString{end+1} = 'Run transitions'; +end +ylabel(tSeriesAxes,'Percent Signal Change'); +xlim(tSeriesAxes,[0 ceil(time(end)+1)]); + +%FFT +actualFFT = abs(fft(actualTSeries)); +actualFFT = actualFFT(1:floor(end/2)); +hActualFFT = plot(fftAxes,frequencies,actualFFT,'k.-'); +if nComponents==numel(ehdr) + modelFFT = abs(fft(modelTSeries)); + modelFFT = modelFFT(1:floor(end/2)); + hModelFFT = plot(fftAxes,frequencies,modelFFT,'--r'); +end +xlim(fftAxes,[0 1/d.tr/2]); +xlabel(fftAxes,'Frequencies (Hz)'); +ylabel(fftAxes,'FFT'); + +%legend +legendPosition = getSubplotPosition(2,2,[1 1 4],[1 1],0,.2); +%panel with position identical to the legend axes so that we can reposition the EV checkboxes +hPanel = uipanel(fignum,'position',legendPosition,'backgroundcolor',get(fignum,'color'),'bordertype','none'); +hSubtractFromTseries = uicontrol(fignum,'unit','normalized','style','check','position',[.1 .47 .9 .03],... + 'string','Subtract unchecked EVs from actual timeseries','value',1); +set(hSubtractFromTseries,'callback',{@plotModelTSeries,hActualTSeries,actualTSeries,hModelTSeries,d.scm,ehdr,d.emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT}); + +lhandle = legend(h,legendString,'position',legendPosition); +set(lhandle,'Interpreter','none','box','off'); +set(hPanel,'resizeFcn',{@resizePanel,lhandle}); + +for iEV = 1:nEVs + thisPosition = get(findobj(lhandle,'string',legendString{iEV}),'position')'; + uicontrol(hPanel,'style','check','unit','normalized','position',[thisPosition(1) thisPosition(2) .1 .1],... + 'value',1,'callback',{@plotModelTSeries,hActualTSeries,actualTSeries,hModelTSeries,d.scm,ehdr,d.emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT}); +end + +disppercent(inf); +%delete(handle); %don't delete the button to plot the time-series + + +function resizePanel(handle,eventData,hLegend) + +set(handle,'position',get(hLegend,'position')); + +function plotModelTSeries(handle,eventData,hActualTSeries,actualTSeries,hModelTSeries,scm,ehdr,emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT) +%get which EV are checked (note that children of the uipanel are ordered from bottom to top, so we flipud +whichEVs = get(get(hPanel,'children'),'value'); +if ~isnumeric(whichEVs) + whichEVs = flipud(cell2mat(whichEVs)); +end +whichEVs = logical(whichEVs); +evHdr = ehdr; +evHdr(~whichEVs,:) = 0; +evHdr = reshape(evHdr',[],1); +evHdr(emptyEVcomponents)=[]; +if size(scm,2)==numel(evHdr) + modelTSeries = scm*evHdr; + modelFFT = abs(fft(modelTSeries)); + modelFFT = modelFFT(1:floor(end/2)); + set(hModelTSeries,'Ydata',modelTSeries); + set(hModelFFT,'Ydata',modelFFT) + if get(hSubtractFromTseries,'value') + ehdr(whichEVs,:) = 0; + extendedHdr = reshape(ehdr',[],1); + extendedHdr(emptyEVcomponents)=[]; + actualTSeries = actualTSeries - scm*extendedHdr; + end + set(hActualTSeries,'Ydata',actualTSeries); + actualFFT = abs(fft(actualTSeries)); + actualFFT = actualFFT(1:floor(end/2)); + set(hActualFFT,'Ydata',actualFFT); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%% function to initialize figure%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function initializeFigure(fignum,numberColors) + +lineWidth = 2; +fontSize = 15; + +%set default values for figure aspect +set(fignum,'DefaultLineLineWidth',lineWidth); +set(fignum,'DefaultAxesFontSize',fontSize); +%set the colors +colors = color2RGB; +colors = colors([7 5 6 8 4 3 2 1]); %remove white and black and re-order +for i_color = 1:length(colors) + colorOrder(i_color,:) = color2RGB(colors{i_color}); +end +if numberColors>size(colorOrder,1) + colorOrder(end+1:numberColors,:) = randomColors(numberColors-size(colorOrder,1)); +end +colorOrder = colorOrder(1:numberColors,:); + + +set(fignum,'DefaultAxesColorOrder',colorOrder); +%for bars, need to set the colormap +set(fignum,'colormap',colorOrder); + +% turn off menu/title etc. +set(fignum,'NumberTitle','off'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%% function to plot ehdr %%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [h,hSte] = plotEhdr(hAxes,time,ehdr,ehdrSte,lineSymbol,drawSymbols, steOnly) + +colorOrder = get(hAxes,'colorOrder'); +% whether to plot the line inbetween points or not +if ieNotDefined('lineSymbol'),lineSymbol = '-';,end +if ieNotDefined('drawSymbols'),drawSymbols = 1;,end +if ieNotDefined('steOnly'),steOnly = 0;,end + +% and display ehdr +if steOnly + h=[]; +else + h=plot(hAxes,time,ehdr,lineSymbol); + if drawSymbols + for iEv = 1:size(ehdr,2) + set(h(iEv),'Marker',getsymbol(iEv),'MarkerSize',8,'MarkerEdgeColor','k','MarkerFaceColor',colorOrder(iEv,:)); + end + end +end + +if ~ieNotDefined('ehdrSte') + hold on + %if ~ishold(hAxes),hold(hAxes);end; + hSte=errorbar(hAxes,repmat(time',1,size(ehdr,2)),ehdr,ehdrSte,ehdrSte,'lineStyle','none')'; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%% function to plot contrasts %%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [h hSte]= plotBetas(hAxes,econt,econtste, steOnly) + +colorOrder = get(hAxes,'colorOrder'); +if ieNotDefined('steOnly'),steOnly = 0;end + +% % display econt +% if size(econt,1)==1 +% econt = econt'; +% econtste = econtste'; +% end +% +if size(econt,2)==1 + set(hAxes,'nextPlot','add'); + h=zeros(size(econt,1),1); + 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')); + set(hAxes,'xTickLabel',{}) + set(hAxes,'xTick',[]) +else + h = bar(hAxes,econt','grouped','edgecolor','none'); + for iBar = 1:size(econt,1) + set(h(iBar),'faceColor',colorOrder(iBar,:)); + end + set(hAxes,'xtick',1:size(econt,2)) + xlabel('EV components'); +end +if steOnly + set(h,'visible','off'); +end + +if ~ieNotDefined('econtste') + + + %hSte = errorbar(hAxes,(1:size(econt,1))', econt, econtste, 'k','lineStyle','none'); + %hSte = errorbar(hAxes, [1 2; 1 2; 1 2],econt, econtste, 'k','lineStyle','none'); + +ctrs = 1:length(econt); +data = econt; +%figure +hBar = bar(ctrs, data); +ctr = []; +ydt = []; +if length(hBar) > 1 + for k1 = 1:size(data,2) + ctr(k1,:) = bsxfun(@plus, hBar(1).XData, [hBar(k1).XOffset]'); + ydt(k1,:) = hBar(k1).YData; + end +else + k1 = 1; + ctr(k1,:) = bsxfun(@plus, hBar(1).XData, [hBar(k1).XOffset]'); + ydt(k1,:) = hBar(k1).YData; + +end +%hold on +hSte = errorbar( ctr', ydt', econtste, '.r'); +%hold off +hSte = hSte(1); + + +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%% function to make scale edit boxes %%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function makeScaleEditButton(fignum,axisHandle,minMaxData) + +pos = get(axisHandle,'position'); +if ieNotDefined('minMaxData') + minMaxData = get(axisHandle,'YLim'); +else + minMaxData(1) = minMaxData(1)-.02*diff(minMaxData); + minMaxData(2) = minMaxData(2)+.02*diff(minMaxData); +end +minMaxData(1) = min(minMaxData(1),0); +set(axisHandle,'YLim',minMaxData); +uicontrol(fignum,'style','edit','units','normalized',... + 'position',[pos(1)+pos(3) pos(2)+.6*pos(4) .03 .03],... + 'string',num2str(minMaxData(2)),'callback',{@changeScale,axisHandle,'max'}); +uicontrol(fignum,'style','edit','units','normalized',... + 'position',[pos(1)+pos(3) pos(2)+.4*pos(4) .03 .03 ],... + 'string',num2str(minMaxData(1)),'callback',{@changeScale,axisHandle,'min'}); +set(axisHandle,'YLimMode','manual'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%% function to make lineseries visible %%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function makeVisible(handle,eventdata,hAxes) + +switch(get(handle,'style')) + case 'pushbutton' + string = get(handle,'String'); + if strcmp(get(hAxes,'visible'),'off') + set(hAxes,'visible','on'); + set(handle,'String',['Hide' string(5:end)]); + set(handle,'userdata','visible'); + else + set(hAxes,'visible','off'); + set(handle,'String',['Show' string(5:end)]); + set(handle,'userdata','invisible'); + end + + case 'popupmenu' + set(hAxes,'visible','off') + handleNum = get(handle,'value'); + if handleNum ~= length(get(handle,'string')); + set(hAxes(:,:,handleNum),'visible','on'); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%% changeScale %%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function changeScale(handle,eventData,axisHandle,whichScale) + +axes(axisHandle); +scale = axis; +switch(whichScale) + case 'min' + scale(3) = str2num(get(handle,'String')); + case 'max' + scale(4) = str2num(get(handle,'String')); +end +axis(scale); \ No newline at end of file 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..5a48c1ece --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m @@ -16,6 +16,12 @@ sampleDelay=sampleDuration/2; end +if sampleDuration == 1000 + sampleDuration = sampleDuration ./ 1000; + sampleDelay = sampleDelay ./ 1000; +end% [ma] magic fix + + if ieNotDefined('params') params = struct; end diff --git a/mrLoadRet/Plugin/pRF/pRF.m b/mrLoadRet/Plugin/pRF/pRF.m old mode 100644 new mode 100755 index 974046536..c8e09c320 --- a/mrLoadRet/Plugin/pRF/pRF.m +++ b/mrLoadRet/Plugin/pRF/pRF.m @@ -76,8 +76,8 @@ r2.colormap = hot(312); r2.colormap = r2.colormap(end-255:end,:); r2.alpha = 1; -r2.colormapType = 'setRangeToMax'; -r2.interrogator = 'pRFPlot'; +r2.colormapType = 'normal'; +r2.interrogator = 'myOverlayStats'; r2.mergeFunction = 'pRFMergeParams'; % create the parameters for the polarAngle overlay @@ -178,6 +178,12 @@ thisPolarAngle = nan(1,n); thisEccentricity = nan(1,n); thisRfHalfWidth = nan(1,n); + + %thisr2 = nan(1,n); + thisRawParamsCoords = nan(3,n); + thisResid = nan(numel(concatInfo.whichScan),n); + thistSeries = nan(numel(concatInfo.whichScan),n); + thismodelResponse = nan(numel(concatInfo.whichScan),n); % get some info about the scan to pass in (which prevents % pRFFit from calling viewGet - which is problematic for distributed computing @@ -200,7 +206,8 @@ % 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 = 240; + %blockSize = 240; + 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. @@ -223,6 +230,11 @@ % pass it into pRFFit and pRFFit will load the tSeries itself loadROI = loadROITSeries(v,loadROI,scanNum,params.groupName,'keepNAN',true); % 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); @@ -233,30 +245,149 @@ % display time update dispHeader(sprintf('(pRF) %0.1f%% done in %s (Estimated time remaining: %s)',100*blockStart/n,mlrDispElapsedTime(toc),mlrDispElapsedTime((toc*n/blockStart) - toc))); end + + 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; + %myVar = thehrfs.hrf_struct.voxHRFs; % smoothed version of hrfprf? + + end + + if params.pRFFit.HRFpRF == 1 + %keyboard + parfor 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 = pRFFit(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; + thisPolarAngle(ii) = fit.polarAngle; + thisEccentricity(ii) = fit.eccentricity; + thisRfHalfWidth(ii) = fit.std; + % keep parameters + rawParams(:,ii) = fit.params(:); + r(ii,:) = fit.r; + thisr2(ii) = fit.r2; + thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; + 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 + + + + + % regular prf fitting + parfor ii = blockStart:blockEnd + + fit = pRFFit(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); + + + if ~isempty(fit) + + thisr2(ii) = fit.r2; + thisPolarAngle(ii) = fit.polarAngle; + thisEccentricity(ii) = fit.eccentricity; + thisRfHalfWidth(ii) = fit.std; + % keep parameters + rawParams(:,ii) = fit.params(:); + r(ii,:) = fit.r; + thisr2(ii) = fit.r2; + thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; + thisResid(:,ii) = fit.residual; + thistSeries(:,ii) = fit.tSeries; + thismodelResponse(:,ii) = fit.modelResponse; + %keyboard + +% tempVar = zeros(length(overlayNames),1); +% +% for iOverlay = 1:numel(overlayNames) +% +% test = strcmpi(fieldnames(fit), overlayNames(iOverlay) ); +% +% 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)]; + + end + end + + end % now loop over each voxel - parfor i = blockStart:blockEnd - fit = pRFFit(v,scanNum,x(i),y(i),z(i),'stim',stim,'concatInfo',concatInfo,'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',i,'dispN',n,'tSeries',loadROI.tSeries(i-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,'paramsInfo',paramsInfo); - if ~isempty(fit) - % keep data, note that we are keeping temporarily in - % a vector here so that parfor won't complain - % then afterwords we put it into the actual overlay struct - thisr2(i) = fit.r2; - thisPolarAngle(i) = fit.polarAngle; - thisEccentricity(i) = fit.eccentricity; - thisRfHalfWidth(i) = fit.std; - % keep parameters - rawParams(:,i) = fit.params(:); - r(i,:) = fit.r; - end - end +% parfor i = blockStart:blockEnd +% fit = pRFFit(v,scanNum,x(i),y(i),z(i),'stim',stim,'concatInfo',concatInfo,'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',i,'dispN',n,'tSeries',loadROI.tSeries(i-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,'paramsInfo',paramsInfo); +% if ~isempty(fit) +% % keep data, note that we are keeping temporarily in +% % a vector here so that parfor won't complain +% % then afterwords we put it into the actual overlay struct +% thisr2(i) = fit.r2; +% thisPolarAngle(i) = fit.polarAngle; +% thisEccentricity(i) = fit.eccentricity; +% thisRfHalfWidth(i) = fit.std; +% % keep parameters +% rawParams(:,i) = fit.params(:); +% r(i,:) = fit.r; +% end +% end % set overlays - for i = 1:n - r2.data{scanNum}(x(i),y(i),z(i)) = thisr2(i); - polarAngle.data{scanNum}(x(i),y(i),z(i)) = thisPolarAngle(i); - eccentricity.data{scanNum}(x(i),y(i),z(i)) = thisEccentricity(i); - rfHalfWidth.data{scanNum}(x(i),y(i),z(i)) = thisRfHalfWidth(i); + for ii = 1:n + r2.data{scanNum}(x(ii),y(ii),z(ii)) = thisr2(ii); + polarAngle.data{scanNum}(x(ii),y(ii),z(ii)) = thisPolarAngle(ii); + eccentricity.data{scanNum}(x(ii),y(ii),z(ii)) = thisEccentricity(ii); + rfHalfWidth.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidth(ii); end end % display time update @@ -266,6 +397,11 @@ pRFAnal.d{scanNum}.params = rawParams; pRFAnal.d{scanNum}.r = r; + pRFAnal.d{scanNum}.r2 = thisr2; + pRFAnal.d{scanNum}.rawCoords = thisRawParamsCoords; + pRFAnal.d{scanNum}.myresid = thisResid; + pRFAnal.d{scanNum}.mytSeries = thistSeries; + pRFAnal.d{scanNum}.mymodelResp = thismodelResponse; iScan = find(params.scanNum == scanNum); thisParams.scanNum = params.scanNum(iScan); @@ -468,7 +604,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % save the stim image stimFilename = fullfile(exportDir,'stim.nii.gz'); -if mlrIsFile(stimFilename) +if isfile(stimFilename) disp(sprintf('(pRF) Removing already existing %s',getLastDir(stimFilename))); system(sprintf('rm -f %s',stimFilename)); end @@ -481,7 +617,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % and save it maskFilename = fullfile(exportDir,'mask.nii.gz'); -if mlrIsFile(maskFilename) +if isfile(maskFilename) disp(sprintf('(pRF) Removing already existing %s',getLastDir(maskFilename))); system(sprintf('rm -f %s',maskFilename)); end @@ -493,7 +629,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % save data boldFilename = fullfile(exportDir,'bold.nii.gz'); -if mlrIsFile(boldFilename) +if isfile(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..2008dd418 --- a/mrLoadRet/Plugin/pRF/pRFFit.m +++ b/mrLoadRet/Plugin/pRF/pRFFit.m @@ -10,7 +10,7 @@ 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] = parseArgs(varargin); +[v, scanNum, x, y, z, fitParams, tSeries,hrfprf] = parseArgs(varargin); if isempty(v),return,end % get concat info @@ -18,6 +18,13 @@ fitParams.concatInfo = viewGet(v,'concatInfo',scanNum); end +% adding own params... +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) @@ -97,15 +104,43 @@ fitParams = setFitParams(fitParams); % just return model response for already calcualted params +% if fitParams.getModelResponse +% % get model fit +% [residual fit.modelResponse fit.rfModel] = getModelResidual(fitParams.params,tSeries,fitParams); +% % get the canonical +% fit.p = getFitParams(fitParams.params,fitParams); +% fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); +% % return tSeries +% fit.tSeries = tSeries; +% return; +% end + if fitParams.getModelResponse - % get model fit - [residual fit.modelResponse fit.rfModel] = getModelResidual(fitParams.params,tSeries,fitParams); - % get the canonical - fit.p = getFitParams(fitParams.params,fitParams); - fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); - % return tSeries - fit.tSeries = tSeries; - return; + + if ~ieNotDefined('hrfprf') + %params = 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 + [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 @@ -177,7 +212,7 @@ % 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); + [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); @@ -188,7 +223,7 @@ fit.params = fitParams.initParams; fit.r2 = maxr^2; fit.r = maxr; - [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); + [fit.polarAngle, fit.eccentricity] = cart2pol(fit.x,fit.y); % 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)); @@ -197,23 +232,54 @@ end end -% 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); +if ~ieNotDefined('hrfprf') + %params = 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); + return + end + % possibly we need to send to getCanonicalHRF to make sure the + % offsets/scaling line up... + else - disp(sprintf('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); - return + + % 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); + return + end + end +% 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); +% return +% 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); +[residual modelResponse rfModel fit.r] = getModelResidual(params,tSeries,fitParams,[],hrfprfcheck); 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') @@ -223,11 +289,17 @@ % compute polar coordinates [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); +fit.residual = residual; +fit.tSeries = tSeries; +fit.modelResponse = modelResponse; + % 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)); end +end + %%%%%%%%%%%%%%%%%%%%%% % setFitParams % %%%%%%%%%%%%%%%%%%%%%% @@ -351,10 +423,11 @@ end end +end %%%%%%%%%%%%%%%%%%%%%%%%%% %% getModelResidual %% %%%%%%%%%%%%%%%%%%%%%%%%%% -function [residual modelResponse rfModel r] = getModelResidual(params,tSeries,fitParams,justGetModel) +function [residual, modelResponse, rfModel, r, hrf] = getModelResidual(params,tSeries,fitParams,justGetModel, hrfprfcheck) residual = []; if nargin < 4, justGetModel = 0;end @@ -366,17 +439,32 @@ % compute an RF rfModel = getRFModel(p,fitParams); +if ieNotDefined('hrfprfcheck') + hrfprfcheck = 0; +end + % init model response modelResponse = [];residual = []; % create the model for each concat for i = 1:fitParams.concatInfo.n % get model response - nFrames = fitParams.concatInfo.runTransition(i,2); + nFrames = (fitParams.concatInfo.runTransition(i,2)-fitParams.concatInfo.runTransition(i,2)+1); thisModelResponse = convolveModelWithStimulus(rfModel,fitParams.stim{i},nFrames); + + if isfield(fitParams, 'hrfprf') + %keyboard + 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 % get a model hrf - hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); + %hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); % and convolve in time. thisModelResponse = convolveModelResponseWithHRF(thisModelResponse,hrf); @@ -392,8 +480,10 @@ thisModelResponse = thisModelResponse - mean(thisModelResponse); end - if ~justGetModel + if isempty(justGetModel), justGetModel = 0; end + %if ~justGetModel % compute correlation of this portion of the model response with time series + if justGetModel == 0 thisTSeries = tSeries(fitParams.concatInfo.runTransition(i,1):fitParams.concatInfo.runTransition(i,2)); thisTSeries = thisTSeries - mean(thisTSeries); @@ -420,12 +510,53 @@ residual = [residual;thisResidual(:)]; end + % return model only if justGetModel,return,end % scale the whole time series -if ~fitParams.betaEachScan - [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); +% if ~fitParams.betaEachScan +% [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); +% end + +if ~isfield(fitParams, 'hrfprf') + if ~fitParams.betaEachScan + [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); + end +end +% +% 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 +% +if fitParams.getModelResponse ~= 1 + %if hrfprfcheck == 1 + if isfield(fitParams, 'hrfprf') + if ~any(isnan(modelResponse)) + %disp('bloop') + % 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 @@ -440,7 +571,7 @@ % disp(sprintf('(pRFFit:getModelResidual) r: %f',residual)); end - +end %%%%%%%%%%%%%%%%%%%%%% % dispModelFit % %%%%%%%%%%%%%%%%%%%%%% @@ -476,7 +607,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) canonical = getCanonicalHRF(p.canonical,fitParams.framePeriod); plot(canonical.time,canonical.hrf,'k-') if exist('myaxis') == 2,myaxis;end - +end %%%%%%%%%%%%%%%%%%%%%%%% % scaleAndOffset % %%%%%%%%%%%%%%%%%%%%%%%% @@ -494,7 +625,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) else residual = tSeries; end - +end %%%%%%%%%%%%%%%%%%%%%% %% getFitParams %% %%%%%%%%%%%%%%%%%%%%%% @@ -542,7 +673,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) otherwise disp(sprintf('(pRFFit) Unknown rfType %s',rfType)); end - +end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% convolveModelWithStimulus %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -559,7 +690,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % and take the sum modelResponse(frameNum) = sum(sum(rfModel.*stim.im(:,:,frameNum))); end - +end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% convolveModelResponseWithHRF %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -568,7 +699,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) n = length(modelTimecourse); modelTimecourse = conv(modelTimecourse,hrf.hrf); modelTimecourse = modelTimecourse(1:n); - +end %%%%%%%%%%%%%%%%%%%%% %% getGammaHRF %% %%%%%%%%%%%%%%%%%%%%% @@ -579,6 +710,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) if p.diffOfGamma fun = fun - thisGamma(time,p.amplitudeRatio,p.timelag2,p.offset2,p.tau2,p.exponent2)/100; end +end %%%%%%%%%%%%%%%%%%% %% thisGamma %% @@ -598,6 +730,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) gammafun = (gammafun-min(gammafun)) ./ (max(gammafun)-min(gammafun)); end gammafun = (amplitude*gammafun+offset); +end %%%%%%%%%%%%%%%%%%%%%%%%% @@ -610,7 +743,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % normalize to amplitude of 1 hrf.hrf = hrf.hrf / max(hrf.hrf); - +end %%%%%%%%%%%%%%%%%%%% %% getRFModel %% %%%%%%%%%%%%%%%%%%%% @@ -624,6 +757,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) else disp(sprintf('(pRFFit:getRFModel) Unknown rfType: %s',fitParams.rfType)); end +end %%%%%%%%%%%%%%%%%%%%%%%% @@ -633,13 +767,13 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % compute rf rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.std^2))+((fitParams.stimY-params.y).^2)/(2*(params.std^2)))); - +end %%%%%%%%%%%%%%%%%%% % parseArgs % %%%%%%%%%%%%%%%%%%% -function [v scanNum x y s fitParams tSeries] = parseArgs(args); +function [v, scanNum, x, y, s, fitParams, tSeries, hrfprf] = parseArgs(args) -v = [];scanNum=[];x=[];y=[];s=[];fitParams=[];tSeries = []; +v = [];scanNum=[];x=[];y=[];s=[];fitParams=[];tSeries = []; hrfprf=[]; % check for calling convention from interrogator if (length(args) >= 7) && isnumeric(args{6}) @@ -713,7 +847,12 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) 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=[]'}); + 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; @@ -809,7 +948,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) fitParams.prefit.y = prefity(:); fitParams.prefit.rfHalfWidth = prefitrfHalfWidth(:); end - +end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % checkStimForAverages % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -913,6 +1052,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) end end +end %%%%%%%%%%%%%%%%% % getStim % %%%%%%%%%%%%%%%%% @@ -946,7 +1086,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) disp(sprintf('(pRFFit) Using precomputed stim image')); stim = gpRFFitStimImage.stim; end - +end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % applyConcatFiltering % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -983,7 +1123,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % make back into the right dimensions tSeries = tSeries(:)'; - +end %%%%%%%%%%%%% %% r2d %% %%%%%%%%%%%%% @@ -1002,3 +1142,4 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) while (sum(degrees<-360)) degrees = degrees + (degrees<-360)*360; end +end \ No newline at end of file diff --git a/mrLoadRet/Plugin/pRF/pRFGUI.m b/mrLoadRet/Plugin/pRF/pRFGUI.m index fc4cfef3d..e6b9d340a 100644 --- a/mrLoadRet/Plugin/pRF/pRFGUI.m +++ b/mrLoadRet/Plugin/pRF/pRFGUI.m @@ -149,6 +149,9 @@ 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} = {'saveForExport',0,'type=checkbox','Creates files for export to do the pRF on systems like BrainLife. This will save a stim.nii file containing the stimulus images, a bold.nii file with the time series a mask.nii file that contains the voxel mask over which to the analysis and a pRFparams.mat file which contains various parameters'}; +paramsInfo{end+1} = {'HRFpRF',false,'type=checkbox','Set to true if you want to load in pre-computed HRFs using prfhrfRefit'}; + + % if we are continuing, then just add on any parameters that % are needed (i.e. ones that are not included in the old % analysis) diff --git a/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m new file mode 100755 index 000000000..63da73b7c --- /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 ~isfile(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..b3b4cf039 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m @@ -0,0 +1,632 @@ +% 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(5); + + +% create the paramteres for the rfHalfWidth overlay +% deal with the sigma. +rfHalfWidth = r2; +rfHalfWidth.name = 'rfHalfWidth'; +rfHalfWidth.range = [0 5]; +rfHalfWidth.clip = [0 5]; +rfHalfWidth.colormapType = 'normal'; +rfHalfWidth.colormap = pink(256); + +% % 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); + rfHalfWidth.data{scanNum} = nan(scanDims); + + +% 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); + + thisRfHalfWidth = nan(1,n); + 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; + thisRfHalfWidth(ii) = fit.rfHalfWidth; + + + % keep parameters + rawParams(:,ii) = fit.params(:); + r(ii,:) = fit.r; + %thisr2(ii) = fit.r2; + thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; + thisResid(:,ii) = fit.residual; + 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); + rfHalfWidth.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidth(ii); + +% 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; + pRFAnal.d{scanNum}.myresid = thisResid; + 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; + rfHalfWidth.params{scanNum} = thisParams; + + % 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; +pRFAnal.overlays = [r2 prefDigit prefPD rfHalfWidth ]; +%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..823070a12 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -0,0 +1,2451 @@ +% 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; + disppercent(-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 + % 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 + disppercent(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'} + + + % 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 + [~,index] = max(max(rfModel)); % max digit amp of Gaussian + % 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; + + %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 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 + + % 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) ); + %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'} + + + + [~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian + fit.prefPD = index; + thisX = linspace(1,size(rfModel,2),100); + 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 + 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 + 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-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 + + % 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 + + +%%%%%%%%%%%%%%%%%%%%%% +%% 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-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 % 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-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 + % 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 gernerate the rfModel +switch fitParams.rfType + case {'gaussian','gaussian-hdr','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 +% 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,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)']; + 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) + + + otherwise + rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.std^2))+((fitParams.stimY-params.y).^2)/(2*(params.std^2)))); +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-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..4ce11ebfe --- /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-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/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m old mode 100644 new mode 100755 index 6e1d4b87f..3c2c5ff1f --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -49,7 +49,7 @@ hemiNames = {'left', 'right'}; outDir = fullfile(pwd,'surfRelax'); if ~exist('volumeCropSize') - volumeCropSize = [176 256 256]; + volumeCropSize = [256 256 256]; end defaultPixelSize = [1 1 1]; if ~exist('pixelSize') @@ -126,7 +126,7 @@ end system(commandString); else - if mlrIsFile(outFile) + if isfile(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); diff --git a/mrUtilities/ImageProcessing/convolve.mexmaci64 b/mrUtilities/ImageProcessing/convolve.mexmaci64 new file mode 100755 index 0000000000000000000000000000000000000000..6d9d9f3caf150fb7488d903c2cf21d5cf96c04bd GIT binary patch literal 42812 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�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 literal 0 HcmV?d00001 diff --git a/mrUtilities/ImageProcessing/corrDn.mexmaci64 b/mrUtilities/ImageProcessing/corrDn.mexmaci64 index 24de84c52651ba8aead05b36f16f477348d400a5..b332253e460efa38475fc1b6c197bb0b2a68f4f1 100755 GIT binary patch literal 38708 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∋hdD^KWTTA4Uz)H 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 u3j0*0fo&oqx)(Z>P{n@-vRmRX8v!f=$bV3wsU zgt&%aV0Rzb-}qr4jL9-G_5r_)DvWk!bA`=K_m?bjdCZ~mwne6BcTV5KZDu#Q_nhDF z{La1i-1~YPuN-!*{^Iuhy>vo210h6!PN6rOhE`W_rXkN3+eijrXy-8wZDi<;a$C@< zfqc!7;Lu#TZBQpGbaIeA&Xupatg|^fi`c+yLLNKK*=;xV=!cvZzR9jY&U?5AXl~6A z#C$5J$6#bNd?{yOukbrKft}?yZ8>@@r0dXly)NmX^baA3nvbPn6WBRElS^Zl_y*Qv zaAxH28qcw_hF!Tk0U&|9joS&q;$L+TGRUmP>3Nb%|_voCFiv> zGv2Ztkx92@v2WF#4WLUconeMEHIt%cObE+M4~R~{xJqjIG`oR)mcBO20t9a1et>W$ z_Imn;+>JouY*D**Nt<*WALWE`ZnRG9=2Ra*3^N^P}@?3=0aEx006K#K)GK!NC3zt^2dzXOM^BdSD28{2L! z%6*3;q*bDue`%$K33}orK(&&JS<~#exybzNGQ7gxHy4FB>f+xuaW+C1-F!`h0*Yzz zB(0x;{UlV{_%p2Io(67;*3Sc{0QVoeP=bL1J+KXJq$(=+za%Jy0IDOOfG;X*Q~nhAIf|%>;wf+!KA!4GjMhh`-X&9) z6-c{s9Z2%&(qYNlGyz+j$K!XMHkb=gvxfF-=xGi4H3a8_$`cyeuA#9M`tR&> zYX~VPgM-h=k*l%vw>X}}(g<;;H%%l9hT*$U7QC4n-%gG1rp8k+)(kup03#hp76jO7 z>rU`Tf3*I}?Zy|b6xPGuslP|)!yyw3j5#Tf@O3=lMv#QZa@uX;9E9iK=byFyV?8OZ z+f%wdquYxlPApuNQ(Bu{k+gQP=Tx!3!=zq=|J ztnEB{uscM6%h9;HV4ywJiiF*^m-RTZ!zE5cg*Hg(-tQ09B62RHx*$NkaX2Tu?EJp2 zc3&uPteq6M2HN~Ve@9m!=x=%Mg}{scU?=ih4WtK)orM!7Bbf=~ESymo{Cv2E<^KVo z2<4E*#|i;<5H#!6;zI`zb*YB$*}rk6hJn&r(*xN50LD)U#70f6U5k;h#~ds7;G2#Y jf-w_x_znpCl@U9^n(x<(;D1VHK1V56!iF7A%q8S+$^*}; literal 8648 zcmeHNZERat89rW8mWGwpy!V{z z#BM(Rg5)Ee^L{_)J?}Z!vG2(%SKq&OBV(RA##jUT&FBRvCP1m(g6_M4F;(pk4E8&! z%E4PHG9d$ktwaBeFsrJqkJ*d9)p+^!+;)r-K_j`IvwW;)2bYSfYT4O{RaRBw{d@yA zZ5AFRd&=HetH4{#kMPt{J(_*LUQF7S2=43bfn#dckOQ6p_FF1Q+RUf^vM2qcT8 zFJdm?R5fbZW+FWzbk%tO5qN(Pi*#idq9Ru)RW+O5pGe2lL^^Iba^?O$A@RuF0)XUd z3}JEzos%7yTUC3Mer1WpJs&GjIw!PlckQa0PO8>;Dqb7N^%+D|%n zZd28ywi=#S0Qp1!$xHDlKk9B%RU`7_sG3M=BRZ9?dc8XY9z9FcNM5UMLHMa^sQ3H# z26}qkXH2GOG=axtM`F`re<>%yI)>XBJJ>;-%%9^1Q1@&(YRIEt9u<1ZiR{0FOt}tj zWh?{wS@hlL)Ck}0{05-MQh9zdpV0xfi~2@N3Y? z=!DNc*XVTOo!ZwO_q2j{mrgE(O?eR(uP1qXSbN{NW$UT-P$FV#=6L%qQ%mXl4f7{f zyK;BPA9uy}J-TTn@JO~RWN`2XEAW2fAg_Ygt(g%cy*wnh0fF+Y#Qg#^tLE8lL=9~P zSTZ4=*I)M}O0@>AsrTvTzC={FR)SyjaAsL9|26CUgN&YDsnpyi@~^DRnz;A% z-TsV>>>ZGWfD4de#HGdSDUAP{Sg$jGA??@9scbi=*yUc zo~NTSf8MtVnV$bz`~R)LKrr`rWv{ZQFEEjp4tL?BF*JF=+Z+8$c&hoW?x{_z*dw)G z3P#_Set(MCIU3$5O06f0Qd4+7JhkbqV01PVz0{L?ar;Ray)7s4!9vW2X~BH;1Q=<{ zL8?+wk-W_(P4IP*V zH-xysH_=~*%UnDl`94L}hCfCYKB$IYBj!^u*HHc7RQ=haYgBl^#v zv@_oN8H__GU`)=C@dKKz0OkU`7HGl`@0r?kvA7Ga7rE z^n?Ef_b<>(pqD|fVEu2z{nD04oD(l?q0MM&ekE9XF8HmNrHRegN~QaiA1Uese-iuc zo-buU>F*03y5O6bl^%H)(ZSqHO76{IF8j=(t91|G6r8%N5vQckiqZxrzg_4gI+4$k z;Z`y<3By*Rxj;TR`Ll*XGlbsU3*fzl28VwU{Kmp3&IEyJM_7Q+n|of`n^z8%)@3(9 zDHrCTM5)l50yEDdlx?5w$(=7eO_7t~XM#CTFn6Nx6uBu<+c#(!aM*A|FjxPj;N*dP zAU7YH45Mt`z)az(PfDfWq33<~E5phIyaY)7bpp;8en_ZOcYVaA5SQ99D{SB8QkYBh z*$+>HOSg0Bs3TRPFVMHCQp;m_VoNQDo$-h>{?ZwLg)uaBn+~G%m0AWskBqh;WZ`_N zR2mHscZRq=;%12h!xpBAYbNd(IL103>o|9KJaCMRj9q+I(C?+jyAx?!H#2wo_8G}+ zN^i5Yk-o%F^|o|2)t9924A7)9Z4oWDPfKoVWR0@SJv#-JwX|j%sc|``*_ymRVUNld z0Oajs8PkaQ1m3pRGGHT{0=uq@t|@92s8yg=fm#J>6{uC9R)JatY89we zpjLre1!@(jRp5WI0!>@J1C-YnyKspSb1v1@2=0Y(34Y&0>vou=4p-W3LKMwI zv+T;IqxABHdF$9wx)Nde&(Aym1`q?k8E-7y{}Z5h9uj1kE>MU^hq&GmFx*xp`?yS3 zole_{5&mWQo+_DMtHF+oN#1BadY3}Uxc}E6?uEOSv&pPtJ(VRSNfdA8vccjMj8l~A Mx(!kn-yE_30zzxhh5!Hn diff --git a/mrUtilities/ImageProcessing/upConv.mexmaci64 b/mrUtilities/ImageProcessing/upConv.mexmaci64 index bb2974a0f305fbb29fcdb4d0d6af82ebce38e8f6..2abd6da3f57e88f32e81d84311fd611ff28d9e2b 100755 GIT binary patch literal 42804 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≦>y+Nf=KiM>d^ca2t%|3&FS7w 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 H7{~twBOmg| diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexmaci64 b/mrUtilities/MatlabUtilities/mrDisp.mexmaci64 index 5914140a557c593eb6aeff76211daaad435586fe..62ca2485eeba3e9c4b8742e991738f7ed2edcf7d 100755 GIT binary patch literal 8824 zcmeHNPiS047@wCm%{GQ6m4X&y`y!@I8_6FlG^oYh+O@A3>q?R$N}O!6Z?n(tzq~(n z14UZ=gK-HgdT7B*j(YPV=)qE`Q1PPl&aZ!WF{bTcjKz?8k@`9rdk30iKhpOaW1LSXU!L|&?O9(tWC9}u zlZiYycg>2s=^KqVwNi^fZdcqfHF;f_``6t+Cyg5%m&aZ&h0<9#6F^^1CmWyrSJFmmn{->`}q zZWXg7AGgoPry^cT1RxxZA!C!Kd7bd*^;BXy5vq~%c@ItNL?&v*c`?tO<-&X^&&#e2 zeD`>?wtPWcYA=QOr*$HQ^TkqVnQVy{6VLUaXh1j`PtGa1aGpc@huTvAQsQG~4VR(3CTy#SI#M|ccXjo?L{!`12YZgXMhM(&Y+R!-H^?<)4 z0u%2_6a$I@#eiZ!F`yVw3@8Tvoq-u+<#yuSlyP~Z58F|r+W8@NsYaCcQo%= zqq^T%`LX^vLK&+mZF2Rq^}m`;m?l^LtkVwHSUu5z0vx|yuacqKdCm_qQNIBA%N}@q zg957aLG`HUoG2WMP0v|cy0DZ{TVo9M|uyw4UK*5K}OGq_U{9YU!QFZL)XGW z^*6gd(mL2pd{&Hg&E9AZ*5T9~{MpYp{QNgR{~h_{>cgKvC8nB#UqIJ#$B{RFYBrm> zB$a-k5^XXYx2S{yYkW;5+Nd_Jp)}~QnW@C-4|e7If}Odx1`N>#SO{n?vL|=SxMn$ z+|80^cAE7blM@#E9ktW7g+x8fXsw-QGXN|a3_>6Pa_ZG z7bEzU2&QXJ_)|B&6p#bsFAy(+-wkl9KRUC)ffJ+qXPjQiJLot$(0w_b1>Ma@XT(#B vBLerJscpXf*=n-{E5J7elP_m6G=FmD0v(#OEz5*^PI}Qy+IES4Tmkt99zKGp literal 8752 zcmeHNO>7%Q6rRlwq!1b-A!wkq8`RL0N^yb^jrg_GxY?>!lj5W*MeWo->v)Onb@qoQ zQV%JABC;a+%mG!1LobL5#0h~aK%yr=oH!uS10Y*~)Kftbs(EjI+}(}i^a7`S((Ie} z=FK;6znP79XY$>~pSSKNMCm4k^g($LN*x?;fkqjG@}oisrE`f>bIj$xMEoWZHb9VG zQ80p1M_+cZ#wCbX*~$H%h<3DlM4hP~vg$P{wH-HOw_HKIeFMB{kBC4UZlfiA0`D&2 z;dr#57c062z98O|z?&5DXm_>Hpt32ytnFAvc|pVk@!l4AyG6USC8Q4(wtqf85O}i!0PP@##72$t`VO1dsrX#n(<0|{3yt%NYemitrR5^ES4tVPNGpy7 zdA7%saJW9OzZB#j*NGHL7fjDG*@PDnKoSq_ARgu@h0f{s%WykAY=;j@H-_IC52K^&#YqsNaWrjE*IOvTeJ_+*C@6?83&zWP!nUAP zpuc#8eNB&mh2tzg{r+dU#tW~1d^$F`@Lc|zC&8yeL42|kGJt}0#(npf$`eqRw~itG zP%tlDIhvPd^rUXthFJzTHZZ(XKD_t9 z>LMNR4yUb*S#A%>+W-Oc^y5AR9;G9$nKjc}z+w~h=ePyXt^4o30#t2-Yw0Q7ddBiJbI^2b@}IL=H(FK(Y)ZsFr|bsL|#NzAIGokFx|WJ=4UxfRgdkNIp9uX{US`~(z03h)A)iaIzK-ZeXH$gRr|7O<>cKwH4Z^4z23aeImaTy&B)i@jLk`&{+0~Z$PZ$dvrg%&pyDvK9oyfgD69!3*qiq zEP;7J=z`F5LZ1~H0b_TJ8U}_8gbaiXgbaiXgbaiXgbaiXgbaiXgbaiXgbe(j4BS7Y z&f|Cw$0#0wr?ZPRTUewEZraL`6HAdA_vcL4sgPO>PC9bzn1 zn~8$`j=1Hl1E+Z;(oJr#Es-0$FR^`%3lOviY7wz7fqNtaV9Wn6KsT7Ut-@pou3j7W zPQ3Pu0ehO;ts~)=xD7i>TuukP~Juo&*y;JJFc7?t?$+c#N^ iVxNakA9L}Ulhk`imvnROA$rpsQ`d&m?u2ET7Wo%53>-TE diff --git a/mrUtilities/mrFlatMesh/assignToNearest.mexmaci64 b/mrUtilities/mrFlatMesh/assignToNearest.mexmaci64 index 9b899ea5a72f9dc6fc1c82c6b07be61d53475482..cb11200256f708d200cf7a715b62fa8a39679007 100755 GIT binary patch delta 1564 zcmZuxZ%kWN6u+;f^pzHF%L-${Mv1TtB(BX!*y83|+DdC$#QljP)DGlzHsjAIt*LRO zY|szSrU`gmV)T=gRJ@@2E{KWpl! zP*{u7k&e%PZ|Asf)}RVEw(P%LOyI|1eIOBLM@J0n=S~Q+5s`kLh zA630Aa8fZBSA$sUig6oTE)KB*KuF4pY%_fx+F>Jb@Vv`j#nMQz!I%z)NnBI z11#;n){{Q*DS%upe7vcXxq9ry?+`vA!)1h306pT8H`o{6`5=)BrB6Yfq2F1 zI3N#jdLe2+{7Vr-s>?7>ZVtmz37Be4@s!J>zj_>Y3B%q7?wES0j+IVLWYtjv3{RpS z0o|3FSosEe;u9s?&71(xml|I1if&SrC$fs`d2oTuds7o6_kv6^fcad)DU)S?Qi^K) z$;}uh=)we*@@!{U+{v51R3J;IZ^()AJ}&PUvwkk?5({qIDxUc;gwlE!At8F`*0&Hw z+{vxzTh6q>AXqLZq|AeS{&9-WjLX=(6S0>gR>9z`(sP#dC_WhA= z<3qH%?m>w5-7e6c%@FPRxjcGi1zIX%7wyad#x*A3uL4 z9wngFm_Um=BSL>vaE-P?J0 zM~5#C331fyiZ-rH4T@|WE(5BX>bcm+YtiuN2x$$4hlGeQ77Is&!TyWkH-yMI>NYjW z?}hEp)xp4g)ud{ngpjL%55U1~*Fd;ngd)e%LlR1@M8UeF90J8S*|1&!ho54ncJWts z@qu0Z!Y&@)$+6~4D*~()jHMfDbvhXmdl|A8WEO&_@aP1P{Ldk<`G#ih2)o;GS|iW! EAMB5jkN^Mx literal 8752 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%0AeCp7!~@{0 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 Date: Fri, 24 Jan 2020 10:10:35 +0000 Subject: [PATCH 042/254] bug --- mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 823070a12..764dce5f8 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -432,9 +432,9 @@ [~,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) ); + 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 From 8a108c621551086c357bf3743ef728b9b7a6e89a Mon Sep 17 00:00:00 2001 From: jb85aub Date: Thu, 27 Feb 2020 10:10:20 +0200 Subject: [PATCH 043/254] combineTransformOverlays.m: handles cases in which scans have different dimensions --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index a70d74328..a5ce27a4c 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -649,13 +649,18 @@ case {'3D Array','4D Array','Scalar'} outputOverlay(iOverlay) = defaultOverlay; 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}) + 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 @@ -677,8 +682,6 @@ end end end - maxValue = max(allScansData(allScansData-inf)); outputOverlay(iOverlay).clip = [minValue maxValue]; outputOverlay(iOverlay).range = [minValue maxValue]; outputOverlay(iOverlay).name = outputOverlayNames{iOverlay}; From 760654fb99471725572fc1159b37adde2cf18424 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Thu, 27 Feb 2020 10:21:16 +0200 Subject: [PATCH 044/254] weightedMeanStd.m: added optional argument for population mean range --- .../weightedMeanStd.m | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) 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); From c6b0839e5a51fbd81d3b63a7a0879ac952e96bcc Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Fri, 13 Mar 2020 10:20:31 +0000 Subject: [PATCH 045/254] adds an option to check for quiet group deletion when scripting mrTools --- mrLoadRet/View/viewSet.m | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 4ec3674a6..3f42e2d9f 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -284,12 +284,26 @@ nScans = viewGet(view,'nScans'); % 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'); From efec381e17ee55462ff789624f611be4a936f3b2 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Mon, 16 Mar 2020 10:07:40 +0000 Subject: [PATCH 046/254] resetting defaults --- mrLoadRet/Edit/setFramePeriod.m | 4 ++-- mrLoadRet/File/loadROI.m | 2 +- mrLoadRet/GUI/mrPrint.m | 3 ++- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m | 2 ++ .../pRFGetSomatoStimImageFromStimfile.m | 2 +- mrUtilities/MatlabUtilities/mrDisp.mexmaci64 | Bin 8824 -> 0 bytes 6 files changed, 8 insertions(+), 5 deletions(-) delete mode 100755 mrUtilities/MatlabUtilities/mrDisp.mexmaci64 diff --git a/mrLoadRet/Edit/setFramePeriod.m b/mrLoadRet/Edit/setFramePeriod.m index 32884ec7e..613a99f67 100644 --- a/mrLoadRet/Edit/setFramePeriod.m +++ b/mrLoadRet/Edit/setFramePeriod.m @@ -93,8 +93,8 @@ hdr.xyzt_units = newUnits; end % set the frameperiod - %hdr.pixdim(5) = framePeriod*1000; - hdr.pixdim(5) = 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/loadROI.m b/mrLoadRet/File/loadROI.m index a77279d62..d220052c3 100644 --- a/mrLoadRet/File/loadROI.m +++ b/mrLoadRet/File/loadROI.m @@ -85,7 +85,7 @@ s = load(pathStr{p}); varNames = fieldnames(s); roi = eval(['s.',varNames{1}]); - roi.name = varNames{1}; + %roi.name = varNames{1}; % Add it to the view [view tf] = viewSet(view,'newROI',roi,replaceDuplicates); if tf diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 2529a7be1..943c04335 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -17,7 +17,8 @@ mrGlobals; % get input arguments -getArgs(varargin,{'useDefault=1','roiSmooth=0','roiLabels=0'}); +%getArgs(varargin,{'useDefault=1','roiSmooth=0','roiLabels=0'}); +getArgs(varargin,{'useDefault=0','roiSmooth=1','roiLabels=1'}); % get base type baseType = viewGet(v,'baseType'); diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m index 147a5c39b..26ece2402 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m @@ -207,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... diff --git a/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m index 63da73b7c..6ab01d2ad 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m +++ b/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m @@ -110,7 +110,7 @@ % load stimfile if isstr(stimfile) stimfile = setext(stimfile,'mat'); - if ~isfile(stimfile) + if ~mlrIsFile(stimfile) disp(sprintf('(pRFGetSomatoStimImageFromStimfile) Could not open stimfile: %s',stimfile)); return end diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexmaci64 b/mrUtilities/MatlabUtilities/mrDisp.mexmaci64 deleted file mode 100755 index 62ca2485eeba3e9c4b8742e991738f7ed2edcf7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8824 zcmeHNPiS047@wCm%{GQ6m4X&y`y!@I8_6FlG^oYh+O@A3>q?R$N}O!6Z?n(tzq~(n z14UZ=gK-HgdT7B*j(YPV=)qE`Q1PPl&aZ!WF{bTcjKz?8k@`9rdk30iKhpOaW1LSXU!L|&?O9(tWC9}u zlZiYycg>2s=^KqVwNi^fZdcqfHF;f_``6t+Cyg5%m&aZ&h0<9#6F^^1CmWyrSJFmmn{->`}q zZWXg7AGgoPry^cT1RxxZA!C!Kd7bd*^;BXy5vq~%c@ItNL?&v*c`?tO<-&X^&&#e2 zeD`>?wtPWcYA=QOr*$HQ^TkqVnQVy{6VLUaXh1j`PtGa1aGpc@huTvAQsQG~4VR(3CTy#SI#M|ccXjo?L{!`12YZgXMhM(&Y+R!-H^?<)4 z0u%2_6a$I@#eiZ!F`yVw3@8Tvoq-u+<#yuSlyP~Z58F|r+W8@NsYaCcQo%= zqq^T%`LX^vLK&+mZF2Rq^}m`;m?l^LtkVwHSUu5z0vx|yuacqKdCm_qQNIBA%N}@q zg957aLG`HUoG2WMP0v|cy0DZ{TVo9M|uyw4UK*5K}OGq_U{9YU!QFZL)XGW z^*6gd(mL2pd{&Hg&E9AZ*5T9~{MpYp{QNgR{~h_{>cgKvC8nB#UqIJ#$B{RFYBrm> zB$a-k5^XXYx2S{yYkW;5+Nd_Jp)}~QnW@C-4|e7If}Odx1`N>#SO{n?vL|=SxMn$ z+|80^cAE7blM@#E9ktW7g+x8fXsw-QGXN|a3_>6Pa_ZG z7bEzU2&QXJ_)|B&6p#bsFAy(+-wkl9KRUC)ffJ+qXPjQiJLot$(0w_b1>Ma@XT(#B vBLerJscpXf*=n-{E5J7elP_m6G=FmD0v(#OEz5*^PI}Qy+IES4Tmkt99zKGp From a6a5d67bf3a87cde592d458e69e742bd723a43f1 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Mon, 16 Mar 2020 10:11:36 +0000 Subject: [PATCH 047/254] resetting to defaults --- mrLoadRet/Plugin/pRF/pRF.m | 188 +++------------ mrLoadRet/Plugin/pRF/pRFFit.m | 219 ++++-------------- mrLoadRet/Plugin/pRF/pRFGUI.m | 3 - .../Plugin/pRF/pRFGetStimImageFromStimfile.m | 0 mrLoadRet/Plugin/pRF/pRFMergeParams.m | 0 mrLoadRet/Plugin/pRF/pRFPlot.m | 0 mrLoadRet/Plugin/pRF/pRFPlugin.m | 0 7 files changed, 65 insertions(+), 345 deletions(-) mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFGUI.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFMergeParams.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFPlot.m mode change 100644 => 100755 mrLoadRet/Plugin/pRF/pRFPlugin.m diff --git a/mrLoadRet/Plugin/pRF/pRF.m b/mrLoadRet/Plugin/pRF/pRF.m index c8e09c320..974046536 100755 --- a/mrLoadRet/Plugin/pRF/pRF.m +++ b/mrLoadRet/Plugin/pRF/pRF.m @@ -76,8 +76,8 @@ r2.colormap = hot(312); r2.colormap = r2.colormap(end-255:end,:); r2.alpha = 1; -r2.colormapType = 'normal'; -r2.interrogator = 'myOverlayStats'; +r2.colormapType = 'setRangeToMax'; +r2.interrogator = 'pRFPlot'; r2.mergeFunction = 'pRFMergeParams'; % create the parameters for the polarAngle overlay @@ -178,12 +178,6 @@ thisPolarAngle = nan(1,n); thisEccentricity = nan(1,n); thisRfHalfWidth = nan(1,n); - - %thisr2 = nan(1,n); - thisRawParamsCoords = nan(3,n); - thisResid = nan(numel(concatInfo.whichScan),n); - thistSeries = nan(numel(concatInfo.whichScan),n); - thismodelResponse = nan(numel(concatInfo.whichScan),n); % get some info about the scan to pass in (which prevents % pRFFit from calling viewGet - which is problematic for distributed computing @@ -206,8 +200,7 @@ % 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 = 240; - blockSize = n; + blockSize = 240; tic; % break into blocks of voxels to go easy on memory % if blockSize = n then this just does on block at a time. @@ -230,11 +223,6 @@ % pass it into pRFFit and pRFFit will load the tSeries itself loadROI = loadROITSeries(v,loadROI,scanNum,params.groupName,'keepNAN',true); % 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); @@ -245,149 +233,30 @@ % display time update dispHeader(sprintf('(pRF) %0.1f%% done in %s (Estimated time remaining: %s)',100*blockStart/n,mlrDispElapsedTime(toc),mlrDispElapsedTime((toc*n/blockStart) - toc))); end - - 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; - %myVar = thehrfs.hrf_struct.voxHRFs; % smoothed version of hrfprf? - - end - - if params.pRFFit.HRFpRF == 1 - %keyboard - parfor 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 = pRFFit(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; - thisPolarAngle(ii) = fit.polarAngle; - thisEccentricity(ii) = fit.eccentricity; - thisRfHalfWidth(ii) = fit.std; - % keep parameters - rawParams(:,ii) = fit.params(:); - r(ii,:) = fit.r; - thisr2(ii) = fit.r2; - thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; - 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 - - - - - % regular prf fitting - parfor ii = blockStart:blockEnd - - fit = pRFFit(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); - - - if ~isempty(fit) - - thisr2(ii) = fit.r2; - thisPolarAngle(ii) = fit.polarAngle; - thisEccentricity(ii) = fit.eccentricity; - thisRfHalfWidth(ii) = fit.std; - % keep parameters - rawParams(:,ii) = fit.params(:); - r(ii,:) = fit.r; - thisr2(ii) = fit.r2; - thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; - thisResid(:,ii) = fit.residual; - thistSeries(:,ii) = fit.tSeries; - thismodelResponse(:,ii) = fit.modelResponse; - %keyboard - -% tempVar = zeros(length(overlayNames),1); -% -% for iOverlay = 1:numel(overlayNames) -% -% test = strcmpi(fieldnames(fit), overlayNames(iOverlay) ); -% -% 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)]; - - end - end - - end % now loop over each voxel -% parfor i = blockStart:blockEnd -% fit = pRFFit(v,scanNum,x(i),y(i),z(i),'stim',stim,'concatInfo',concatInfo,'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',i,'dispN',n,'tSeries',loadROI.tSeries(i-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,'paramsInfo',paramsInfo); -% if ~isempty(fit) -% % keep data, note that we are keeping temporarily in -% % a vector here so that parfor won't complain -% % then afterwords we put it into the actual overlay struct -% thisr2(i) = fit.r2; -% thisPolarAngle(i) = fit.polarAngle; -% thisEccentricity(i) = fit.eccentricity; -% thisRfHalfWidth(i) = fit.std; -% % keep parameters -% rawParams(:,i) = fit.params(:); -% r(i,:) = fit.r; -% end -% end + parfor i = blockStart:blockEnd + fit = pRFFit(v,scanNum,x(i),y(i),z(i),'stim',stim,'concatInfo',concatInfo,'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',i,'dispN',n,'tSeries',loadROI.tSeries(i-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,'paramsInfo',paramsInfo); + if ~isempty(fit) + % keep data, note that we are keeping temporarily in + % a vector here so that parfor won't complain + % then afterwords we put it into the actual overlay struct + thisr2(i) = fit.r2; + thisPolarAngle(i) = fit.polarAngle; + thisEccentricity(i) = fit.eccentricity; + thisRfHalfWidth(i) = fit.std; + % keep parameters + rawParams(:,i) = fit.params(:); + r(i,:) = fit.r; + end + end % set overlays - for ii = 1:n - r2.data{scanNum}(x(ii),y(ii),z(ii)) = thisr2(ii); - polarAngle.data{scanNum}(x(ii),y(ii),z(ii)) = thisPolarAngle(ii); - eccentricity.data{scanNum}(x(ii),y(ii),z(ii)) = thisEccentricity(ii); - rfHalfWidth.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidth(ii); + for i = 1:n + r2.data{scanNum}(x(i),y(i),z(i)) = thisr2(i); + polarAngle.data{scanNum}(x(i),y(i),z(i)) = thisPolarAngle(i); + eccentricity.data{scanNum}(x(i),y(i),z(i)) = thisEccentricity(i); + rfHalfWidth.data{scanNum}(x(i),y(i),z(i)) = thisRfHalfWidth(i); end end % display time update @@ -397,11 +266,6 @@ pRFAnal.d{scanNum}.params = rawParams; pRFAnal.d{scanNum}.r = r; - pRFAnal.d{scanNum}.r2 = thisr2; - pRFAnal.d{scanNum}.rawCoords = thisRawParamsCoords; - pRFAnal.d{scanNum}.myresid = thisResid; - pRFAnal.d{scanNum}.mytSeries = thistSeries; - pRFAnal.d{scanNum}.mymodelResp = thismodelResponse; iScan = find(params.scanNum == scanNum); thisParams.scanNum = params.scanNum(iScan); @@ -604,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 @@ -617,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 @@ -629,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 index 2008dd418..e81a57df7 100755 --- a/mrLoadRet/Plugin/pRF/pRFFit.m +++ b/mrLoadRet/Plugin/pRF/pRFFit.m @@ -10,7 +10,7 @@ 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 @@ -18,13 +18,6 @@ fitParams.concatInfo = viewGet(v,'concatInfo',scanNum); end -% adding own params... -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) @@ -104,43 +97,15 @@ fitParams = setFitParams(fitParams); % just return model response for already calcualted params -% if fitParams.getModelResponse -% % get model fit -% [residual fit.modelResponse fit.rfModel] = getModelResidual(fitParams.params,tSeries,fitParams); -% % get the canonical -% fit.p = getFitParams(fitParams.params,fitParams); -% fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); -% % return tSeries -% fit.tSeries = tSeries; -% return; -% end - if fitParams.getModelResponse - - if ~ieNotDefined('hrfprf') - %params = 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 - [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 + % get model fit + [residual fit.modelResponse fit.rfModel] = getModelResidual(fitParams.params,tSeries,fitParams); + % get the canonical + fit.p = getFitParams(fitParams.params,fitParams); + fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); + % return tSeries + fit.tSeries = tSeries; + return; end % return some fields @@ -212,7 +177,7 @@ % 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); + [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); @@ -223,7 +188,7 @@ fit.params = fitParams.initParams; fit.r2 = maxr^2; fit.r = maxr; - [fit.polarAngle, fit.eccentricity] = cart2pol(fit.x,fit.y); + [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); % 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)); @@ -232,54 +197,23 @@ end end -if ~ieNotDefined('hrfprf') - %params = 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); - return - end - % possibly we need to send to getCanonicalHRF to make sure the - % offsets/scaling line up... - +% 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 - - % 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); - return - end - + disp(sprintf('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); + return end -% 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('(pRFFit) Unknown optimization algorithm: %s',fitParams.algorithm)); -% return -% 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); +[residual modelResponse rfModel fit.r] = getModelResidual(params,tSeries,fitParams); 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') @@ -289,17 +223,11 @@ % compute polar coordinates [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); -fit.residual = residual; -fit.tSeries = tSeries; -fit.modelResponse = modelResponse; - % 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)); end -end - %%%%%%%%%%%%%%%%%%%%%% % setFitParams % %%%%%%%%%%%%%%%%%%%%%% @@ -423,11 +351,10 @@ end end -end %%%%%%%%%%%%%%%%%%%%%%%%%% %% getModelResidual %% %%%%%%%%%%%%%%%%%%%%%%%%%% -function [residual, modelResponse, rfModel, r, hrf] = getModelResidual(params,tSeries,fitParams,justGetModel, hrfprfcheck) +function [residual modelResponse rfModel r] = getModelResidual(params,tSeries,fitParams,justGetModel) residual = []; if nargin < 4, justGetModel = 0;end @@ -439,32 +366,17 @@ % compute an RF rfModel = getRFModel(p,fitParams); -if ieNotDefined('hrfprfcheck') - hrfprfcheck = 0; -end - % init model response modelResponse = [];residual = []; % create the model for each concat for i = 1:fitParams.concatInfo.n % get model response - nFrames = (fitParams.concatInfo.runTransition(i,2)-fitParams.concatInfo.runTransition(i,2)+1); + nFrames = fitParams.concatInfo.runTransition(i,2); thisModelResponse = convolveModelWithStimulus(rfModel,fitParams.stim{i},nFrames); - - if isfield(fitParams, 'hrfprf') - %keyboard - 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 % get a model hrf - %hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); + hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); % and convolve in time. thisModelResponse = convolveModelResponseWithHRF(thisModelResponse,hrf); @@ -480,10 +392,8 @@ thisModelResponse = thisModelResponse - mean(thisModelResponse); end - if isempty(justGetModel), justGetModel = 0; end - %if ~justGetModel + if ~justGetModel % compute correlation of this portion of the model response with time series - if justGetModel == 0 thisTSeries = tSeries(fitParams.concatInfo.runTransition(i,1):fitParams.concatInfo.runTransition(i,2)); thisTSeries = thisTSeries - mean(thisTSeries); @@ -510,53 +420,12 @@ residual = [residual;thisResidual(:)]; end - % return model only if justGetModel,return,end % scale the whole time series -% if ~fitParams.betaEachScan -% [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); -% end - -if ~isfield(fitParams, 'hrfprf') - if ~fitParams.betaEachScan - [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); - end -end -% -% 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 -% -if fitParams.getModelResponse ~= 1 - %if hrfprfcheck == 1 - if isfield(fitParams, 'hrfprf') - if ~any(isnan(modelResponse)) - %disp('bloop') - % 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 +if ~fitParams.betaEachScan + [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); end @@ -571,7 +440,7 @@ % disp(sprintf('(pRFFit:getModelResidual) r: %f',residual)); end -end + %%%%%%%%%%%%%%%%%%%%%% % dispModelFit % %%%%%%%%%%%%%%%%%%%%%% @@ -607,7 +476,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) canonical = getCanonicalHRF(p.canonical,fitParams.framePeriod); plot(canonical.time,canonical.hrf,'k-') if exist('myaxis') == 2,myaxis;end -end + %%%%%%%%%%%%%%%%%%%%%%%% % scaleAndOffset % %%%%%%%%%%%%%%%%%%%%%%%% @@ -625,7 +494,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) else residual = tSeries; end -end + %%%%%%%%%%%%%%%%%%%%%% %% getFitParams %% %%%%%%%%%%%%%%%%%%%%%% @@ -673,7 +542,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) otherwise disp(sprintf('(pRFFit) Unknown rfType %s',rfType)); end -end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% convolveModelWithStimulus %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -690,7 +559,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % and take the sum modelResponse(frameNum) = sum(sum(rfModel.*stim.im(:,:,frameNum))); end -end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% convolveModelResponseWithHRF %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -699,7 +568,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) n = length(modelTimecourse); modelTimecourse = conv(modelTimecourse,hrf.hrf); modelTimecourse = modelTimecourse(1:n); -end + %%%%%%%%%%%%%%%%%%%%% %% getGammaHRF %% %%%%%%%%%%%%%%%%%%%%% @@ -710,7 +579,6 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) if p.diffOfGamma fun = fun - thisGamma(time,p.amplitudeRatio,p.timelag2,p.offset2,p.tau2,p.exponent2)/100; end -end %%%%%%%%%%%%%%%%%%% %% thisGamma %% @@ -730,7 +598,6 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) gammafun = (gammafun-min(gammafun)) ./ (max(gammafun)-min(gammafun)); end gammafun = (amplitude*gammafun+offset); -end %%%%%%%%%%%%%%%%%%%%%%%%% @@ -743,7 +610,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % normalize to amplitude of 1 hrf.hrf = hrf.hrf / max(hrf.hrf); -end + %%%%%%%%%%%%%%%%%%%% %% getRFModel %% %%%%%%%%%%%%%%%%%%%% @@ -757,7 +624,6 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) else disp(sprintf('(pRFFit:getRFModel) Unknown rfType: %s',fitParams.rfType)); end -end %%%%%%%%%%%%%%%%%%%%%%%% @@ -767,13 +633,13 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % compute rf rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.std^2))+((fitParams.stimY-params.y).^2)/(2*(params.std^2)))); -end + %%%%%%%%%%%%%%%%%%% % parseArgs % %%%%%%%%%%%%%%%%%%% -function [v, scanNum, x, y, s, fitParams, tSeries, hrfprf] = parseArgs(args) +function [v scanNum x y s fitParams tSeries] = parseArgs(args); -v = [];scanNum=[];x=[];y=[];s=[];fitParams=[];tSeries = []; hrfprf=[]; +v = [];scanNum=[];x=[];y=[];s=[];fitParams=[];tSeries = []; % check for calling convention from interrogator if (length(args) >= 7) && isnumeric(args{6}) @@ -847,12 +713,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) 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=[]'}); + 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=[]'}); % default to display fit fitParams.dispFit = dispFit; fitParams.stim = stim; @@ -948,7 +809,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) fitParams.prefit.y = prefity(:); fitParams.prefit.rfHalfWidth = prefitrfHalfWidth(:); end -end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % checkStimForAverages % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1052,7 +913,6 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) end end -end %%%%%%%%%%%%%%%%% % getStim % %%%%%%%%%%%%%%%%% @@ -1086,7 +946,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) disp(sprintf('(pRFFit) Using precomputed stim image')); stim = gpRFFitStimImage.stim; end -end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % applyConcatFiltering % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1123,7 +983,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % make back into the right dimensions tSeries = tSeries(:)'; -end + %%%%%%%%%%%%% %% r2d %% %%%%%%%%%%%%% @@ -1142,4 +1002,3 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) while (sum(degrees<-360)) degrees = degrees + (degrees<-360)*360; end -end \ No newline at end of file diff --git a/mrLoadRet/Plugin/pRF/pRFGUI.m b/mrLoadRet/Plugin/pRF/pRFGUI.m old mode 100644 new mode 100755 index e6b9d340a..fc4cfef3d --- a/mrLoadRet/Plugin/pRF/pRFGUI.m +++ b/mrLoadRet/Plugin/pRF/pRFGUI.m @@ -149,9 +149,6 @@ 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} = {'saveForExport',0,'type=checkbox','Creates files for export to do the pRF on systems like BrainLife. This will save a stim.nii file containing the stimulus images, a bold.nii file with the time series a mask.nii file that contains the voxel mask over which to the analysis and a pRFparams.mat file which contains various parameters'}; -paramsInfo{end+1} = {'HRFpRF',false,'type=checkbox','Set to true if you want to load in pre-computed HRFs using prfhrfRefit'}; - - % if we are continuing, then just add on any parameters that % are needed (i.e. ones that are not included in the old % analysis) diff --git a/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m old mode 100644 new mode 100755 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 From eeb9393508c416970197316150d22a8771040838 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Wed, 18 Mar 2020 19:41:47 +0000 Subject: [PATCH 048/254] resetting to default --- mrLoadRet/File/loadROI.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/File/loadROI.m b/mrLoadRet/File/loadROI.m index d220052c3..a77279d62 100644 --- a/mrLoadRet/File/loadROI.m +++ b/mrLoadRet/File/loadROI.m @@ -85,7 +85,7 @@ s = load(pathStr{p}); varNames = fieldnames(s); roi = eval(['s.',varNames{1}]); - %roi.name = varNames{1}; + roi.name = varNames{1}; % Add it to the view [view tf] = viewSet(view,'newROI',roi,replaceDuplicates); if tf From b9b3d3e4bfa398031593c575c0c8fbdec7b24caf Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Wed, 18 Mar 2020 19:42:54 +0000 Subject: [PATCH 049/254] reset --- mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m index 3c2c5ff1f..6e1d4b87f 100755 --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -49,7 +49,7 @@ hemiNames = {'left', 'right'}; outDir = fullfile(pwd,'surfRelax'); if ~exist('volumeCropSize') - volumeCropSize = [256 256 256]; + volumeCropSize = [176 256 256]; end defaultPixelSize = [1 1 1]; if ~exist('pixelSize') @@ -126,7 +126,7 @@ end system(commandString); else - if isfile(outFile) + 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); From 048b357f873f28c9209654231ec6172b61d2e9b6 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Thu, 19 Mar 2020 20:38:33 +0000 Subject: [PATCH 050/254] generic bug explanation and removal of file --- .../GLM_v2/newGlmAnalysis/glmPlot_edit.m | 852 ------------------ .../GLM_v2/newGlmAnalysis/hrfDoubleGamma.m | 10 +- 2 files changed, 8 insertions(+), 854 deletions(-) delete mode 100755 mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot_edit.m diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot_edit.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot_edit.m deleted file mode 100755 index a26eee129..000000000 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot_edit.m +++ /dev/null @@ -1,852 +0,0 @@ -function glmPlot_edit(thisView,overlayNum,scanNum,x,y,s,roi) -% glmPlot.m -% -% $Id$ -% usage: glmPlot() is an interrogator function -% by: julien besle, modified from eventRelatedPlot and glmPlot -% date: 09/14/07, 12/02/2010 -% purpose: plot GLM beta weights, contrasts, estimated HDR and time-series from GLM analysis - - -% check arguments -if ~any(nargin == [1:7]) - help glmPlot - return -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Get data -% get the analysis structure -analysisType = viewGet(thisView,'analysisType'); -analysisParams = convertOldGlmParams(viewGet(thisView,'analysisParams')); - -if isempty(analysisType) - mrWarnDlg('(glmPlot) No analysis is loaded'); - return -end - -if ~ismember(analysisType,{'glmAnalStats','glmAnal','glmcAnal','erAnal','deconvAnal'}) - disp(['(glmPlot) Wrong type of analysis (' analysisType ')']); - return; -end -glmData = viewGet(thisView,'d'); -if isempty(glmData) - disp(sprintf('(glmPlot) No GLM analysis for scanNum %i',scanNum)); - return -end -r2data = viewGet(thisView,'overlaydata',scanNum,1); -r2clip = viewGet(thisView,'overlayClip',1); -numberEVs = glmData.nhdr; -framePeriod = viewGet(thisView,'framePeriod',scanNum); - - -if isfield(glmData, 'contrasts') && ~isempty(glmData.contrasts) - numberContrasts = size(glmData.contrasts,1); - if isfield(glmData,'EVnames') - contrastNames = makeContrastNames(glmData.contrasts,glmData.EVnames); - else - for iContrast = 1:numberContrasts - contrastNames{iContrast} = num2str(glmData.contrasts(iContrast,:)); - end - end -else - numberContrasts=0; -end - -if isfield(glmData,'EVnames') - EVnames = glmData.EVnames; -else - for i_beta = 1:length(glmData.stimNames) - EVnames{i_beta} = [num2str(i_beta) ': ' glmData.stimNames{i_beta}]; - end -end - -if ismember(analysisType,{'glmAnalStats','glmAnal','glmcAnal'}) - plotBetaWeights = 1; - plotDeconvolution=0; - - % check to see if there is a regular event related analysis - erAnalyses = []; - for anum = 1:viewGet(thisView,'nAnalyses') - if ismember(viewGet(thisView,'analysisType',anum),{'erAnal','deconvAnal'}) - erAnalyses = [erAnalyses anum]; - end - end - if ~isempty(erAnalyses) - if length(erAnalyses)==1 - erAnalNum = erAnalyses; - else - erAnalNum = 1:length(erAnalyses); - while length(erAnalNum)>1 - erAnalNames = viewGet(thisView,'analysisNames'); - erAnalNum = find(buttondlg('Choose a deconvolution analysis or press OK if none is required',erAnalNames(erAnalyses))); - end - if all(~size(erAnalNum)) - return - end - erAnalNum = erAnalyses(erAnalNum); - end - if ~isempty(erAnalNum) - % get the event related data - deconvData = viewGet(thisView,'d',scanNum,erAnalNum); - deconvAnalysisParams = convertOldGlmParams(viewGet(thisView,'analysisParams',erAnalNum)); - %check that the EVs are compatible between the deconvolution and the GLM analysis - if ~isfield(deconvData,'EVnames') - mrWarnDlg('(glmPlot) Cannot plot old deconvolution analysis'); - clear('deconvData'); - elseif isequal(deconvData.EVnames,glmData.EVnames) - plotDeconvolution=1; - else - mrWarnDlg('(glmPlot) Name of EVs in deconvolution and GLM analyses are incompatible.'); - clear('deconvData'); - end - end - % - end -else - plotDeconvolution=0; - plotBetaWeights = 0; -end - - -%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& Set graph constants - -% select the window to plot into -fignum = selectGraphWin; -initializeFigure(fignum,max(numberEVs,numberContrasts)) -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; -yMargin = .01; -for iPlot = 1:length(roi)+1 - betaSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,1,subplotXgrid,subplotYgrid,xMargin,yMargin); - contrastSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,2,subplotXgrid,subplotYgrid,xMargin,yMargin); - ehdrSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,3,subplotXgrid,subplotYgrid,xMargin,yMargin); - contrastEhdrSubplotPosition(iPlot,:) = getSubplotPosition(iPlot,4,subplotXgrid,subplotYgrid,xMargin,yMargin); - stePopupPosition(iPlot,:) = getSubplotPosition(iPlot,5,subplotXgrid,subplotYgrid,xMargin,yMargin); - tSeriesButtonPosition(iPlot,:) = getSubplotPosition(iPlot,6,subplotXgrid,subplotYgrid,xMargin,yMargin); -end -deconvButtonPosition = getSubplotPosition(3+length(roi),5,subplotXgrid,subplotYgrid,xMargin,yMargin); -ehdrButtonPosition = getSubplotPosition(3+length(roi),6,subplotXgrid,subplotYgrid,xMargin,yMargin); -legendBetaPosition = getSubplotPosition(3+length(roi),1,subplotXgrid,subplotYgrid,xMargin,yMargin); -legendContrastsPosition = getSubplotPosition(3+length(roi),2,subplotXgrid,subplotYgrid,xMargin,yMargin); -legendEhdrPosition = getSubplotPosition(3+length(roi),3,subplotXgrid,subplotYgrid,xMargin,yMargin); -legendHdrContrastPosition = getSubplotPosition(3+length(roi),4,subplotXgrid,subplotYgrid,xMargin,yMargin); - - -%&&&&&&&&&&&&&&&&&&&&&& LOOP on voxel + ROIs (if any) %&&&&&&&&&&&&&&&&&&&&&& - -if ~isempty(roi) - volumeBetas = reshape(glmData.ehdr,[numel(r2data) size(glmData.ehdr,4) size(glmData.ehdr,5)]); -% volumeBetaSte = reshape(glmData.ehdrste,[numel(r2data) size(glmData.ehdrste,4) size(glmData.ehdrste,5)]); -% if numberContrasts -% volumeRSS = glmData.rss; -% end -% if plotDeconvolution -% volumeDeconv = reshape(deconvData.ehdr,[numel(r2data) size(deconvData.ehdr,4) size(deconvData.ehdr,5)]); -% end -end - -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)); - volumeIndices = [x y s]; - e = getEstimates(glmData,analysisParams,volumeIndices); - buttonString{1} = 'estimate std error'; - - else %this is an ROI - - hWait = uicontrol('parent',fignum,'style','text','unit','normalized',... - 'string','Computing Standard Errors for ROI. Please wait...','position',ehdrSubplotPosition(iPlot,:),'backgroundcolor',get(fignum,'color')); - drawnow; - roiNum = iPlot-1; - % get roi scan coords - roi{roiNum}.scanCoords = getROICoordinates(thisView,roi{roiNum},scanNum); - %get ROI estimates - volumeIndices = sub2ind(size(r2data),roi{roiNum}.scanCoords(1,:),roi{roiNum}.scanCoords(2,:),roi{roiNum}.scanCoords(3,:)); - roiIndices = (r2data(volumeIndices)>r2clip(1)) & (r2data(volumeIndices)1 && iSte ==1 -% disp(titleString{1}); -% disp(e.contrastBetas); -% end - 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 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 iSte==1, hContrastHdr =h; hEhdr = [hEhdr;h];end; - end - - if iSte~=1 - set(hEhdrSte(:,:,iSte),'visible','off'); - end - - end - uicontrol('Parent',fignum,... - 'units','normalized',... - 'Style','popupmenu',... - 'Callback',{@makeVisible,hEhdrSte},... - 'String', [buttonString {'No error bars'}],... - 'Position',stePopupPosition(iPlot,:)); - % % % % display ehdr with out lines if we have a fit - % % % % since we also need to plot fit - % % % if isfield(glmData,'peak') & isfield(glmData.peak,'fit') & ~any(isnan(glmData.peak.amp(x,y,s,:))) - % % % h = plotEhdr(e.time,e.hdr,e.hdrSte,''); - % % % for r = 1:glmData.nhdr - % % % glmData.peak.fit{x,y,s,r}.smoothX = 1:.1:glmData.hdrlen; - % % % fitTime = glmData.tr*(glmData.peak.fit{x,y,s,r}.smoothX-0.5); - % % % plot(fitTime+glmData.tr/2,glmData.peak.fit{x,y,s,r}.smoothFit,getcolor(r,'-')); - % % % end - % % % xaxis(0,e.time(end)+framePeriod/2); - % % % end - % % % % add peaks if they exist to the legend - % % % if isfield(glmData,'peak') - % % % for i = 1:glmData.nhdr - % % % names{i} = sprintf('%s: %s=%0.2f',names{i},glmData.peak.params.method,glmData.peak.amp(x,y,s,i)); - % % % end - % % % end - - % if there is deconvolution data, display that too - %but do not plot the std deviation (it gets too cluttered) - if plotDeconvolution - if numberContrasts - deconvAnalysisParams.contrasts = analysisParams.contrasts; - end - eDeconv = getEstimates(deconvData,deconvAnalysisParams,volumeIndices); - if any(any(eDeconv.hdr)) - eDeconv.hdr = mean(eDeconv.hdr,3); - h=plotEhdr(ehdrAxes,eDeconv.time,eDeconv.hdr); - hDeconv = cat(1,hDeconv,h); - if numberContrasts - if ~isempty(eDeconv.contrastHdr) - eDeconv.contrastHdr = mean(eDeconv.contrastHdr,3); - h = plotEhdr(hdrContrastAxes,eDeconv.time,eDeconv.contrastHdr); - hDeconv = cat(1,hDeconv,h); - else - mrWarnDlg('(glmPlot) Cannot plot contrast for deconvolution analysis'); - end - end - end - else - eDeconv.time=0; - end - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Finalize axes - if ~isempty(e.betas) - %plot baselines of histograms - if plotBetaWeights - 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)))]); - 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); - maxSte = max(e.contrastBetaSte,[],3); - if isnan(maxSte) - maxSte = 0; - end - makeScaleEditButton(fignum,contrastAxes,... - [nanmin(nanmin(e.contrastBetas-maxSte)),nanmax(nanmax(e.contrastBetas+maxSte))]); - if iPlot==1 - ylabel(contrastAxes,{'Contrast' 'Estimates'}); - lhandle = legend(hContrastBeta,contrastNames,'position',legendContrastsPosition); - set(lhandle,'Interpreter','none','box','off'); - end - end - end - set(ehdrAxes,'xLim',[0,max(eDeconv.time(end),e.time(end))+framePeriod/2]); - maxSte = abs(max(e.hdrSte,[],3)); - makeScaleEditButton(fignum,ehdrAxes,... - [nanmin(nanmin((e.hdr-maxSte))),nanmax(nanmax((e.hdr+maxSte)))]); - if iPlot==1 - ylabel(ehdrAxes,{'Scaled HRF','% Signal change'}); - lhandle = legend(hHdr,EVnames,'position',legendEhdrPosition); - set(lhandle,'Interpreter','none','box','off'); - end - if numberContrasts - set(hdrContrastAxes,'xLim',[0,max(eDeconv.time(end),e.time(end))+framePeriod/2]); - maxSte = max(e.contrastHdrSte,[],3); - if isnan(maxSte) - maxSte = 0; - end - makeScaleEditButton(fignum,hdrContrastAxes,... - [nanmin(nanmin(e.contrastHdr-maxSte)),nanmax(nanmax(e.contrastHdr+maxSte))]); - if iPlot==1 - ylabel(hdrContrastAxes,{'Scaled Contrast HRF','% Signal change'}); - lhandle = legend(hContrastHdr,contrastNames,'position',legendHdrContrastPosition); - set(lhandle,'Interpreter','none','box','off'); - end - xlabel(hdrContrastAxes,'Time (sec)'); - else - xlabel(ehdrAxes,'Time (sec)'); - end - end - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% plot the time series - %put a button to plot the time series of this roi - if iPlot==1 - thisRoi = [x y s]; - else - thisRoi = roi{roiNum}; - end - uicontrol('Parent',fignum,... - 'units','normalized',... - 'Style','pushbutton',... - 'Callback',{@eventRelatedPlotTSeries, thisView, analysisParams, glmData, thisRoi},... - 'String',['Plot the time series for ' titleString{1}],... - 'Position',tSeriesButtonPosition(iPlot,:)); - - -end - - -if plotDeconvolution && ~isempty(hDeconv) - set(hDeconv,'visible','off','MarkerEdgeColor','none','MarkerFaceColor','none'); - uicontrol('Parent',fignum,... - 'units','normalized',... - 'Style','pushbutton',... - 'Callback',{@makeVisible,hDeconv},... - 'String','Show deconvoluted HDR',... - 'Position',deconvButtonPosition); - uicontrol('Parent',fignum,... - 'units','normalized',... - 'Style','pushbutton',... - 'Callback',{@makeVisible,hEhdr},... - 'String','Hide estimated HDR',... - 'Position',ehdrButtonPosition); -end - -drawnow; - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% function to plot the time series for the voxel and rois % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function eventRelatedPlotTSeries(handle,eventData,thisView,analysisParams, d, roi) - -fignum = selectGraphWin(0,'Make new'); -initializeFigure(fignum,d.nhdr); -monitorPositions = getMonitorPositions; -figurePosition = get(fignum,'position'); -[whichMonitor,figurePosition]=getMonitorNumber(figurePosition,monitorPositions); -screenSize = monitorPositions(whichMonitor,:); % find which monitor the figure is displayed in -figurePosition(3) = screenSize(3); -figurePosition(4) = screenSize(3)/5; -set(fignum,'position',figurePosition); -h=uicontrol('parent',fignum,'unit','normalized','style','text',... - 'string','Loading timecourse. Please wait...',... - 'position',[0.1 0.45 .8 .1],'backgroundcolor',get(fignum,'color')); -%this removes the figure toolbar (because the figure toolbar property is set to 'auto' by default) -%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'); - -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))); - titleString = sprintf('Voxel %i,%i,%i Time Series',roi(1),roi(2),roi(3)); - ehdr = shiftdim(d.ehdr(roi(1),roi(2),roi(3),:,:),3); -else - fprintf(1,'\n'); - roi = loadROITSeries(thisView,roi); - actualTSeries = mean(roi.tSeries,1)'; - titleString = ['ROI ' roi.name ' Time Series']; - ehdr = permute(d.ehdr,[4 5 1 2 3]); - ehdr = ehdr(:,:,sub2ind(d.dim(1:3),roi.scanCoords(1,:)',roi.scanCoords(2,:)',roi.scanCoords(3,:)')); - ehdr = nanmean(ehdr,3); -end -%convert to percent signal change the way it's done in getGlmStatistics -actualTSeries = (actualTSeries - mean(actualTSeries))/mean(actualTSeries)*100; -junkFrames = viewGet(thisView, 'junkFrames'); -nFrames = viewGet(thisView,'nFrames'); -actualTSeries = actualTSeries(junkFrames+1:junkFrames+nFrames); - -% if isfield(analysisParams,'scanParams') && isfield(analysisParams.scanParams{thisView.curScan},'acquisitionSubsample')... -% && ~isempty(analysisParams.scanParams{thisView.curScan}.acquisitionSubsample) -% acquisitionSubsample = analysisParams.scanParams{thisView.curScan}.acquisitionSubsample; -% else -% acquisitionSubsample = 1; -% end -% if ~isfield(d,'estimationSupersampling') -% d.estimationSupersampling=1; -% end -% time = ((0:length(actualTSeries)-1)+(acquisitionSubsample-.5)/d.estimationSupersampling)*d.tr; -if ~isfield(d,'acquisitionDelay') - d.acquisitionDelay=d.tr/2; -end -time = rem(d.acquisitionDelay,d.tr)+d.tr*(0:length(actualTSeries)-1); - -% frequencies = 1/length(actualTSeries)/d.tr:1/length(actualTSeries)/d.tr:1/d.tr/2; -% frequencies = (1/length(actualTSeries):1/length(actualTSeries):1/2)./d.tr; -frequencies = (1:1:length(actualTSeries)/2)./length(actualTSeries)./d.tr; - - -delete(h); -set(fignum,'name',titleString) -tSeriesAxes = axes('parent',fignum,'outerposition',getSubplotPosition(1:3,1,[1 1 4],[1 1],0,0)); -hold on -fftAxes = axes('parent',fignum,'outerposition',getSubplotPosition(3,2,[1 1 4],[1 1],0,0)); -hold on -%hold(tSeriesAxes); - - -%Plot the stimulus times -set(tSeriesAxes,'Ylim',[min(actualTSeries);max(actualTSeries)]) -if ~isfield(d,'designSupersampling') - d.designSupersampling=1; -end - -colorOrder = get(tSeriesAxes,'colorOrder'); -if isfield(d,'EVmatrix') && isfield(d,'EVnames') - stimOnsets = d.EVmatrix; - stimDurations = []; - legendString = d.EVnames; -elseif isfield(d,'stimDurations') && isfield(d, 'stimvol') - stimOnsets = d.stimvol; - stimDurations = d.stimDurations; - legendString = d.stimNames; -elseif isfield(d, 'stimvol') - stimOnsets = d.stimvol; - stimDurations = []; - legendString = d.stimNames; -end -if isfield(d,'runTransitions') - runTransitions = d.runTransitions; -else - runTransitions = []; -end - -[h,hTransitions] = plotStims(stimOnsets, stimDurations, d.tr/d.designSupersampling, colorOrder, tSeriesAxes, runTransitions); -legendString = legendString(h>0); -h = h(h>0); -nEVs = length(h); - -%and the time-series -%plot a baseline -plot(tSeriesAxes,time,zeros(size(time)),'--','linewidth',1,'color',[.5 .5 .5]); -h(end+1) = plot(tSeriesAxes,time,actualTSeries,'k.-'); -hActualTSeries = h(end); - -nComponents = size(d.scm,2); -if nComponents==numel(ehdr) - %compute model time series - extendedEhdr = reshape(ehdr',numel(ehdr),1); - if fieldIsNotDefined(d,'emptyEVcomponents') - d.emptyEVcomponents = []; - else - d.scm(:,d.emptyEVcomponents)=[]; - extendedEhdr(d.emptyEVcomponents)=[]; - end - modelTSeries = d.scm*extendedEhdr; - h(end+1) = plot(tSeriesAxes,time,modelTSeries,'--r'); - hModelTSeries = h(end); -end -legendString{end+1} = 'Actual Time Series'; -legendString{end+1} = 'Model Time Series'; -if ~isempty(hTransitions) - h = [h hTransitions]; - legendString{end+1} = 'Run transitions'; -end -ylabel(tSeriesAxes,'Percent Signal Change'); -xlim(tSeriesAxes,[0 ceil(time(end)+1)]); - -%FFT -actualFFT = abs(fft(actualTSeries)); -actualFFT = actualFFT(1:floor(end/2)); -hActualFFT = plot(fftAxes,frequencies,actualFFT,'k.-'); -if nComponents==numel(ehdr) - modelFFT = abs(fft(modelTSeries)); - modelFFT = modelFFT(1:floor(end/2)); - hModelFFT = plot(fftAxes,frequencies,modelFFT,'--r'); -end -xlim(fftAxes,[0 1/d.tr/2]); -xlabel(fftAxes,'Frequencies (Hz)'); -ylabel(fftAxes,'FFT'); - -%legend -legendPosition = getSubplotPosition(2,2,[1 1 4],[1 1],0,.2); -%panel with position identical to the legend axes so that we can reposition the EV checkboxes -hPanel = uipanel(fignum,'position',legendPosition,'backgroundcolor',get(fignum,'color'),'bordertype','none'); -hSubtractFromTseries = uicontrol(fignum,'unit','normalized','style','check','position',[.1 .47 .9 .03],... - 'string','Subtract unchecked EVs from actual timeseries','value',1); -set(hSubtractFromTseries,'callback',{@plotModelTSeries,hActualTSeries,actualTSeries,hModelTSeries,d.scm,ehdr,d.emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT}); - -lhandle = legend(h,legendString,'position',legendPosition); -set(lhandle,'Interpreter','none','box','off'); -set(hPanel,'resizeFcn',{@resizePanel,lhandle}); - -for iEV = 1:nEVs - thisPosition = get(findobj(lhandle,'string',legendString{iEV}),'position')'; - uicontrol(hPanel,'style','check','unit','normalized','position',[thisPosition(1) thisPosition(2) .1 .1],... - 'value',1,'callback',{@plotModelTSeries,hActualTSeries,actualTSeries,hModelTSeries,d.scm,ehdr,d.emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT}); -end - -disppercent(inf); -%delete(handle); %don't delete the button to plot the time-series - - -function resizePanel(handle,eventData,hLegend) - -set(handle,'position',get(hLegend,'position')); - -function plotModelTSeries(handle,eventData,hActualTSeries,actualTSeries,hModelTSeries,scm,ehdr,emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT) -%get which EV are checked (note that children of the uipanel are ordered from bottom to top, so we flipud -whichEVs = get(get(hPanel,'children'),'value'); -if ~isnumeric(whichEVs) - whichEVs = flipud(cell2mat(whichEVs)); -end -whichEVs = logical(whichEVs); -evHdr = ehdr; -evHdr(~whichEVs,:) = 0; -evHdr = reshape(evHdr',[],1); -evHdr(emptyEVcomponents)=[]; -if size(scm,2)==numel(evHdr) - modelTSeries = scm*evHdr; - modelFFT = abs(fft(modelTSeries)); - modelFFT = modelFFT(1:floor(end/2)); - set(hModelTSeries,'Ydata',modelTSeries); - set(hModelFFT,'Ydata',modelFFT) - if get(hSubtractFromTseries,'value') - ehdr(whichEVs,:) = 0; - extendedHdr = reshape(ehdr',[],1); - extendedHdr(emptyEVcomponents)=[]; - actualTSeries = actualTSeries - scm*extendedHdr; - end - set(hActualTSeries,'Ydata',actualTSeries); - actualFFT = abs(fft(actualTSeries)); - actualFFT = actualFFT(1:floor(end/2)); - set(hActualFFT,'Ydata',actualFFT); -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%% function to initialize figure%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function initializeFigure(fignum,numberColors) - -lineWidth = 2; -fontSize = 15; - -%set default values for figure aspect -set(fignum,'DefaultLineLineWidth',lineWidth); -set(fignum,'DefaultAxesFontSize',fontSize); -%set the colors -colors = color2RGB; -colors = colors([7 5 6 8 4 3 2 1]); %remove white and black and re-order -for i_color = 1:length(colors) - colorOrder(i_color,:) = color2RGB(colors{i_color}); -end -if numberColors>size(colorOrder,1) - colorOrder(end+1:numberColors,:) = randomColors(numberColors-size(colorOrder,1)); -end -colorOrder = colorOrder(1:numberColors,:); - - -set(fignum,'DefaultAxesColorOrder',colorOrder); -%for bars, need to set the colormap -set(fignum,'colormap',colorOrder); - -% turn off menu/title etc. -set(fignum,'NumberTitle','off'); - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%% function to plot ehdr %%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function [h,hSte] = plotEhdr(hAxes,time,ehdr,ehdrSte,lineSymbol,drawSymbols, steOnly) - -colorOrder = get(hAxes,'colorOrder'); -% whether to plot the line inbetween points or not -if ieNotDefined('lineSymbol'),lineSymbol = '-';,end -if ieNotDefined('drawSymbols'),drawSymbols = 1;,end -if ieNotDefined('steOnly'),steOnly = 0;,end - -% and display ehdr -if steOnly - h=[]; -else - h=plot(hAxes,time,ehdr,lineSymbol); - if drawSymbols - for iEv = 1:size(ehdr,2) - set(h(iEv),'Marker',getsymbol(iEv),'MarkerSize',8,'MarkerEdgeColor','k','MarkerFaceColor',colorOrder(iEv,:)); - end - end -end - -if ~ieNotDefined('ehdrSte') - hold on - %if ~ishold(hAxes),hold(hAxes);end; - hSte=errorbar(hAxes,repmat(time',1,size(ehdr,2)),ehdr,ehdrSte,ehdrSte,'lineStyle','none')'; -end - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%% function to plot contrasts %%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function [h hSte]= plotBetas(hAxes,econt,econtste, steOnly) - -colorOrder = get(hAxes,'colorOrder'); -if ieNotDefined('steOnly'),steOnly = 0;end - -% % display econt -% if size(econt,1)==1 -% econt = econt'; -% econtste = econtste'; -% end -% -if size(econt,2)==1 - set(hAxes,'nextPlot','add'); - h=zeros(size(econt,1),1); - 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')); - set(hAxes,'xTickLabel',{}) - set(hAxes,'xTick',[]) -else - h = bar(hAxes,econt','grouped','edgecolor','none'); - for iBar = 1:size(econt,1) - set(h(iBar),'faceColor',colorOrder(iBar,:)); - end - set(hAxes,'xtick',1:size(econt,2)) - xlabel('EV components'); -end -if steOnly - set(h,'visible','off'); -end - -if ~ieNotDefined('econtste') - - - %hSte = errorbar(hAxes,(1:size(econt,1))', econt, econtste, 'k','lineStyle','none'); - %hSte = errorbar(hAxes, [1 2; 1 2; 1 2],econt, econtste, 'k','lineStyle','none'); - -ctrs = 1:length(econt); -data = econt; -%figure -hBar = bar(ctrs, data); -ctr = []; -ydt = []; -if length(hBar) > 1 - for k1 = 1:size(data,2) - ctr(k1,:) = bsxfun(@plus, hBar(1).XData, [hBar(k1).XOffset]'); - ydt(k1,:) = hBar(k1).YData; - end -else - k1 = 1; - ctr(k1,:) = bsxfun(@plus, hBar(1).XData, [hBar(k1).XOffset]'); - ydt(k1,:) = hBar(k1).YData; - -end -%hold on -hSte = errorbar( ctr', ydt', econtste, '.r'); -%hold off -hSte = hSte(1); - - -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%% function to make scale edit boxes %%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function makeScaleEditButton(fignum,axisHandle,minMaxData) - -pos = get(axisHandle,'position'); -if ieNotDefined('minMaxData') - minMaxData = get(axisHandle,'YLim'); -else - minMaxData(1) = minMaxData(1)-.02*diff(minMaxData); - minMaxData(2) = minMaxData(2)+.02*diff(minMaxData); -end -minMaxData(1) = min(minMaxData(1),0); -set(axisHandle,'YLim',minMaxData); -uicontrol(fignum,'style','edit','units','normalized',... - 'position',[pos(1)+pos(3) pos(2)+.6*pos(4) .03 .03],... - 'string',num2str(minMaxData(2)),'callback',{@changeScale,axisHandle,'max'}); -uicontrol(fignum,'style','edit','units','normalized',... - 'position',[pos(1)+pos(3) pos(2)+.4*pos(4) .03 .03 ],... - 'string',num2str(minMaxData(1)),'callback',{@changeScale,axisHandle,'min'}); -set(axisHandle,'YLimMode','manual'); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%% function to make lineseries visible %%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function makeVisible(handle,eventdata,hAxes) - -switch(get(handle,'style')) - case 'pushbutton' - string = get(handle,'String'); - if strcmp(get(hAxes,'visible'),'off') - set(hAxes,'visible','on'); - set(handle,'String',['Hide' string(5:end)]); - set(handle,'userdata','visible'); - else - set(hAxes,'visible','off'); - set(handle,'String',['Show' string(5:end)]); - set(handle,'userdata','invisible'); - end - - case 'popupmenu' - set(hAxes,'visible','off') - handleNum = get(handle,'value'); - if handleNum ~= length(get(handle,'string')); - set(hAxes(:,:,handleNum),'visible','on'); - end -end - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%% changeScale %%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function changeScale(handle,eventData,axisHandle,whichScale) - -axes(axisHandle); -scale = axis; -switch(whichScale) - case 'min' - scale(3) = str2num(get(handle,'String')); - case 'max' - scale(4) = str2num(get(handle,'String')); -end -axis(scale); \ No newline at end of file diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m index 5a48c1ece..9e47182d8 100755 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m @@ -16,10 +16,16 @@ sampleDelay=sampleDuration/2; end -if sampleDuration == 1000 +% 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% [ma] magic fix +end if ieNotDefined('params') From 499aaeb61467ae588607e83b696875e20fa6540c Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Tue, 24 Mar 2020 16:31:50 +0000 Subject: [PATCH 051/254] resetting --- mrAlign/regHistogram.mexmaci64 | Bin 8892 -> 12924 bytes mrUtilities/ImageProcessing/corrDn.mexmaci64 | Bin 38708 -> 30192 bytes .../ImageProcessing/myCinterp3.mexmaci64 | Bin 8728 -> 8648 bytes mrUtilities/ImageProcessing/upConv.mexmaci64 | Bin 42804 -> 30192 bytes .../mrFlatMesh/assignToNearest.mexmaci64 | Bin 8832 -> 8752 bytes mrUtilities/mrFlatMesh/dijkstra.mexmaci64 | Bin 20120 -> 19704 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/mrAlign/regHistogram.mexmaci64 b/mrAlign/regHistogram.mexmaci64 index 2598c5f4ee008d7d6a4cbce7be2c29e5d58fa1d2..01c1ff1122bfe10ac0132c627f23cf851ca7d051 100755 GIT binary patch literal 12924 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 diff --git a/mrUtilities/ImageProcessing/corrDn.mexmaci64 b/mrUtilities/ImageProcessing/corrDn.mexmaci64 index b332253e460efa38475fc1b6c197bb0b2a68f4f1..24de84c52651ba8aead05b36f16f477348d400a5 100755 GIT binary patch literal 30192 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∋hdD^KWTTA4Uz)H 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 u3j0*0fo&oqx)CCX*=>Ujg5(7nn>nxYFlJ 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 Mx(!kn-yE_30zzxhh5!Hn delta 1806 zcmZuyZ%k8H6u(Z>P{n@-vRmRX8v!f=$bV3wsU zgt&%aV0Rzb-}qr4jL9-G_5r_)DvWk!bA`=K_m?bjdCZ~mwne6BcTV5KZDu#Q_nhDF z{La1i-1~YPuN-!*{^Iuhy>vo210h6!PN6rOhE`W_rXkN3+eijrXy-8wZDi<;a$C@< zfqc!7;Lu#TZBQpGbaIeA&Xupatg|^fi`c+yLLNKK*=;xV=!cvZzR9jY&U?5AXl~6A z#C$5J$6#bNd?{yOukbrKft}?yZ8>@@r0dXly)NmX^baA3nvbPn6WBRElS^Zl_y*Qv zaAxH28qcw_hF!Tk0U&|9joS&q;$L+TGRUmP>3Nb%|_voCFiv> zGv2Ztkx92@v2WF#4WLUconeMEHIt%cObE+M4~R~{xJqjIG`oR)mcBO20t9a1et>W$ z_Imn;+>JouY*D**Nt<*WALWE`ZnRG9=2Ra*3^N^P}@?3=0aEx006K#K)GK!NC3zt^2dzXOM^BdSD28{2L! z%6*3;q*bDue`%$K33}orK(&&JS<~#exybzNGQ7gxHy4FB>f+xuaW+C1-F!`h0*Yzz zB(0x;{UlV{_%p2Io(67;*3Sc{0QVoeP=bL1J+KXJq$(=+za%Jy0IDOOfG;X*Q~nhAIf|%>;wf+!KA!4GjMhh`-X&9) z6-c{s9Z2%&(qYNlGyz+j$K!XMHkb=gvxfF-=xGi4H3a8_$`cyeuA#9M`tR&> zYX~VPgM-h=k*l%vw>X}}(g<;;H%%l9hT*$U7QC4n-%gG1rp8k+)(kup03#hp76jO7 z>rU`Tf3*I}?Zy|b6xPGuslP|)!yyw3j5#Tf@O3=lMv#QZa@uX;9E9iK=byFyV?8OZ z+f%wdquYxlPApuNQ(Bu{k+gQP=Tx!3!=zq=|J ztnEB{uscM6%h9;HV4ywJiiF*^m-RTZ!zE5cg*Hg(-tQ09B62RHx*$NkaX2Tu?EJp2 zc3&uPteq6M2HN~Ve@9m!=x=%Mg}{scU?=ih4WtK)orM!7Bbf=~ESymo{Cv2E<^KVo z2<4E*#|i;<5H#!6;zI`zb*YB$*}rk6hJn&r(*xN50LD)U#70f6U5k;h#~ds7;G2#Y jf-w_x_znpCl@U9^n(x<(;D1VHK1V56!iF7A%q8S+$^*}; diff --git a/mrUtilities/ImageProcessing/upConv.mexmaci64 b/mrUtilities/ImageProcessing/upConv.mexmaci64 index 2abd6da3f57e88f32e81d84311fd611ff28d9e2b..bb2974a0f305fbb29fcdb4d0d6af82ebce38e8f6 100755 GIT binary patch literal 30192 zcmeHweRx#Wx$g`a5iPodwJjdCj0%-GL1ola89&YjfdENN`Lu|w))`R@SRny)?(bb| z?fD{n^qkW_?tR!#X7*lduf5j${l4p6YfpBa{P43&Ef#B*#bTL;&*$)IzrMrlfRz2|bgEul-Tc z@EzHn${@>GkHW&@N?*m=b*p8I%>Jf5CT?tyNK%=2U>=!$7Z#SRDO^=iR8}HdWcJta zgs5L4`=fGdf8gtKvu`>V7M8B{l~fd!I={-RGy6MKC|YNWlZDE%YhH7utT#`I{uYRu z%>Kq?e8n{XBNZwv%+Fi&pswYaI=fyzVInHaW3n@X3*=551oKS#JZ-Uj1?Oz&?jbZF z`|vP6bX+Ar^c>-oiWiD$M^&ZhCsB#7b$R&0v9Q8kpdSf@c$JB z)W!Ls>mT?y9#qMZ{@OEVB z&nmCztxRu|m7BBlji}ko^mcc#AD=**b0pgE0MYe1qNY0-cC#r;{ZoX95#&|If@n2b z7~VF`2($x_2Ik#_QjT|vW_ViFP{E8i?_WJJ;lhaZ^AUO*-i?O;SrGT=H!T)tI69N} z&|&zupzb44XXAC74Sy@@J`{Ch3+dT*!{3Iw!=mm@s`GC`#RsU+*h%ykK^-bSMn&{h zx;9=T`nf~@aMgo+Kr1>AhLzxDh)31qy;k4l$MqJg61)RtjUD>8X`o(Lbe3j4RCnkz zEwsjJ=q)H`=`AS7!%Xi~l~=ar7mww$6Vc@~0+FUisb4;oAZ*c{;oykU{1Lj*Sjh`I z`#`&=WRszHy4kW?Fjo0_x8@4q6rR?t1m6J@`Ht!R)z)cQeyw&qOaIu^MhoGj#CE(~7&<8vCQh zHq4&SY_p@EAdWTk7lL8mWo{!-8+}g4;1J~yoxk53!EjHgOVwrSlWO5Wk1|VxUh!`= z9@bpIseT4V(1Y((AJJI9#`>HCAkt}kK2c`X;%VqHKZUMV0C#Q3c4fTFT|DC6sJaI8 zjH`#;?CKu>A7yX;S7Y6u0bs+k3DQU0)7aZ?LvH}09Lp4p=A+eSH}J`YaJYf)Ttn|5 zRA)*&?IfPSa{=xF=jpRizXk1~s{5m!>nxU77KukfN=`xjAVvp(A}C$I|I^f)?~0rM zCU3rmn*ga_e}Pa0w!Oq^h(;QZ>#qYc5DZA=9z3tHKcoK}(1j4)-}5>W-78RsMte~a zJ#{}I`xEu=ZO`#`82aW=LAKDQ6M@YxXlw9Sr{nS1^=Oi3RA)ymc(@(_{}?*v4tXJx zoN{jCH$=eSDFBeS8$rjDn31O6o{Gmk#=4pL?4xKO+MuuClTZl&v>H+neF}ASuU4EP z@zL+$(yhF**IjpNif=j^uA+vjY*>u*F(rHMF-@7bLt~y+Jve~!@@dLT-N%%9y+Nqb zX5}T)g8F+gIG$fb&s*_aEC&?;cdjS2Zf^80)DSp7hOVGhgjyZ=2H_L1M91U>0rWnu z(*W8^P20_;!?*wh(?|jU!7zhlxVE7tcbf77!4!7s9k75>HJ44{l$xqE#ZD45PtGTm z_!o2n>pq*VH;U_-$!q^~x_(B~6G?tX5ZsC~)jurGWGSNy#5L15Meo2RU;;3nO)BdL zEcXD27dU196+@$&4b;thDk$>;DpuX9%&I4mvw≦>y+Nf=KiM>d^ca2t%|3&FS7w zaV7!{;4+Os^Qd97Z1_u@L5-RCJUk8LI6$m>KX?EP(}N!hRt1+>EtdFpHNJx_+oG|u zj@ZTt!K&y#<8hL}$06>-yJ%2U1l?xE3fba*RK#w_R|~=Sh^+mu5M?Sal8^mBmgmX) z)hNfx@ReeJsQp#)z729tlhc2uDX!X6bp)Z(+RqvCl$ii$RB@gY?N#VBj3(>JN28I%`cQR3Un#*z_|>ebm+ z%@Nhv5w##5&SQhR^2*W0>|=M`2TQo`*BlXzy$`cGHy-YKf0mmCHbMS17SXKl!{-g3 zba?HWHRAAQ=M~3?#~u0t)e$yyyGw6V7U^w|Tfdu4b=UPxQ(b%f@9M!Yx*SA+ zW5J;`^ktFJOjH>9pbP5Jv=ujvK=vx-v5{b0X;?bNVsQo9sHimWuu{R!3r+uwhPvyM zRm$9LH6@zi9n@J=cOKwUpu2hk3*CrG@`}6JXByj=Z%pq_kq>r^jm7&YZaPKKcB!ri z+}1Hw$+ivS`*BlY2~=F}*7*Ff*=i1~f|Z zw;`cMV7@glD3NHshibfoF7E=RX*3<7Yiub9Re%Nwgnp8OkS`UXT;SOgcvNE}8XIDt z0c>ETo`84LHv(+nHk5G3_2>kF=D!EOp9=iE9KY@q{8ke-p7~bKpb;2!1r{jH^H2j; zZ;Z~mkKC%eu6IdFAdyD3J13=Do6>X#6v5=*fmC#%2!wCvIr!%;?sFFp$GbSky5fD{ zA7|SrXWQGcX8<4Drd!`v<2|}-cfgs38u$k66xIseG$A+`ntqRFEQ1ie?;K&CUd=TU zcpc0}RNI?Z+@rH0khzNui}>h5=^OhF07)f>61o&JoCb|a=r*O9M1>@(=_Onkos3=hiLn& zb3>KZ{(SH)2j1tC#`h2#nUJMOBlCov1Db0%@VdtKYizIK55oXJ9?xO`I9wMhpGlZr zjBzawNUp_Mpc8C0;i(AONIN=7I|i5@A$S&>@I?M5@T4(NYHUL(=os55TxtZSCmLU1 zv2>s^9;*WRAnbyFGN16M%64n;jYuf4-&0OO_(;ttS%Ap(Sp;W@@dB`+fm}=x9YgSy znD|)VM+9r=x#T9ZRmTuHDMy5+h;1wa_i2rg%bcq^x(q$rgQMqd%+ z2Do%K+-4)6nW!+l5gN5=5j2k*F9dZBJ@A;CYuRR{$-qM(V=Ea_a1!y~^zNE9(7y=u z&zTx8X|Dc&24AVMgKWYRn%vNn!=^(lI3UTss4|lC(f*6m}nJc&6aKeShfQ>vR@_dk%!b zAI$LrJ?`Ru7(_Pg30>W`UmXRKty40!X_r{ z!c0PlOYc`Xi*mE{2-CM@!ml(>#ckj~VTKb*+pmP)P59`^q*etg zG;88?Op#;o+jbrJOe}}b>C{{UfgW~}jT`<>?r|cfN?pC@O!}RN-w7aIj`8SS7u4v; zq(;qpQ|bm-pw|d=5EPFHD0(xYNb1|aAD*9^O3?5$*bsXV1jB6cO>Fl-CTYg)rnh?) z6idtwTUrwS4pYM&YC#@5y*QusAcanIEo5=CnYYn70(XU={bA;>ce79xy2re6qArka zRkUu0-l*11SXD(IW@C;a__G}EOr$lT%AtZOW`+t`w`>+9U2!6dJz-UzgI9dk8RnhP z^+rrG;UX7?Y)IH;tHa|cXe_vdL@IP=cJ$*JNWKhzhE&NWcU@qU@ZFlh8z}W}KtwUJ zq0iJD-I^oJyfDhSaKDB=UvnJO9AnJe%guC)n4GpSZ;RpWb$J`%dK)qOLk10~D$O}? zf@p#oJ`H2nVggzQB(PW`sopqeF*?bFy1 znUjoD20ZNYwrGZEYVf8?^QZ6$hK>wVL}~sRdFPxLi9DEhXfAIrM(R|WPLjRQ8<8tS zF23H{4XtCy{n7jofK9msPqCpKFo5l*D5D)wfeBIyEXW80*ffwdm)`_PY1+V9?Ww0p zgYJkS^G;J^&3asSeOlutWez9eBh3|NT}bH$<_I&C7Qgn!`&_%oxxc|@Q-5L+cy-OX zQ-?XjvEZW#Ne)wHh#W@O zr+n^?Im~f_ZX{)XuDQC|@CC!S&Cu%!L`T%xM?iKFfS9y_2?;PcjEw|i&00fWVAUHS z2rlnNrRe~i4WGAz7uRwDY*1NWd>f)S!H7(uK(c*uMo5vE1*B=9>gP3fG{@6I%sg)& zU63#^N)vKbQ*QWMK%@aqm;^O$HvQYV`ISiz%ws2`2e|bFK#hG!HU=$dyfdkWR{ub) z`q7H#>ivUp_>Hy%R%&Qbs>)14ofN!Qn@AmZjwH~sxBPYZ^i7Tr_(yY?*urz-gH|fF zvZEEtq||EJvlJVMaTZr+NONP&%{;6L$MqM4DNvfSV1>Cs!EN~27f7tD_dy!bfMgp> z2gn?K3d2|PKc$G*LlK}MQofbY_W;YPbK@WLqD_htDj^<23!Y92sW%-`ER2>Q5->br zNF!OjaMG-#OEO7eDniw-V%CzdD8P%PQs70Z_Nl@Hdv04ylcIzc1Kf=H!#VjIv*Q6_ z0W|~C#Z91T0MC%FGmIdYE^d@-p5aDMYK^dL;@hP6h1t7Islbxfq(%>-Q~=Op`IH5g zO-Q~IMn~92G`oeG%|SEB2Fw)W3#}R(%h6kaZjfTOvzh!559l%se+te4!{1>lRoJu- zCNnM!U$XC6d~uzuCt~UhY#~j+#LB@GR{kNv=L^7xMn1d~Y^z6R(i@H=?+o7>q_rvC zaU@gLLR6nFaT)SfMk<8fq?8>4qBZxX$yeY;%%9FFPf}Ij_xb|^=ThkBg_>A7xKLSW zHuuOnVRsO@eh81yR}o+7n8YJuQXi!a3Mv~!KH&|_Mure)bwH^R4+K1&%JYZk%GRA!pd(b*pu!t;TcoAoH{!q%u6&JgTBbJiZs3SSBx|ZM9HbvjmG_Gc5i$NlXsOu;z6{ zGTA(4LnLDe8L#J<%(YVB)<}^8k7i6fzfO4^4~3JBT;x3>oa9=ZOH5%L5d@JE?@18m zaU%tA`~iWv(mcpx5X74Byq8lhC+9^n0cjEC6y65uX+)IXTq~A5w=fS@ld*&X9(;?h ztRSLvC+(>SDQTcIKD18)mf!;iN@*f0o4nw5!BeZVL$I^Nk1j-)&M=r$^U&XbA7*6v zPl6x60M3Xk=ZeTO8<_COk~A+RvII>?V4%$alf#H?8~I@pT zNn{*{M40(I4ow#44uEpk)Io8r9v&djjG#qnx)VVL7ZKd10HOL1F$%d-L{AA(mM*iW zg7lu?z>iaun&!ZzGM&Ld>BAB0$}xX!@=xdze7Gb{wED*q9j@>R{m78Q5H(4&2GR@Ff@M-egaudve+T&aWJWXBH%y=d z=i}!@hW=;$h9D(R(Mcu%WG7E2pyBfw?hAq-J`Yfm3YCG53{xaeusDk4(-hNoHe{AT zkq8;geMr#~>kt1~^64jZpgu4P4+78FClm%!tcoy;CO8B(59BY*KnoK^Dh94V%ea2g z-13k!HJ-njg2eON-%Nc% z5n_fv7fbR8nBXcs2m_arC_4UZMop%Ihp7GxHRpj-QpwASE9j7%r8Im<(I~GVf;wHJ zCNMjnew^tac^C!O!0@J+x*s9CW7We}4s%NAL=Gl)Y&v8$o5S`n?Fr`a-c`V7x6AHNLx2CMR~rP zqBLyI;Jb_b3$fR2gUT~Y!Q<6)&G-?SXY5ubq{8lam*&C@*7l`5W}D%Em*Y>vnUM?a z!oyy}lx~IANe7iC#{09$rramv|oyR_7CseP8Qi58jD2^>6 z$}9WpCM=LY0;X;pR|Juf69rm~~h?kC%`2yg3}5KWc5 z;t>cgb`0*$V+C75rf%%Xpr!j^|0wZSwT`rfq! z&^Ql`V;4*H<)MXfvCnpha+sK1+Yk%}HpO=dl6t5}8)b9RECRp3NvDOmo2c5`3d2iu zvJMT8Bdd-6l!j9X*g}s|+%D>=bA%0B^xz2cl(bR^7m>n4Me{(vrY-Q}+RO6Z^HCL6V*oz&fnE4#O4OkWU=I{Ta4F|Ic7RTV2bxROhq( zGD^~nvLQg^?801RoIrEzK$z?7XMs_NK5FO)akr?ht+cnV`6-x7ga$5uSa}X$;yYYF zr8bZYoa05@3eQXUox>f5N+C9tKtN(yKyc#>Dvk440nzL+F!&Q!_6>Yjy>kh7; zs%u~1DU3&CVh1%ACt#f2DzemUFQ*y8|Gn5mWy<$BJBsSAiCL`Mc{08Ww87r15$9NZ zC)RZw5g6;ud7-OEH7lIXPWMJQQY@h^v0;U6#*~^a!$0cscN+R8pdvBxmFlh?)vF~8 z$O4}Oh*d4@KQ?}gmb0uC+q512A*>j+D~+cS$7WCxG@AhHbG)F}DKv*S8zPdiNR~c| z#68n{llpegvPfVp#LV!+-*Mux9^Lt|>gdr}l+&+UcfJpDo|At2C!ybv>tH7Nx+VEM zoO}aZ5PW5>WxdsFI0(qf3lK2oG0s8(0t7w=fhT)&;q;44GCuSrr>qs~hsEzQQ13HP z?+npYg6j}f#+KrXjgj7u4Ufktp$KDxVwT>@?L+JULbeUpItOA~D0b5~!J0VZakA6y z@mM3(aZ6)`rHTCrMJ}(xzL|-NzhD;xI|Ng^L?u zz~qFb>4V+lmJUHgFVm%^tAGuI5^PfA+k~y7HX3fs9>jm9wvcv#gtGfpCL!!mavHu^2fS0=QaoC zLog0|w4A_@V@MC#Y=>7%dHfy4V(PD_U`IX|`#!{?WMkY%9Yd@?9zL;$bt~>Y*4=Pq zs28>`Qa2ulV%t83>1$OM^~0-KyL8tvQuN(q_h9z6EA@xq*x?_1KNF^eK-mu~>F7TE zBNz>672qQahzSU3ieo2CAZ!oNOc=p-)zycTywbb^aAN#P%pNq^Sk?gr?NhBE>yAIc z^tDl{x)W~3@QlLr(aRlMAh@Gs9N7?(FIvcF74oFJVhQ^P+hYW_5Ga!wK;fk>K!5NK z$klJ0#r|Q%5-v0DtHQQ`>dJ)8JARIFC8N%1Kho^)xyElH0y#>^__ZmG@kxvyNChhc z+L8SO?O?|ZNDZu??szw(O^qXohJ8|I{G{DeHb@2#YuDIA$%#h>@Dw+IV?;~1R&I>W zMZYxrC*cQ-M=}i{_9a0LeNVyqM5(yTdFiLX^Hy7g^t3Y%77xGh}X?%#hiWIulG{p(EVfzVMJgG!&CuZR()CM8;IthW?=--S|~5BH2n`~uCRh3 zy5d3fUJ9ZaHVz*ko4%Kj=|G@_xSPjzZ2$1M^2!kOz4}Y}Y|J@;_hpiiwd&gLKY~CG zaa{cv#znlMIl?ZyN%0^CK#V+)!Vh_0LcnfRi zwC!64EU+P-Ee?ujktpm?>hB_>2|EdQj~%9`s#j3A$TGpg_wu2-;19*HtWR_HIr?CP zu=g1EcxaWt7I5ydJi<69Rn&nZgygag=USqH)7ioftEcb13zq{ zAgAQq79T)}Y2ELd09^$R_y!#hs_}vNP7Gf2awGXwiFt4&DYqhj=lQUq{d6R|L~T*I3P#7;QDl;5XjyKzPPY zQor6t5{^we>Z43=#d`{vdD2@McwgLBh2WpI#eD)MJNuYln4xyW8{`e0 z-4JtH4sAtL!)J7xl%}shg86HwqdGf5um90ojyV|BJA`TxrC={PZBH_%9D`(70!46n zVnVyDO-ftbf!7x)%qKf`gzOkB$0P!0J((_Vqh!XqrM$s}(1sG4pb^P@-!}A7F6wvC zrWcn!$ek@l#073Vl{Q+;aMQrW-W`@5ZJ6VejX&vLU*n}YjuZ4IixY3;<^W+ zKTn4t{!X?Jz}OPq+wn(YodXWQ{8)+Do9U(NjjHvi9IEB;EqTRVlp({=m``B&atUqC zn!(d#m{rjGOYw;A+N0Dz10>kE7&uVR2ljUd2N-5L+f~JlMX5HW;qRc0;3=<0oueBY zt%C#kp?N(a+ZVah%eM965z!w0W~pvb8u;Ek9Z%_sdoTP%$Tk&;^cLQJ1^|N>(+RXN zK72cPY#D%o&I3PuAZj_JzEm#TOsH$tW6T?lJr84teNyb0<0w{|DTRc$vHFyTXF*V; zzo?Ac4iI3gJ9KZ48nRtR<)g?j3E$ppK66Z2da2TokLQqdg5WBPh8;s_Z#7}WDh!~@ zzLcO8#1w16YJ4kE5ijP)@wZKPeS~+~;r`$VI4R*-1TB@|Bfx1gXm6CzY6w-_U5ELu~+E+2(o&b<7n(3QQ!~^$;S^HXbYl)jj2|amD>AWPW0rtSBO>>4WMi&D z6*lbfY)n0mB(uD%9vRcXQ)%{?!YFQnIreaD zI~{mKgVqnuYL%T~>q!_3WFU)2tYc)B5j#UjKq|f-=@)+{{TIlJxSQEj5 zg!0NZ%;xZB5LQN-{Lo=BohzcNuk`5rFghjzOl&1d!0#F5CATj6nY3m~@RxOTD zV<){Bf609O=pJ+|bni9Xn7|1NhwubyToZwU`$Kn5rFYlZF_oQ`@PdE5v5FKu_a1g6 z)!oHAVYT|*Mm82nc0h%*r>#fLc{%KMYQs|6Dl%nhoqWD|$$DW3*_ zBZv3$nWg_3fl5#H-FPX(89sa!{T2&e;q1q|;Y(n9yL>mGYlXV5d=C#^QGNwUH#NXb zYU6(k*hSFIG3Ci^)qgK3Q47Lg0h^y_{WRW{s9C6?x2H(W#0lTF|hpPn4& z)045-GpI<7(rZv|L41rzf_@_giOVqacOo|Sz|HDg`gM9~(jr&6|=F-4l7=lA_u5<7&oWXuOR-_hh7c zmK;F#5AmV}5iEL={OrzldkhrnvqET~8O+uhF$dT)#}$fB6lE zXDeMt#WipLrntV3>W9R&gRXnUHSfPeT>pgX+i*>IR+K#LuPCXs7y0a^B}J7!`|b9k ziq)03(mguzJn92FRoTn@xVxgnUSwZazV4RF^+gqxCH9r&{uQMq_Eq@RN}s)~2*3PV zB_3UvmtUYilJ?;F9@Di4^u=jU6xqdZ$)d}}1^UBj_s%kMdb{HCb-tpt>+HVol-o;6 zOUg>t`6Oz*-M5QM{lMMCY;{FR5q=xi?psr|&i+hEML8c=eWc(523}l{*6yqWi+1N9 zcyYl)7l?0ayR!}p`k~#qhD`+wct4WCinEaW%znSK*0%-}Ut3nR8sf1QB(Gdsz7ABs zzi8dMa-Y4Zw6we!WVS=<{H3PQSn|rKapm3iC+4iQ&sn+ba?4_0QHAeryFIn0WIa`+ z)|6+|JOX}ZbR^`M!&ABT8ItvNEA9UEl|^OiOYuX+#9Y)>l-Sp zSXEk5?89#?^8>%)T(WK@YS%+ZEN5V|uB5o6va+b+yLS9E>H6{tx`K`rmsl3AErn1e z5SwSW07hV*sDT-=E_h zyX^~$)|Qs6wEN1F^d`7~W<}9=Z@@Dvp&7Rjx_)1Yg=F2t_jb$E5_7yNCyo+XK_$JS zq8!NFJg4$z``SvF0w2_+#J&zc&}=VX1>*o2Tf5R;vT}9FEl{JCr6mA-DyV#5lg<%+2`#2uFAEE^IvmL{8xe$J`x(#EMFD&sg`|Iy!y{%=O^jvvYXsfT3$ zbU$mRlaBnu=l9whknY0A4&Kp)mI_4HsUQ;1`QW0(a*#9 zsY^OKiG6e=`=Mb0&$T$E%R7mz2@fvmsO8`I+Hp_Ks+Tq$$ImaxBRgCeufcEs%kv@p zu00*2^1MKvee&EW&uip)qdY$>&r9W5ljnAMZk6YVJkOJ7^LO<-W%&bn9+&4@c@|Jj z^8JLUtG!E{uaIT?0#RP@U&J~0esR9hEW1ScDzp99MEP2?EZaL}`=JNK{dw~K*dwBR z@%thLE>hqk1ujzHA_Xo|;35SsQs5#5E>hqk1ujzHA_e|mpa3NtkfNA6Q=aYeJXfCQ z%QH=u-yXE;|d0ryV%jJ2EJg=AMDtWGz=LUIhk>`!_yh)zJI3r^-H6qJHvbbe!Og_bPTh@1>T%B<`D8EX$mXGSMIH@hy(KGs*=S<)sNbUcJ8D+|~r`~@tqx^D4`PGbawe36_&m_095RW^7xDQLJ~Q#T z1|Q0KUW*Uqkx6{Mj1OK=w%GBx0iPT3xd|TSS!K}{(myXe^)GAMe7SI zN>=)@_>)mvQnemyA}O_JG&5V!0t)Y+mQ5^$>&nfwnnJ! z|3b|t@+$9NgHTwF$m H7{~twBOmg| literal 42804 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 ubIx}%)#O8^;mk$QwCV+msv^{zam(i(`FQR8 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 EAMB5jkN^Mx diff --git a/mrUtilities/mrFlatMesh/dijkstra.mexmaci64 b/mrUtilities/mrFlatMesh/dijkstra.mexmaci64 index 31cb91f724804a9fd7d9787df2436829075b8951..2979c628268c641b3631c5d0b1f4b5483e9ed8dc 100755 GIT binary patch delta 5550 zcma)AeNw3V+N%TY$dUASdY-4CKEoqL)3Z6ikCSCob2V-hU^WhRS8axMWOx@o5-YjeRqo)Hi z_x|p^Gjrd}-23M3iPOUUlS2GozrXb`k`O^n2+=`}hdQY1h+i*6cKmorj3W*(l$A3S z>gYJRF6Qha1?*J_IomP3Zd$@(1)bu;gvD&7ur}^I7w2;^Vimg_k;Dud4g0N-97nPg zRSvg6tdTA53x=2+u6@Y0Ic$MyU0n7O1rBg2Vr5hZjCxA7T!>_sRVxHNo1{(>rm-}2 zW{f^tG1l`wdKRNjWzFhzp`E<}R`Kk#Fl%nUX`6`~0O{g=2Du2a;+sKZ5ZahYQ#$E> zw4#h;Z)oNgB?yFM!w5S;*alP&_{4fb5&)~9A{_z@|D>}J?6ibL0dIqfWg)N7jbuWd z0V|QL0mBT|AxvjBt#0c+Q^z02{+_${Tyv&qzwqYCrC?@&ie1Rm$#O*=x0I-R1spQ; z&?rKGak`L8(38EQHK=W%cECwO~1ebFJD^ef6WZ z>Mk`qrA?h&Z{KRKKTuO)Zvg9rN8w>@q99Ki@gs-5R+%?hVPPP2@V_UQZM8+)HrF`O z#aS{3R*LOzpkYVRc1~oy-PBlFQ->3e^VhUB`rJosh3~)Aup<%kHsOS@v*UwkCbh!? z3S>!RL;aGyHRV_~EGecaukjx6>9ICfx zvUlRv3XSZ3oJm;5R>rTMT$LrJ9DxpmFn9dHsQnOOX6X|3YVF;+8JU8R$d)Ipjw+_o zS6gi$y*q=gieKOw{C6-&TYZgRjC?~|F6nla^o7SkThsQfRC)mRuR)|zDYC_tL7Zt- zVhRKvZTXpwI1{M!k3feqjPoY-=%~~mEYgj94)R^5r%Q%+O-D~YKrIjP;%l4s~F(o6YPKgetZpIaPRy{WLj680|ipd`l%f z&R$4a>)L_dwcN}F_x=Lw2K{?zf5CNJ1E7PP`Z<+@8st92?*^fO1I{F~bk8h3tm-fL zQXte@+!HwbLog`ywfdY>q(NAdTzCjmX-OaYy?rbg2lo?ygu@sIL+qXyYI#?~1NM_SW1~ z;aPTh?&H&9MrKm4GKEyo1}oxbv*x9{tfRr8MQZG^wvLI%Fqc@o_A@!x!h4OA1?Hob z$B{cK>TTIzIx5B#`G~fUt@&m>4nFk zQVF0dwFHALXVB_($H7VQg$?{@94fa{`?mjP34@?~FqpM1K z#H07w(`m`#R}r}Ka8g4-k(RNm0el~cgeTbqOyJL8VfL&I+yqLcK4fp8NcxQC^*N)# ztOCtmm(3380)E-9H5Z&RdjOvg^#lIg@$@;a6==R4q}IIKR?&Jp=SseG+ai4-T@P5$ z)?@WkNx);V2cTK%e-Ao9K->t-m+>WZVGFQFeBzPcLk7q7L|HEOmRgPS%xeQN3ZxwB z6lj|U*xGtaJXQr=Ez*cpddSYFFPqT=ax7|^dM9-Lyc>h;LHcsN)Y$oichN0ygKYNZ z?<34SKWUi{&W+FFU2_ojn@Xp5Lf`OCi77_%K4zytK!fUw5PhHN*zx(f39q8S>(If@ z?S?*7($Urr#Ur1vf6jl(bpg+PWJA($HH5a3KcrFGqKL)jEg+>Wy*;ozxj;9Ad!#p>Y|>;o=}{dJ+YUl6l3;myQJGB#V$Yw@=RAp^n`04% zX|UIvcbWM!V_ntgo2LK*4qO)_{+Zl9OiLvrhu+h(~v zBDX)6+cvrVh1?#M+hcOuF1N>_g}w658U_bVTZ3s=fwor{fpGY1H~`gU$bE*~VdPv_ zki~>PJb>IUk$Vrhw~;%K+=s}WMQ#wePULPN_Y!jVkUNUp8swUh1BMJ^mZ-+Bh2dJ{ z(vYhJE*KPa^W!&@HFM$lR|SR1+l{XaAtzRRB#C~Q5pfS?)W8A31SP>-@uC-3u1jDA@WCtfe;^jTy)MM z#19l#Dw<3q+Xy5(C~y&1?-q#4rY7o6HGJ!6h~Ur=O}3V(9Fauh(-FZviA1(fCJ{!s z4EUmmV2qv(7rZG%yIW6G+3*CHcWR5u1T|J6m8go~a-N}jX)%y=S0_x0np(WLQXMyN!^&Z4=d_d4E>4Fy z)hlL1pQSJ%Jey;#CJsKXKUI-*%Db@|zH7LEz!qRJa@rM=E8_8wk@C z6d~GTQVkA6iD|28yUC$*=yt5%qI8;REG;c`qv)9)lv`uiRs;p)&%6BFjOZVBE3hL{v z4STjVIZ?Jd0c1@Z%J%N9t3X_>;Cg$R)1F_~SiaY8Epyh_G?lJ!WoM&-LpCV1I}02Q zh?_##Tw8_MEsP85OIIj;+Qwy@DnhdMahVmnofwBtAnY86wtB>8gaj8lYHrx-D64P4 zr{+b#N7~p}Tj8v!t0j6h@oz@WorQWR*#@K0c1La7?eGacjbG&x+ErbG8~zM>P#L-+ z{8N;1ss%!3L&Z?Wa){&50ew+`^+$uyNj=LALMOJD)xXrjV_$$|PsEm&5!pF9gUvF|W8c$ltjxT6(s=4;H_UT{Y&Ln5VJf6(NUV_1 z<=otzyJ?!xUA}p`N~mE6taASm?(1q4M5{vuk`pjM$(On~SMhBk<{REq&IC@W$N!7|sHSl}FA zV?3d^v-W7u;dHgLUAM8Zp2OPNMbU*&sZ{iJE-P)A zG4dG$u}Wfm%jr^b7Rct?tTuV#I0D3)+adQPz{31=iFTC2>V ziYLNXrT&>UXVt>!D2!R04pG4|N0U_)_~k6z`7?8G9GjrWK6gy zn30Q<&BET)7grOKM?1Mm)TY+VFDUvEQSHCa)V2Fb&ClsiOJ@O33z;O1BZMDE=EX4D z=!yh8P1V$BVdzrt*fL4bDe3PtS*gjO`OuRlo!|6|r-vQ2K5l;zz z_356f$@g;HT-u(oe5x&ykV;78qe(F6>7|6M7sSshZNMtYpuY6E%liKvjS3yXmcI{u zA+(*+Y^?6Xc3+A{qxAvC6>kB37<^O^6Wcs`(xM%jMGvQE=C$7Ot$0QLd~h}NgiWl$ zCKmA1+hIvo5d6j1AvOK-e9ZckgTM)$S7V&m{ z$b5|>f`5H5()L|63Nu`26~Bo*asyc63C4ccJ;l_vM^D`21)ow7uS9K{p@jNS37oe4 zAk4?G(X>fkkgvgg`0-=&;ipS0Dfm`tVfEaap5wY@ig^L>QbFu!#i4}E$8dm=_mW_X z`1j4h$SJ1v3qCW=p1C!-yyH_)b?(OnpObdZT*#T}iJ4O;)o8BsJB-?T{+8MdzIL7W z80l9tb2ukWnYA=MZ75st%Uy`(eXtf@!S|GoK00fTr*I@1wTdmh!TJc%KER$Ve=iln66=29(HIsId5o zBEu-fQ>YH1T6oHhvwj6RuSuLen#c#Bz$O;J5G_8yZ%MdM(%+z!b7pvUBflEVR&l7j z@GA>neg&MzDh}kIkE}(uuRJV>91dwaK#Lf98@>Sc=aKmmK8+)&0<}HJw;xnvm7^bR zMfkPYVU+qVicFTFz~BTKPO6Ia!scFU>?)ZY9{R{x62;15>ujVfsD zu08oZMytqx3fnp%_|?mS2QvqupzK2fB&+zQ7X(lr1G9iyPd}Df!WawhCa7|bv0kCM^W<@jvAi;?dyF+eo%8e___BYD6_i=S zG1lIbc%KM`=lG5b7>mUY11zmoJgUbP_L;*Wqr4$L$(={6eBP3amgrgO#0U3G1m9P~ zwphe}Mt%luf(JvG)mVLwe(+VRIAT?ZgVcU+re`f2OE`8qh*(yiGi4;+T2fqe@qn-}+;MucaGKcW-RZExvW9 z!HrT-Y6LO05EU&bt20r{C=*jtgtnkc;J&sLo@hB&-VwBlm)!SS#jgeam;Dxh@)?W2 z^fbvo&rkQ_CJDYJa1iqa?q5Rmq@^%S4SA`aU04zAv%qbK!E67%TQL5;r+UGBM^u5O z`3%=KzGAQzco0$LYYlJ6>Op)81f+yGlz5m^kPw_zLt=Gbbap?y{-U%0AeCp7!~@{0 z1j15zUMk;{%J-%6qE!A>Dlbdr$5MG!D*q;xpGxI`R7Rxo3#q&&mH&Vew$ndtJ}9Qj z=_#kS?BHK9ge{$y|qs`vmK+<@A8)G(+;52tV%d)4IMQ&x{9Tlru{AZ2Kz_kBJ(oaURF;A6RV_$RqwyZ1uF(>O7G7TGs?-%ZL{U-8DLHtn>XSA>Z?6TlcuV zZrkJbtu3{#EhJ#v^25e0U~buL-`vo&#o;0Wxiw&K*iviTMJje0=yKD8599aio1OrY zeXXOpWxE5IJN2o^t8^E-9j@&SO|@>vE;QSu&NlCMxOO0{LYBR*wbs7bv9+PeURUd` z+lCc2X|&svK4;q`qkX%h)$DSWH*a0n>c%SPM6k7%)HXKms6*U3kzO-pcorKFcsZ$d zwZm<8HltzRsKL@ykA}dQ!DU}2e|2coR#q3Yb&cA}@v~#2@Ii##qtJ#6v$J=jY_K#} zI%{3cxCBz-aQH++6suaA>f8-Gnn+%KL!-mx*xAzHa%`z}Htce^n$e!GB;GY}_JURL zVb?q@B!kXqv<)k@^muWmrz@fR9%;>sF~c#h z-N10ndtD57GjDE_Fa*3|W_XSfFrHvK%CMK=poG1|$&S&*SUtmo49AYyO@7+!+d1BRL@GNB?y$dd6MhKHxg_$7wBvt|4zhB44#8fMt3lORchIENh$Gi+iw zcHAlfd$Kt%kq;^%4n~Nb({_eq=k+HH$Ik6ZhK)1i4lgi_5f_shUclIX?EFs#JOMbd zUq-3Ge7$3RET9}k3{gC2 zkUl3pYS@1?TxEuL7$FuKKV+E2Qt~;&1uVqfU^o`?)bOy#PKF7wdT;^4!xngF%N_Cz z8(Bo02mKSMhi2r2*CSw96FIGk!>%}dM8a@ovGj5remxHN#9_~yl3{EBeKH$@||>HxlvZ(m0+ddrb}C4^{1|0R0I-+ Date: Wed, 20 May 2020 07:20:42 +0300 Subject: [PATCH 052/254] editOverlayGUImrParams.m: added option to use an anonymous function for the user-defined color map --- mrLoadRet/Edit/editOverlayGUImrParams.m | 44 +++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index 18582b549..20baf8882 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -64,7 +64,7 @@ function editOverlayGUImrParams(viewNum) % 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'}; @@ -272,26 +272,34 @@ function mrCmapCallback(params,viewNum) % 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 From 4ddf99426ad33d820376d9ba28ee6fad50bb7e64 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 20 May 2020 07:25:20 +0300 Subject: [PATCH 053/254] editOverlayGUImrParams.m: remove cmapHSV from the list of colormap functions that accept more than one input arguments (it doesn't) --- mrLoadRet/Edit/editOverlayGUImrParams.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index 20baf8882..c807e458b 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -262,7 +262,7 @@ 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)); From 2c9faf0d76edbcc87600438354e0fc08655979c5 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 20 May 2020 07:32:48 +0300 Subject: [PATCH 054/254] editOverlayGUImrParams.m: automatically adds any colormap function added to the colormap folder (mrLoadRet/colormapFunctions) --- mrLoadRet/Edit/editOverlayGUImrParams.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index c807e458b..afb546e86 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -60,6 +60,13 @@ function editOverlayGUImrParams(viewNum) if ~isempty(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'); % set up params dialog paramsInfo = {}; From e4d14307763f85c540791840e908b0bb768501e0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 May 2020 17:30:26 +0300 Subject: [PATCH 055/254] added randomHSV colormap --- mrLoadRet/colormapFunctions/randomHSV.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 mrLoadRet/colormapFunctions/randomHSV.m 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 From 7fb82f31af308d234b0cc46147a318a7038f1c43 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 27 May 2020 08:31:27 +0300 Subject: [PATCH 056/254] mrExport2SR.m, viewGUIPlugin.m: added option to export overlay in scan space (or any base space) instead of current base space; added a menu to the GUI for scan space --- mrLoadRet/File/mrExport2SR.m | 77 +++++++++---------- .../Nottingham/viewGUI/viewGUIPlugin.m | 14 +++- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/mrLoadRet/File/mrExport2SR.m b/mrLoadRet/File/mrExport2SR.m index 3db320617..6e2c68d7a 100644 --- a/mrLoadRet/File/mrExport2SR.m +++ b/mrLoadRet/File/mrExport2SR.m @@ -1,62 +1,55 @@ -function[] = mrExport2SR(viewNum, pathstr) +function[] = 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.datatype = 16; % make sure data are written with 32 bits per value to avoid surprises hdr.bitpix = 32; -hdr.datatype = 16; -hdr.is_analyze = 1; 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; +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 - - - - - - - +%write nifti file +mlrImageSave(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) diff --git a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m index 141325a8c..5699b0141 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'); @@ -303,5 +305,15 @@ 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 + From 0b133729aa9502a454f579edf143945aeac5e13b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 28 May 2020 08:12:12 +0300 Subject: [PATCH 057/254] mlrImage: enabled .nii.gz load/save on Windows --- mrUtilities/File/mlrImage/mlrImageHeaderLoad.m | 12 ++++++++++-- mrUtilities/File/mlrImage/mlrImageHeaderSave.m | 13 +++++++++++-- mrUtilities/File/mlrImage/mlrImageLoad.m | 12 ++++++++++-- mrUtilities/File/mlrImage/mlrImageSave.m | 15 +++++++++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) 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/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 From 2cb12644982831aed40eb75782d1ef5bb40afe00 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 28 May 2020 08:13:51 +0300 Subject: [PATCH 058/254] importTSeries.m: can now import .nii.gz files as scans --- mrLoadRet/File/importTSeries.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/File/importTSeries.m b/mrLoadRet/File/importTSeries.m index 081fd4fee..15e3308c4 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 contains(stripext(filename{iFile}),'.') && ~contains(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 From 9abe97d0946d3eff8e3ceb7b6e4f2842b0fc7b44 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 14 Jul 2020 13:02:24 +0300 Subject: [PATCH 059/254] importROI.m,viewGUIPlugin.m: added option to import ROI(s) from NIFTI file(s. Treid to make importROI scriptable, but did not test. --- mrLoadRet/GUI/mrLoadRetGUI.m | 2 +- .../Nottingham/viewGUI/viewGUIPlugin.m | 18 +- mrLoadRet/ROI/importROI.m | 217 ++++++++++++++---- 3 files changed, 188 insertions(+), 49 deletions(-) diff --git a/mrLoadRet/GUI/mrLoadRetGUI.m b/mrLoadRet/GUI/mrLoadRetGUI.m index 506cb6949..abce4e081 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) diff --git a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m index 5699b0141..29cd45850 100644 --- a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m +++ b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m @@ -203,20 +203,21 @@ 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','Import Freesurfer Label','location','/ROI/Import/'); - mlrAdjustGUI(thisView,'set','Import Freesurfer Label','separator','off'); 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,'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','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','Import Freesurfer Label','label','from Freesurfer label file'); - mlrAdjustGUI(thisView,'set','importROIMenuItem','label','from NIFTI file'); % mlrAdjustGUI(thisView,'set','copyRoiMenuItem','label','Copy selected'); % mlrAdjustGUI(thisView,'set','pasteRoiMenuItem','label','Paste'); mlrAdjustGUI(thisView,'set','editRoiMenu','label','Edit'); @@ -315,5 +316,14 @@ function exportOverlayScanMenuItem_Callback(hObject, eventdata, handles) %not su 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/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 From 8d71f7ec4e7866cba07e5f32fa5e1f87b8a27c6d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 24 Jul 2020 12:03:47 +0300 Subject: [PATCH 060/254] took inversBaseCoordMap.m and applyInverseBaseCoordMap.m out of combineTransformOverlays.m --- .../Plugin/GLM_v2/applyInverseBaseCoordMap.m | 25 ++++ .../Plugin/GLM_v2/combineTransformOverlays.m | 108 ++++-------------- mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m | 83 ++++++++++++++ 3 files changed, 133 insertions(+), 83 deletions(-) create mode 100644 mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m create mode 100644 mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m diff --git a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m new file mode 100644 index 000000000..cbdd050a9 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m @@ -0,0 +1,25 @@ +% function volumeData = applyInverseBaseCoordMap(flat2volumeMap,flatData) +% +% Transforms data from flattened cortical patch space to volume space +% according to the mapping in flat2volumeMap. +% The mapping must first be computed using function inverseBaseCoordMap: +% (e.g. flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) ) +% +% Taken out of combineTransformOverlays.m (22/07/2020) +% + +function volumeData = applyInverseBaseCoordMap(flat2volumeMap,volumeDims,flatData) + +volumeData = zeros(volumeDims); +datapoints=zeros(prod(volumeDims),1); +hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from flat to volume'); +maxInstances = size(flat2volumeMap,2); +for i=1:maxInstances + mrWaitBar( i/maxInstances, hWaitBar); + thisBaseCoordsMap = full(flat2volumeMap(:,i)); + volumeData(logical(thisBaseCoordsMap)) = volumeData(logical(thisBaseCoordsMap)) + flatData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); + datapoints = datapoints+logical(thisBaseCoordsMap); +end +datapoints = reshape(datapoints,volumeDims); +volumeData = volumeData ./datapoints; +mrCloseDlg(hWaitBar); \ No newline at end of file diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index a5ce27a4c..4b8a98a20 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -226,7 +226,9 @@ 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) @@ -486,79 +488,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 - 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) - 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; + if params.baseSpace && ~params.exportToNewGroup && (any(any((base2scan - eye(4))>1e-6)) || baseType > 0) + if viewGet(thisView,'basetype')==1 + baseCoordsOverlay=cell(nScans,1); + for iScan=1:nScans + 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); end end - mrCloseDlg(hWaitBar); + else + keyboard %not implemented end end @@ -662,20 +603,21 @@ 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 + if ~params.exportToNewGroup && params.baseSpace && (any(any((base2scan - eye(4))>1e-6)) || baseType > 0) %put back into scan/overlay space for iScan=1:nScans 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; +% data = zeros(scanDims{iScan}); +% datapoints=zeros(prod(scanDims{iScan}),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{iScan}); +% outputOverlay(iOverlay).data{iScan} = data ./datapoints; + outputOverlay(iOverlay).data{iScan} = applyInverseBaseCoordMap(baseCoordsOverlay{iScan},scanDims{iScan},outputOverlay(iOverlay).data{iScan}); else keyboard %not implemented end diff --git a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m new file mode 100644 index 000000000..e7ab6c784 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m @@ -0,0 +1,83 @@ +% function flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) +% +% Computes a correspondence map between each voxel in a given volume and corresponding +% voxels of a flat map and outputs it as a sparse matrix +% By default this is computed for the base space on which the flat map is 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(flat2volumeMap,flatData) +% +% Taken out of combineTransformOverlays.m (22/07/2020) + + +function flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,xform) + +if ieNotDefined('xform') + xform = eye(4); +end +baseCoordsMap = reshape(baseCoordsMap,numel(baseCoordsMap)/3,3); +overlayCoordsMap = (xform*[baseCoordsMap';ones(1,size(baseCoordsMap,1))])'; +overlayCoordsMap = overlayCoordsMap(:,1:3); +overlayCoordsMap(all(~overlayCoordsMap,2),:)=NaN; +overlayCoordsMap = round(overlayCoordsMap); +overlayCoordsMap(any(overlayCoordsMap>repmat(volumeDims,size(overlayCoordsMap,1),1)|overlayCoordsMap<1,2),:)=NaN; +%convert overlay coordinates to overlay indices for manipulation ease +overlayIndexMap = sub2ind(volumeDims, overlayCoordsMap(:,1), overlayCoordsMap(:,2), overlayCoordsMap(:,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); +% % %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(~isnan(overlayIndexMap))); +%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); +flat2volumeMap = sparse(prod(volumeDims),maxInstances); +%Now for each set of unique overlay indices, find the corresponding base indices +hWaitBar = mrWaitBar(-inf,'(inverseBaseCoordMap) Computing inverse baseCoordMap'); +for i=1:maxInstances + mrWaitBar( i/maxInstances, hWaitBar); + %find set of unique instances of overlay indices + [uniqueOverlayIndices, whichBaseIndices]= unique(overlayIndexMap); + %remove NaNs + whichBaseIndices(isnan(uniqueOverlayIndices))=[]; + uniqueOverlayIndices(isnan(uniqueOverlayIndices))=[]; + %for each overlay voxel found, set the corresponding base index + flat2volumeMap(uniqueOverlayIndices,i)=whichBaseIndices; + %remove instances that were found from the overlay index map before going through the loop again + overlayIndexMap(whichBaseIndices)=NaN; +end +mrCloseDlg(hWaitBar); From 6bed787c6cffee7d98981d89ebae1469bd24f639 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 24 Jul 2020 12:05:04 +0300 Subject: [PATCH 061/254] added overlay combine function keepKlargestConnectedClusters.m --- .../keepKlargestConnectedClusters.m | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/keepKlargestConnectedClusters.m 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 + From e3f9fb7227e4f254787b55a2e39a6916f2982ba2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:31:45 +0300 Subject: [PATCH 062/254] mlrImageParseArgs.m: handles cell arguments and other unknown arguments (which previously caused an infinite loop) --- mrUtilities/File/mlrImage/mlrImageParseArgs.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mrUtilities/File/mlrImage/mlrImageParseArgs.m b/mrUtilities/File/mlrImage/mlrImageParseArgs.m index 4c7e108d2..34d8251a6 100644 --- a/mrUtilities/File/mlrImage/mlrImageParseArgs.m +++ b/mrUtilities/File/mlrImage/mlrImageParseArgs.m @@ -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 From 293fb4d7305d7ee2cef2c7ef026c7075331a8fed Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:34:29 +0300 Subject: [PATCH 063/254] makeEmptyMLRDir.m: fixed issue with subject folders containing spaces --- mrLoadRet/File/makeEmptyMLRDir.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/File/makeEmptyMLRDir.m b/mrLoadRet/File/makeEmptyMLRDir.m index 786f5c09b..f21190447 100644 --- a/mrLoadRet/File/makeEmptyMLRDir.m +++ b/mrLoadRet/File/makeEmptyMLRDir.m @@ -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'); From da2c440f488098eb91b412446738db917a9207dc Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:35:47 +0300 Subject: [PATCH 064/254] mrExport2SR.m: added option to output data and header instead of writing file out --- mrLoadRet/File/mrExport2SR.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/File/mrExport2SR.m b/mrLoadRet/File/mrExport2SR.m index 6e2c68d7a..f338a0b57 100644 --- a/mrLoadRet/File/mrExport2SR.m +++ b/mrLoadRet/File/mrExport2SR.m @@ -1,4 +1,4 @@ -function[] = mrExport2SR(viewNum, pathstr, baseNum) +function [overlayData,hdr] = mrExport2SR(viewNum, pathstr, baseNum) % mrExport2SR.m % % usage: [] = mrExprt2SR(viewNum, pathstr) @@ -51,5 +51,7 @@ niftiFileExtension = '.img'; end -%write nifti file -mlrImageSave(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) +if ~ieNotDefined('pathstr') || nargout>0 + %write nifti file + mlrImageSave(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) +end From 36186c1f7e2d8155e2673ea82b58a40fae698806 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:40:05 +0300 Subject: [PATCH 065/254] applyInverseBaseCoordMap.m: takes NaNs into account, which avoids holes in the resulting volume --- mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m index cbdd050a9..ef66e0dac 100644 --- a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m @@ -11,14 +11,17 @@ function volumeData = applyInverseBaseCoordMap(flat2volumeMap,volumeDims,flatData) volumeData = zeros(volumeDims); -datapoints=zeros(prod(volumeDims),1); -hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from flat to volume'); +datapoints = zeros(volumeDims); +hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from surface to volume'); maxInstances = size(flat2volumeMap,2); for i=1:maxInstances mrWaitBar( i/maxInstances, hWaitBar); thisBaseCoordsMap = full(flat2volumeMap(:,i)); - volumeData(logical(thisBaseCoordsMap)) = volumeData(logical(thisBaseCoordsMap)) + flatData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); - datapoints = datapoints+logical(thisBaseCoordsMap); + newData = flatData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); + indices = find(thisBaseCoordsMap); + notNaN = ~isnan(newData); + volumeData(indices(notNaN)) = volumeData(indices(notNaN)) + newData(notNaN); + datapoints(indices(notNaN)) = datapoints(indices(notNaN)) + 1; end datapoints = reshape(datapoints,volumeDims); volumeData = volumeData ./datapoints; From 23a011521ff96bd875fba6cfffcd7a88d3e3a02b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:42:15 +0300 Subject: [PATCH 066/254] inverseBaseCoordMap.m: changed the way the sparse mapping matrix is created to get rid of a Matlab code analyzer warning message, but did not turn out to improve perfomance + fixed some comments --- mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m index e7ab6c784..46dc86464 100644 --- a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m @@ -12,22 +12,23 @@ % Taken out of combineTransformOverlays.m (22/07/2020) -function flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,xform) +function flat2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,xform) if ieNotDefined('xform') xform = eye(4); end -baseCoordsMap = reshape(baseCoordsMap,numel(baseCoordsMap)/3,3); -overlayCoordsMap = (xform*[baseCoordsMap';ones(1,size(baseCoordsMap,1))])'; -overlayCoordsMap = overlayCoordsMap(:,1:3); -overlayCoordsMap(all(~overlayCoordsMap,2),:)=NaN; -overlayCoordsMap = round(overlayCoordsMap); -overlayCoordsMap(any(overlayCoordsMap>repmat(volumeDims,size(overlayCoordsMap,1),1)|overlayCoordsMap<1,2),:)=NaN; -%convert overlay coordinates to overlay indices for manipulation ease -overlayIndexMap = sub2ind(volumeDims, overlayCoordsMap(:,1), overlayCoordsMap(:,2), overlayCoordsMap(:,3)); +coordsMap = reshape(coordsMap,numel(coordsMap)/3,3); +coordsMap = (xform*[coordsMap';ones(1,size(coordsMap,1))])'; +coordsMap = coordsMap(:,1:3); +coordsMap(all(~coordsMap,2),:)=NaN; +coordsMap = round(coordsMap); +coordsMap(any(coordsMap>repmat(volumeDims,size(coordsMap,1),1)|coordsMap<1,2),:)=NaN; +% convert overlay coordinates to overlay indices for manipulation ease +overlayIndexMap = sub2ind(volumeDims, coordsMap(:,1), coordsMap(:,2), coordsMap(:,3)); +clearvars('coordsMap'); % save memory -%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) +% 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 @@ -54,30 +55,45 @@ % % 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 +% 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(~isnan(overlayIndexMap))); -%find the first instance of each unique index +nBaseVoxels = numel(sortedIndices); +% 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) +% compute the number of instances for each unique overlay index +% (= number of different base indices for each unique overlay index) numberInstances = diff(find([firstInstances;true])); maxInstances = max(numberInstances); -flat2volumeMap = sparse(prod(volumeDims),maxInstances); -%Now for each set of unique overlay indices, find the corresponding base indices -hWaitBar = mrWaitBar(-inf,'(inverseBaseCoordMap) Computing inverse baseCoordMap'); +% Now for each set of unique overlay indices, find the corresponding base indices +hWaitBar = mrWaitBar(-inf,'(inverseBaseCoordMap) Inverting baseCoordMap'); +% flat2volumeMap = sparse(prod(volumeDims),maxInstances); % I used to create the sparse mapping matrix, +% % but supposedly it's faster to first gather the matrix's indices and data and create it later (doesn't +% % make much of a difference though, presumably because I minimized the number of iteratiosnin the loop) +allUniqueOverlayIndices = zeros(nBaseVoxels,1); +allWhichBaseIndices = zeros(nBaseVoxels,1); +allInstances = zeros(nBaseVoxels,1); +n = 0; for i=1:maxInstances mrWaitBar( i/maxInstances, hWaitBar); - %find set of unique instances of overlay indices + % find set of unique instances of overlay indices [uniqueOverlayIndices, whichBaseIndices]= unique(overlayIndexMap); - %remove NaNs + % remove NaNs whichBaseIndices(isnan(uniqueOverlayIndices))=[]; uniqueOverlayIndices(isnan(uniqueOverlayIndices))=[]; - %for each overlay voxel found, set the corresponding base index - flat2volumeMap(uniqueOverlayIndices,i)=whichBaseIndices; - %remove instances that were found from the overlay index map before going through the loop again + nUniqueIndices = length(whichBaseIndices); + % keep aside to fill sparse matrix later + allUniqueOverlayIndices(n+(1:nUniqueIndices)) = uniqueOverlayIndices; + allWhichBaseIndices(n+(1:nUniqueIndices)) = whichBaseIndices; + allInstances(n+(1:nUniqueIndices)) = i; +% % for each overlay voxel found, set the corresponding base index +% flat2volumeMap(uniqueOverlayIndices,i)=whichBaseIndices; + % remove instances that were found from the overlay index map before going through the loop again overlayIndexMap(whichBaseIndices)=NaN; + n = n+nUniqueIndices; end +% fill the sparse matrix +flat2volumeMap = sparse(allUniqueOverlayIndices,allInstances,allWhichBaseIndices,prod(volumeDims),maxInstances); mrCloseDlg(hWaitBar); From 0278aa5ad830a434c7a5493def9f9565282195c3 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:43:19 +0300 Subject: [PATCH 067/254] spatialSmooth.m: fixed help --- .../GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index 306aceae7..073aa4d42 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -1,3 +1,6 @@ +% +% function smoothed = spatialSmooth(overlay,FWHM) +% % 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 $ From c34c29e57bed8a43386ba02ad374c6d12b2670f4 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:45:21 +0300 Subject: [PATCH 068/254] newROI.m: made completely scriptable --- mrLoadRet/ROI/newROI.m | 64 ++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 27 deletions(-) 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 From 46815393cd5d7b506f7e89f7b7504c7af89e5492 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:49:15 +0300 Subject: [PATCH 069/254] isview.m: added option to check a view without checking against (and therefore loading) global variable MLR --- mrLoadRet/View/isview.m | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) 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 From e05fa32150dd5ade394a0d3252f8f50a2e73c3e8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 19:52:54 +0300 Subject: [PATCH 070/254] mlrImageParseArgs.m: calls isview without loading global variable MLR so that it can be used independently of mrLoadRet (in case it happens to be run from an mrLoadRet folder) --- mrUtilities/File/mlrImage/mlrImageParseArgs.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrUtilities/File/mlrImage/mlrImageParseArgs.m b/mrUtilities/File/mlrImage/mlrImageParseArgs.m index 34d8251a6..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; From 2a518410b489d3782e51584ed4cce53ff48f3a22 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 22:00:44 +0300 Subject: [PATCH 071/254] remapSurfaces.m: made it work for Windows + checks if remapped surfaces already exists and recompute only if force is set to true --- mrUtilities/surfUtils/remapSurfaces.m | 222 ++++++++++++++------------ 1 file changed, 122 insertions(+), 100 deletions(-) diff --git a/mrUtilities/surfUtils/remapSurfaces.m b/mrUtilities/surfUtils/remapSurfaces.m index 89f6ab29d..a53d85dea 100644 --- a/mrUtilities/surfUtils/remapSurfaces.m +++ b/mrUtilities/surfUtils/remapSurfaces.m @@ -1,127 +1,151 @@ % remapSurfaces.m % % $Id: remapSurfaces.m 1833 2010-11-13 18:37:14Z julien $ -% usage: remapSurfaces(fssubject,fsaverage) +% usage: remapSurfaces(fssubject,fsaverage,) % 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 +% % 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) +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 - end - subjPath = [getenv('SUBJECTS_DIR') '/' fssubject]; - if isempty(dir(subjPath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fssubject ' does not exist']); - return - end - fsaveragePath = [getenv('SUBJECTS_DIR') '/' fsaverage]; - if isempty(dir(fsaveragePath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsaverage ' does not exist']); + freesurferSubjdir = getenv('SUBJECTS_DIR'); +end +if ieNotDefined('force') + force = false; +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)'); return end -else - mrErrorDlg('(remapSurfaces) Not implemented for platforms other than Unix or Mac'); - % implement another way to get the subjects folder + 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 = []; + return +end +fsaveragePath = [freesurferSubjdir '/' fsaverage]; +if isempty(dir(fsaveragePath)) + mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsaverage ' does not exist']); + fsaveragePath = []; + return end if isempty(dir([subjPath '/surfRelax'])) mrWarnDlg(['(remapSurfaces) surfRelax folder does not exist in ' subjPath '. You must first run mlrImportFreesurfer.']); + subjPath = []; return end if isempty(dir([fsaveragePath '/surfRelax'])) mrWarnDlg(['(remapSurfaces) surfRelax folder does not exist in ' fsaveragePath '. You must first run mlrImportFreesurfer.']); + fsaveragePath = []; return end -side = {'left','right'}; -surfs = {'GM','WM'}; -fsSide = {'lh','rh'}; +if exist(fullfile(subjPath,'/surfRelax/',[fssubject '_left_GM_' fsaverage '.off']),'file') && ~force + mrWarnDlg(sprintf('(remapSurfaces) Mapping between Freesurfer subjects %s and %s already exists, use optional argument to recompute',fssubject,fsaverage)); + return + +else -disp('(remapSurfaces) Will process:'); -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']); - %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'])]; - - [~,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 + side = {'left','right'}; + surfs = {'GM','WM'}; + fsSide = {'lh','rh'}; + + disp('(remapSurfaces) Will process:'); + 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']); + %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'])]; + + [~,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']) end - disp([subjPath '/surfRelax/' fssubject '_' side{iSide} '_Curv.vff']) -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']); + 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']); - 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}]); - - %apply regridding matrix to surfaces - thisAverageSurf = averageSurf; - tmpVtcs = averageToSubj*thisAverageSurf.vtcs; - thisAverageSurf.vtcs = subjToAverage*subjSurf.vtcs; - subjSurf.vtcs = tmpVtcs; - - %change file name - [path,filename,extension]=fileparts(subjSurf.filename); - subjSurf.filename = [path '/' filename '_' fsaverage extension]; - [path,filename,extension]=fileparts(thisAverageSurf.filename); - thisAverageSurf.filename = [path '/' filename '_' fssubjectPrefix extension]; - - %save file - disp(['(remapSurfaces) Writing ' subjSurf.filename]); - writeOFF(subjSurf, subjSurf.filename); - disp(['(remapSurfaces) Writing ' thisAverageSurf.filename]); - writeOFF(thisAverageSurf, thisAverageSurf.filename); - + % 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']); + + 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(fullfile(subjPath,'/surfRelax/',jSurf{1})); + + %apply regridding matrix to surfaces + thisAverageSurf = averageSurf; + tmpVtcs = averageToSubj*thisAverageSurf.vtcs; + 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]; + [path,filename,extension]=fileparts(thisAverageSurf.filename); + thisAverageSurf.filename = [path '/' filename '_' fssubjectPrefix extension]; + + %save file + disp(['(remapSurfaces) Writing ' subjSurf.filename]); + writeOFF(subjSurf, subjSurf.filename); + disp(['(remapSurfaces) Writing ' thisAverageSurf.filename]); + writeOFF(thisAverageSurf, thisAverageSurf.filename); + + end end + + %interpolate curvature data + 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(averageToSubjCurvmax(subjCurv))=max(subjCurv); %clip the data to original min/max (because saveVFF normalizes them) - subjToAverageCurv(subjToAverageCurvmax(averageCurv))=max(subjCurv); - averageToSubjCurv(averageToSubjCurv Date: Mon, 3 Aug 2020 22:05:13 +0300 Subject: [PATCH 072/254] mlrLoadLastView.m: simplified code for ouputt arguments --- mrLoadRet/File/mlrLoadLastView.m | 49 ++++++++++++-------------------- 1 file changed, 18 insertions(+), 31 deletions(-) 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 From 476cd78954bd401f892e15199987cef01986ec17 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 22:09:12 +0300 Subject: [PATCH 073/254] mrSaveView.m: added option to save last view mat file under a different name --- mrLoadRet/GUI/mrSaveView.m | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/GUI/mrSaveView.m b/mrLoadRet/GUI/mrSaveView.m index b7f7c5d5f..891bc01c5 100644 --- a/mrLoadRet/GUI/mrSaveView.m +++ b/mrLoadRet/GUI/mrSaveView.m @@ -1,12 +1,13 @@ % mrSaveView.m % % $Id: mrQuit.m 1942 2010-12-16 18:14:41Z julien $ -% usage: mrSaveView(v) +% usage: mrSaveView(v,) % 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)); + disppercent(-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); catch disppercent(inf); - mrErrorDlg('(mrQuit) Could not save mrLastView.mat'); + mrErrorDlg(sprintf('(mrQuit) Could not save %s',lastViewFile)); end From 52d0bc2d8157794601c13f9f6cb7ac57d0002090 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 22:15:26 +0300 Subject: [PATCH 074/254] mrInit.m: takes name of default group saved in mrSession.mat into account when looking for scans to initialize with --- mrLoadRet/Init/mrInit.m | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/Init/mrInit.m b/mrLoadRet/Init/mrInit.m index 00d51713e..1f8f94a66 100644 --- a/mrLoadRet/Init/mrInit.m +++ b/mrLoadRet/Init/mrInit.m @@ -93,19 +93,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]) @@ -135,7 +146,8 @@ % 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,9 +210,13 @@ 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); From 0badd5b775cd5a73116d50dfaa8ff506e36c05b9 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 22:16:46 +0300 Subject: [PATCH 075/254] mrInit.m: made completely scriptable by adding option to skip overwrite prompt --- mrLoadRet/Init/mrInit.m | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mrLoadRet/Init/mrInit.m b/mrLoadRet/Init/mrInit.m index 1f8f94a66..025679e05 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 @@ -277,15 +279,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')); From bcf134168c0eea078739b89ed04bc7f181c330f1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 3 Aug 2020 22:27:10 +0300 Subject: [PATCH 076/254] Change set allowing mrLoadRet to be run wihtout a GUI, but still using the last-saved view (mrLastView.mat or any other) mrLoadRet: added option 'No GUI', which calls mrOpenWindow with noGUI set to false. This then skips all steps that modify the GUI (and require a figure handle) but have not influence on the view. viewSet and refreshMLRDisplay also skip some GUI-related steps if the the view does not include a figure handle. --- mrLoadRet/GUI/refreshMLRDisplay.m | 54 ++++---- mrLoadRet/View/mrOpenWindow.m | 196 ++++++++++++++++-------------- mrLoadRet/View/viewSet.m | 34 +++--- mrLoadRet/mrLoadRet.m | 13 +- 4 files changed, 167 insertions(+), 130 deletions(-) diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index 6372736f5..4faebe9e4 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -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'); @@ -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,27 +186,33 @@ 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 -%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,toc,end @@ -404,7 +416,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 +431,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 diff --git a/mrLoadRet/View/mrOpenWindow.m b/mrLoadRet/View/mrOpenWindow.m index 4c3d524bc..b1e91965e 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,86 +21,92 @@ 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 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]; -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); + % 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))) @@ -140,8 +148,11 @@ 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); @@ -189,29 +200,34 @@ end disppercent(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 + disppercent(-inf,sprintf('(mrOpenWindow) Refreshing MLR display')); + refreshMLRDisplay(view.viewNum); + disppercent(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/viewSet.m b/mrLoadRet/View/viewSet.m index 67cedd8cd..50189fc5b 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -864,22 +864,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 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')); From 2f5a3353e318d65ab12f46007ae9824786f727ef Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 4 Aug 2020 11:36:03 +0300 Subject: [PATCH 077/254] mrDisp.c: adding back compiled mex files for windows and linux platform, as mrDisp.m (fprintf) causes very annoying java errors. Not tested on Mac, so not putting these back. --- mrUtilities/MatlabUtilities/mrDisp.c | 60 ++++++++++++++++++++++ mrUtilities/MatlabUtilities/mrDisp.mexa64 | Bin 0 -> 10295 bytes mrUtilities/MatlabUtilities/mrDisp.mexglx | Bin 0 -> 8710 bytes mrUtilities/MatlabUtilities/mrDisp.mexw32 | Bin 0 -> 6144 bytes mrUtilities/MatlabUtilities/mrDisp.mexw64 | Bin 0 -> 8192 bytes 5 files changed, 60 insertions(+) create mode 100644 mrUtilities/MatlabUtilities/mrDisp.c create mode 100644 mrUtilities/MatlabUtilities/mrDisp.mexa64 create mode 100755 mrUtilities/MatlabUtilities/mrDisp.mexglx create mode 100644 mrUtilities/MatlabUtilities/mrDisp.mexw32 create mode 100644 mrUtilities/MatlabUtilities/mrDisp.mexw64 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.mexa64 b/mrUtilities/MatlabUtilities/mrDisp.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..cb079a728cab07c0929dadb90473d80ed1e592c6 GIT binary patch literal 10295 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 S!#B>cp=wF!*EvJv#r^}AbiWM% literal 0 HcmV?d00001 diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexglx b/mrUtilities/MatlabUtilities/mrDisp.mexglx new file mode 100755 index 0000000000000000000000000000000000000000..de88a28848759bd128dc3aa5d2ba4fbee2866386 GIT binary patch literal 8710 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 Date: Sat, 8 Aug 2020 14:50:39 +0300 Subject: [PATCH 078/254] remapSurfaces.m: added option dryRun that just checks if the necessary files exist but does not compute anything --- mrUtilities/surfUtils/remapSurfaces.m | 182 ++++++++++++++------------ 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/mrUtilities/surfUtils/remapSurfaces.m b/mrUtilities/surfUtils/remapSurfaces.m index a53d85dea..3170cb6d3 100644 --- a/mrUtilities/surfUtils/remapSurfaces.m +++ b/mrUtilities/surfUtils/remapSurfaces.m @@ -13,7 +13,7 @@ % output: left and right GM and WM surfaces with mesh of one subject % and coordinates of the other, and vice-versa -function [subjPath,fsaveragePath] = remapSurfaces(fssubject,fsaverage,force) +function [subjPath,fsaveragePath] = remapSurfaces(fssubject,fsaverage,force, dryRun) freesurferSubjdir = []; subjPath = []; @@ -24,12 +24,17 @@ if ieNotDefined('force') force = false; end +if ieNotDefined('dryRun') + dryRun = false; +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)'); - return + if ~dryRun + return + end end fprintf('(remapSurfaces) Assuming that the Freesurfer subject directory is %s\n',freesurferSubjdir); end @@ -38,112 +43,123 @@ if isempty(dir(subjPath)) mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fssubject ' does not exist']); subjPath = []; - return + if ~dryRun + return + end end fsaveragePath = [freesurferSubjdir '/' fsaverage]; if isempty(dir(fsaveragePath)) mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsaverage ' does not exist']); fsaveragePath = []; - return + if ~dryRun + return + end end if isempty(dir([subjPath '/surfRelax'])) mrWarnDlg(['(remapSurfaces) surfRelax folder does not exist in ' subjPath '. You must first run mlrImportFreesurfer.']); subjPath = []; - return + 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 = []; - return + if ~dryRun + return + end end if exist(fullfile(subjPath,'/surfRelax/',[fssubject '_left_GM_' fsaverage '.off']),'file') && ~force mrWarnDlg(sprintf('(remapSurfaces) Mapping between Freesurfer subjects %s and %s already exists, use optional argument to recompute',fssubject,fsaverage)); - return + if ~dryRun + return + end +end -else - - side = {'left','right'}; - surfs = {'GM','WM'}; - fsSide = {'lh','rh'}; - - disp('(remapSurfaces) Will process:'); - 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']); - %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'])]; - - [~,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']) +if dryRun + return +end + +side = {'left','right'}; +surfs = {'GM','WM'}; +fsSide = {'lh','rh'}; + +disp('(remapSurfaces) Will process:'); +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']); + %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'])]; + + [~,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']) +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']); + + 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(fullfile(subjPath,'/surfRelax/',jSurf{1})); + + %apply regridding matrix to surfaces + thisAverageSurf = averageSurf; + tmpVtcs = averageToSubj*thisAverageSurf.vtcs; + 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]; + [path,filename,extension]=fileparts(thisAverageSurf.filename); + thisAverageSurf.filename = [path '/' filename '_' fssubjectPrefix extension]; + + %save file + disp(['(remapSurfaces) Writing ' subjSurf.filename]); + writeOFF(subjSurf, subjSurf.filename); + disp(['(remapSurfaces) Writing ' thisAverageSurf.filename]); + writeOFF(thisAverageSurf, thisAverageSurf.filename); - 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']); - - 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(fullfile(subjPath,'/surfRelax/',jSurf{1})); - - %apply regridding matrix to surfaces - thisAverageSurf = averageSurf; - tmpVtcs = averageToSubj*thisAverageSurf.vtcs; - 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]; - [path,filename,extension]=fileparts(thisAverageSurf.filename); - thisAverageSurf.filename = [path '/' filename '_' fssubjectPrefix extension]; - - %save file - disp(['(remapSurfaces) Writing ' subjSurf.filename]); - writeOFF(subjSurf, subjSurf.filename); - disp(['(remapSurfaces) Writing ' thisAverageSurf.filename]); - writeOFF(thisAverageSurf, thisAverageSurf.filename); - - end end - - %interpolate curvature data - 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(averageToSubjCurvmax(subjCurv))=max(subjCurv); %clip the data to original min/max (because saveVFF normalizes them) + subjToAverageCurv(subjToAverageCurvmax(averageCurv))=max(subjCurv); + averageToSubjCurv(averageToSubjCurv Date: Sat, 8 Aug 2020 14:57:06 +0300 Subject: [PATCH 079/254] added surfRelaxFlipLR.m --- mrUtilities/surfUtils/surfRelaxFlipLR.m | 112 ++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 mrUtilities/surfUtils/surfRelaxFlipLR.m diff --git a/mrUtilities/surfUtils/surfRelaxFlipLR.m b/mrUtilities/surfUtils/surfRelaxFlipLR.m new file mode 100644 index 000000000..d042bcc48 --- /dev/null +++ b/mrUtilities/surfUtils/surfRelaxFlipLR.m @@ -0,0 +1,112 @@ +% function thisView = surfRelaxFlipLR(freeSurferID,) +% +% 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); From 01e262f6baed3516c6d375577207cddde2d356dc Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 8 Aug 2020 15:03:52 +0300 Subject: [PATCH 080/254] added freesurferSphericalNormalizationVolumes.m --- .../freesurferSphericalNormalizationVolumes.m | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m new file mode 100644 index 000000000..216bd0d12 --- /dev/null +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -0,0 +1,335 @@ +% 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.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.recomputeRemapping (optional): whether to recompute the mapping between the source and destination surfaces (default: false) +% 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,'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 ieNotDefined('interpMethod') + params.interpMethod = mrGetPref('interpMethod'); +end +if ieNotDefined('recomputeRemapping') + params.recomputeRemapping = false; +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') + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find destination volume %s',params.sourceVol{iSource})); + if ~params.dryRun + 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); +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 destination 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 %s',params.destVolTemplate)); + if ~params.dryRun + return; + end +end +if ~params.dryRun + destHdr = mlrImageReadNiftiHeader(params.destVolTemplate); + destXform = getXform(destHdr); +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); +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} '.off']); + destSurf{iSurf} = loadSurfOFF([sourcePath '/surfRelax/' params.fsSourceSubj '_' side{iSide} '_' surfs{iSurf} '_' 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 + + % compute intermediate depth coordinates for destination mesh + destCoords = zeros(destSurf{1}.Nvtcs,3,nDepths,nSides); + for iDepth = 1:nDepths + destCoords(:,:,iDepth) = (1-corticalDepths(iDepth))*destSurf{1}.vtcs + corticalDepths(iDepth)*destSurf{2}.vtcs; + end + + % compute mapping between source surface and destination volume + destCoords = permute(destCoords,[1 4 3 2]); + flat2volumeMap{iSide} = inverseBaseCoordMap(destCoords,destHdr.dim(2:4)'); + +end +clearvars('destCoords'); %save memory + +% 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(destHdr.dim(2:4)'); + % get source volume data + [sourceData,sourceHdr] = mlrImageReadNifti(params.sourceVol{iSource}); + 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),params.interpMethod); + % transform surface data to destination volume + thisData = applyInverseBaseCoordMap(flat2volumeMap{iSide},destHdr.dim(2:4)',surfData); + 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 + 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 + + From 7f4da852300b8f205b244228e4999c1f3f1c5f36 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 8 Aug 2020 15:07:51 +0300 Subject: [PATCH 081/254] added mlrSphericalNormGroup.m --- .../groupAnalysis/mlrSphericalNormGroup.m | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m new file mode 100644 index 000000000..894ce29d8 --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -0,0 +1,342 @@ +% function params = mlrSphericalNormGroup(params,<'justGetParams'>) +% +% goal: Exports overlay 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 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 the group-average using mrLoadRet timeseries analysis. +% +% usage: +% params = mlrSphericalNormGroup(params,'justGetParams') %returns default parameters which need to be modified +% mlrSphericalNormGroup(params) % run 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.freesurferTemplateID Freesurfer subject IF 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 (deafult: 'Spherical Normalization') +% params.mrLoadRetTemplateLastView Name of the saved last view in the template MLR folder (default: mrLastView.mat) +% params.subjectOverlayGroups Group numbers of the overlays to normalize +% params.subjectOverlayAnalyses Analysis numbers of the overlays to normalize +% params.subjectOverlays Overlay numbers to normalize +% params.templateOverlaysGroup In what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported +% 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.computeGroupAverage If true, normalized overlays will be averaged across subjects (optionally hemispheres) (default: true) +% params.templateOverlayNewNames New names of the converted overlays +% params.dryRun If true, just check whether the overlays and surfaces exist (default: false) +% +% author: julien besle (28/07/2020) + +function params = mlrSphericalNormGroup(params,varargin) + +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,'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,'subjectOverlayGroups') + params.subjectOverlayGroups = 1; % group numbers of the overlays to normalize +end +if fieldIsNotDefined(params,'subjectOverlayAnalyses') + params.subjectOverlayAnalyses = 1; % analysis numbers of the overlays to normalize +end +if fieldIsNotDefined(params,'subjectOverlays') + params.subjectOverlays = 1; % overlay numbers to normalize +end +if fieldIsNotDefined(params,'templateOverlaysGroup') + params.templateOverlaysGroup = params.freesurferTemplateID; % in what group of the group mrLoadRet folder the group-normalized overlays (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 = ''; % combineLeftAndRight is true, name of the Freesurfer folder containing the left-right flipped surfaces of the left-right-flipped template +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,'dryRun') + params.dryRun = false; % if true, just check whether the overlays and surfaces exist +end + +if justGetParams, return; end + + +nSubjects = length(params.mrLoadRetSubjectIDs); +nOverlays = length(params.subjectOverlays); + +if nSubjects ~= length(params.freesurferSubjectIDs) + mrWarnDlg('(mlrSphericalNormGroup) There must be the same number of mrLoadRet and freesurfer subject IDs') + return; +end +if nOverlays ~= length(params.subjectOverlayGroups) + mrWarnDlg('(mlrSphericalNormGroup) Specify the same number of overlays and groups') + return; +end +if nOverlays ~= length(params.subjectOverlayGroups) + mrWarnDlg('(mlrSphericalNormGroup) Specify the same number of overlays and analyses') + 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 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 + +% create mrLoadRet group folder +mrLoadRetTemplateFolder = fullfile(params.studyDir,params.mrLoadRetTemplateID); +templateTseriesFolder = fullfile(mrLoadRetTemplateFolder,params.templateOverlaysGroup,'TSeries'); +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', params.templateOverlaysGroup)); + else + initMrLoadRetGroup = false; + mkdir(mrLoadRetTemplateFolder,params.templateOverlaysGroup); + mkdir(fullfile(mrLoadRetTemplateFolder,params.templateOverlaysGroup),'TSeries'); + end +end + +mrQuit(0); % make sure there are no open MLR views + +% export subject overlays to subject-specific scan spaces +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(templateTseriesFolder)+1) - (length(params.mrLoadRetSubjectIDs{iSubj})+1) ... + - (max(params.combineLeftAndRight*(length(params.lrFlippedFreesurferTemplateID)),length(params.freesurferTemplateID))+1) - 7; + + cd(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{iSubj})); + thisView = mrLoadRet(params.mrLoadRetSubjectLastView,'No GUI'); + + cOverlay = 0; + exportNames{iSubj} = cell(0); + convertNames{iSubj} = cell(0); + convertFlippedNames{iSubj} = cell(0); + for iOverlay = 1:nOverlays + thisView = viewSet(thisView,'curGroup',params.subjectOverlayGroups(iOverlay)); + thisView = viewSet(thisView,'curAnalysis',params.subjectOverlayAnalyses(iOverlay)); + if params.subjectOverlays(iOverlay) <= viewGet(thisView,'nOverlays') + thisView = viewSet(thisView,'curOverlay',params.subjectOverlays(iOverlay)); + fprintf('(mlrSphericalNormGroup) Will export Group %d, Analysis %d, overlay %d: %s\n',params.subjectOverlayGroups(iOverlay),params.subjectOverlayAnalyses(iOverlay),params.subjectOverlays(iOverlay),viewGet(thisView,'overlayName')); + cOverlay = cOverlay + 1; + overlayExists = true; + else + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find overlay %d in group %d, analysis %d...',params.subjectOverlays(iOverlay),params.subjectOverlayGroups(iOverlay),params.subjectOverlayAnalyses(iOverlay))); + if ~params.dryRun + return; + else + overlayExists=false; + end + end + + if overlayExists + groupName = viewGet(thisView,'groupName'); + if endsWith(groupName,'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} = sprintf('%02d_%s',iOverlay,fixBadChars(viewGet(thisView,'overlayName'),badCharFixlist,[],maxNcharacters)); + if iSubj==1 + if isempty(params.templateOverlayNewNames) + params.templateOverlayNewNames{iOverlay} = overlayBaseName{iOverlay}; + else + params.templateOverlayNewNames{iOverlay} = sprintf('%02d_%s',iOverlay,fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters)); + end + end + % export overlays to NIFTI in scan space + exportNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj})); + convertNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); + if params.combineLeftAndRight + convertFlippedNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); + end + if ~params.dryRun + mrExport2SR(thisView,exportNames{iSubj}{cOverlay},0); + 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 + + % transform subject scans to group space + fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.freesurferTemplateID); + fsSphericalParams = freesurferSphericalNormalizationVolumes([],'justGetParams'); + fsSphericalParams.sourceVol = exportNames{iSubj}; + fsSphericalParams.fsSourceSubj = params.freesurferSubjectIDs{iSubj}; + fsSphericalParams.fsDestSubj = params.freesurferTemplateID; + fsSphericalParams.destVol = convertNames{iSubj}; + fsSphericalParams.interpMethod = 'linear'; + fsSphericalParams.dryRun = params.dryRun; + fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); + fsSphericalParamsOut.destSurfRelaxVolume = fullfile(mrGetPref('volumeDirectory'),params.freesurferTemplateID,'surfRelax',[params.freesurferTemplateID '_mprage_pp.nii']); % DELETE + if params.combineLeftAndRight + fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.lrFlippedFreesurferTemplateID); + fsSphericalParams.fsDestSubj = params.lrFlippedFreesurferTemplateID; + fsSphericalParams.destVol = convertFlippedNames{iSubj}; + freesurferSphericalNormalizationVolumes(fsSphericalParams); + end +end + +if ~params.dryRun + + % delete subject-space exported NIFTI files + for iSubj =1:nSubjects + for iOverlay = 1:nOverlays + delete(exportNames{iSubj}{iOverlay}); + end + end + + % Concatenate normalized subject data + cOverlay = 0; + for iOverlay = 1:nOverlays + cOverlay = cOverlay+1; + templateOverlayName{cOverlay} = params.templateOverlayNewNames{iOverlay}; + fprintf('\n(mlrSphericalNormGroup) Concatenating subject data for overlay %s\n',templateOverlayName{cOverlay}); + for iSubj = 1:nSubjects + [data(:,:,:,iSubj),hdr] = mlrImageLoad(convertNames{iSubj}{iOverlay}); + end + hdr.dim = [hdr.dim nSubjects]; + hdr.filename = fullfile(templateTseriesFolder,[templateOverlayName{cOverlay} '.nii']); + mlrImageSave(hdr.filename,data,hdr); + if params.combineLeftAndRight + cOverlay = cOverlay+1; + templateOverlayName{cOverlay} = [params.templateOverlayNewNames{iOverlay} '_LRcombined']; + fprintf('\n(mlrSphericalNormGroup) Concatenating hemisphere and subject data for overlay %s\n',templateOverlayName{cOverlay}); + for iSubj = 1:nSubjects + [data(:,:,:,nSubjects+iSubj),hdr] = mlrImageLoad(convertFlippedNames{iSubj}{iOverlay}); + end + hdr.dim = [hdr.dim nSubjects*2]; + hdr.filename = fullfile(templateTseriesFolder,[templateOverlayName{cOverlay} '.nii']); + mlrImageSave(hdr.filename,data,hdr); + end + clear('data'); + end + % delete temporary converted subject files + for iSubj =1:nSubjects + for iOverlay = 1:nOverlays + delete(convertNames{iSubj}{iOverlay}); + if params.combineLeftAndRight + delete(convertFlippedNames{iSubj}{iOverlay}); + end + end + end + + fprintf('(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); + nTemplateOverlays = length(templateOverlayName); + % initialize mrLoadRet view/import group + cd(mrLoadRetTemplateFolder); + if initMrLoadRetGroup + subjectSessionParams = load(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{1},'mrSession.mat')); + [sessionParams, groupParams] = mrInit([],[],'justGetParams=1','defaultParams=1'); + for iScan = 1:nTemplateOverlays + groupParams.description{iScan} = templateOverlayName{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'); + % 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'); + thisView = viewSet(thisView,'newGroup',params.templateOverlaysGroup); + [~,params] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1'); + for iOverlay = 1:nOverlays + params.pathname = fullfile(templateTseriesFolder,[overlayBaseName{iOverlay} '.nii']); + [~,params] = importTSeries(thisView,params); + if params.combineLeftAndRight + params.pathname = fullfile(templateTseriesFolder,[overlayBaseName{iOverlay} '_LRcombined.nii']); + [~,params] = importTSeries(thisView,params); + end + end + end + + if params.computeGroupAverage + statsParams.groupName = params.templateOverlaysGroup; + statsParams.scanList = 1:nTemplateOverlays; + thisView = timeSeriesStats(thisView,statsParams); + % replace 0s by NaNs (because NaNs are written out in the Nifti files as 0; this is probably an MLR problem that can be fixed in mlrImageSave) + [~,params] = combineTransformOverlays(thisView,[],'justGetParams=1','defaultParams=1'); + params.combineFunction = '@(x)replaceByNaNs(x,0)'; + for iOverlay = 1:3 % only do the first 3 overlays (mean, median and std-deviation) + params.outputName = ' '; + params.overlayList = iOverlay; + thisView = combineTransformOverlays(thisView,params); + end + thisView = viewSet(thisView,'deleteOverlay',1:6); % remove first 6 overlays + thisView = viewSet(thisView,'curOverlay',1); % mean + thisView = viewSet(thisView,'clipAcrossOverlays',false); + + end + + mrSaveView(thisView); + deleteView(thisView); + +end From a55b3f36789b6ba668aada249befe692b2564000 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:19:34 +0300 Subject: [PATCH 082/254] cbiWriteNifti.m: added option to append extra volumes and fixed the subset option --- mrUtilities/File/Nifti/cbiWriteNifti.m | 210 +++++++++++++------------ 1 file changed, 113 insertions(+), 97 deletions(-) diff --git a/mrUtilities/File/Nifti/cbiWriteNifti.m b/mrUtilities/File/Nifti/cbiWriteNifti.m index 25534c297..ba364a426 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,46 @@ 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 + +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 +144,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 +284,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) From dff4720dcacce1bc2039a95f538640a120d7a912 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:32:08 +0300 Subject: [PATCH 083/254] cbiCreateNiftiHeader.m: checks consistency between dim and pixdim fields even if not setting the dim field --- mrUtilities/File/Nifti/cbiCreateNiftiHeader.m | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m b/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m index ec86eb396..0c76f3d6f 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)) && 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)) && size(hdr.sform44)==[4,4]) % Calculate srows hdr.srow_x=hdr.sform44(1,:); hdr.srow_y=hdr.sform44(2,:); From 015b2242c410911ca014d2acde513e23248fa88b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:34:27 +0300 Subject: [PATCH 084/254] mrExport2SR.m: replaced call to mlrImageSave by call to mlrImageWriteNifti (since it originally replace a call to cbiWriteNifti + cleaned the part that forces the data to be written as float32 --- mrLoadRet/File/mrExport2SR.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/File/mrExport2SR.m b/mrLoadRet/File/mrExport2SR.m index f338a0b57..432d318c8 100644 --- a/mrLoadRet/File/mrExport2SR.m +++ b/mrLoadRet/File/mrExport2SR.m @@ -37,9 +37,9 @@ hdr.dim(5) = length(overlayNum); end -hdr.datatype = 16; % make sure data are written with 32 bits per value to avoid surprises -hdr.bitpix = 32; +hdr.datatype = 16; % make sure data are written as float32 (single) hdr.scl_slope = 1; +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'; @@ -53,5 +53,5 @@ if ~ieNotDefined('pathstr') || nargout>0 %write nifti file - mlrImageSave(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) + mlrImageWriteNifti(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) end From 35f4ba5e8bea032433f2da4ba9ac6cb8392bc50e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:36:32 +0300 Subject: [PATCH 085/254] freesurferSphericalNormalizationVolumes.m: make sure converted data are written out as float32 --- mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m | 1 + 1 file changed, 1 insertion(+) diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index 216bd0d12..aaa989ebf 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -219,6 +219,7 @@ 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 end if params.dryRun From c29501e3be56ab07d5c17d6bed0fe30fba26ae10 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:48:20 +0300 Subject: [PATCH 086/254] added mlrGroupAverage.m --- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 161 ++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 mrLoadRet/groupAnalysis/mlrGroupAverage.m diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m new file mode 100644 index 000000000..071478783 --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -0,0 +1,161 @@ + +% [thisView,params] = mlrGroupAverage(thisView,params,<'justGetParams'>) +% +% goal: averages volumes in "group" scan across subjects according to conditions +% specified in .mat file linked to the scan. +% 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 +% 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 +% +% author: julien besle (10/08/2020) + +function [thisView,params] = 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 +if fieldIsNotDefined(params,'analysisName') + params.analysisName = 'Group averages'; +end +if fieldIsNotDefined(params,'scanList') + params.scanList = 1:viewGet(thisView,'nScans'); +end + +%read log files associated with scans +cScan = 0; +for iScan = params.scanList + cScan= cScan+1; + logFileName = viewGet(thisView,'stimfilename',iScan); + if isempty(logFileName{1}) + mrWarnDlg(sprintf('(mlrGroupAverage) No mat file linked to scan %d', iScan)); + end + factors{cScan} = load(logFileName{1}); + if isempty(factors{cScan}) + mrWarnDlg(sprintf('(mlrGroupAverage) Cannot open file %s for scan %d', logFileName, iScan)); + end + if iScan == 1 + commonFactors = fieldnames(factors{cScan}); + allFactors = commonFactors; + else + commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); + allFactors = union(allFactors,fieldnames(factors{cScan})); + end +end + +if fieldIsNotDefined(params,'factors') + params.factors = allFactors; +end +if fieldIsNotDefined(params,'averagingMode') + params.averagingMode = 'marginal'; % options are 'marginal' or 'interaction' +end + +if justGetParams, return; end + + +if strcmp(params.averagingMode,'interaction') + if ~all(ismember(params.factors,commonFactors)) + mrErrorDlg('(mlrGroupAverage) Cannot compute averages because factors are missing in some scans'); + end + factorNames{1} = params.factors; +else + for iFactor = 1:length(params.factors) + factorNames{iFactor} = params.factors(iFactor); + end +end + +thisView = viewSet(thisView,'curGroup',params.groupNum); + +for iScan = 1:length(params.scanList) + tseriesPath{iScan} = viewGet(thisView,'tseriespathstr',params.scanList(iScan)); + hdr{iScan} = cbiReadNiftiHeader(tseriesPath{iScan}); + for iFactor = 1:length(factorNames) + levels{iScan} = []; + for jFactor = 1:length(factorNames{iFactor}) + % check that the number of volumes matches the number of elements in the factor variables + if length(factors{iScan}.(factorNames{iFactor}{jFactor})) ~= hdr{iScan}.dim(5) + mrErrorDlg(sprintf('(mlrGroupAverage) Scan %d: Mismatched number of volumes between .mat variable ''%s'' (%d) and time series file (%d)', ... + params.scanList(iScan),factorNames{iFactor}{jFactor},length(factors{iScan}.(factorNames{iFactor}{jFactor})),hdr{iScan}.dim(5))); + end + if size(factors{iScan}.(factorNames{iFactor}{jFactor}),1)==1 + factors{iScan}.(factorNames{iFactor}{jFactor}) = factors{iScan}.(factorNames{iFactor}{jFactor})'; % make sure the factor is a column cell array + end + levels{iScan} = [levels{iScan} factors{iScan}.(factorNames{iFactor}{jFactor})]; + end + if iFactor == 1 && iScan ==1 + uniqueLevels = unique(levels{iScan},'rows','stable'); + else + uniqueLevels = union(uniqueLevels,levels{iScan},'rows','stable'); + end + end +end + +nLevels = size(uniqueLevels,1); +for iOverlay = 1:nLevels + for iScan = 1:length(params.scanList) + overlays(iOverlay).data{params.scanList(iScan)} = zeros(hdr{iScan}.dim(2:4)'); + end +end + +minOverlay = inf(nLevels,1); +maxOverlay = -1*inf(nLevels,1); +for iScan = 1:length(params.scanList) + volumeCount = zeros(nLevels,1); + for iVolume = 1:hdr{iScan}.dim(5) + [~,whichOverlay] = ismember(levels{iScan}(iVolume),uniqueLevels); + % add data + overlays(whichOverlay).data{params.scanList(iScan)} = overlays(whichOverlay).data{params.scanList(iScan)} + cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); + volumeCount(whichOverlay) = volumeCount(whichOverlay) + 1; + end + for iOverlay = 1:nLevels + overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}/volumeCount(whichOverlay),mrGetPref('defaultPrecision')); + minOverlay(iOverlay) = min(minOverlay(iOverlay),min(overlays(iOverlay).data{params.scanList(iScan)}(:))); + maxOverlay(iOverlay) = max(maxOverlay(iOverlay),max(overlays(iOverlay).data{params.scanList(iScan)}(:))); + end +end + +%add overlays' missing fields +for iOverlay = 1:nLevels + overlays(iOverlay).name = uniqueLevels{iOverlay}; + overlays(iOverlay).groupName = viewGet(thisView,'groupName'); + overlays(iOverlay).params = params; + overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; + overlays(iOverlay).type = 'Group average'; + overlays(iOverlay).function = 'mlrGroupAverage'; + overlays(iOverlay).interrogator = ''; +end + +% set or create analysis +analysisNum = viewGet(thisView,'analysisName',params.analysisName); +if isempty(viewGet(thisView,'analysisNum',analysisNum)) + thisView = newAnalysis(thisView,params.analysisName); +else + thisView = viewSet(thisView,'curAnalysis',analysisNum); +end +% add overlays to view +thisView = viewSet(thisView,'newOverlay',overlays); From 3dbe4824dcceb64c0fa2574464e483e89eb72164 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 16:53:13 +0300 Subject: [PATCH 087/254] mlrSphericalNormGroup.m: subject overlays are now combined across different overlays into a single scan and overlay/subject identity is saved in mat file linked to each scan. Group averages are computed using mlrGroupAverage.m + other small fixes --- .../groupAnalysis/mlrSphericalNormGroup.m | 153 +++++++++--------- 1 file changed, 80 insertions(+), 73 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 894ce29d8..75b112731 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -9,8 +9,9 @@ % Optionally computes the group-average using mrLoadRet timeseries analysis. % % usage: -% params = mlrSphericalNormGroup(params,'justGetParams') %returns default parameters which need to be modified -% mlrSphericalNormGroup(params) % run function with modified params +% 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) @@ -147,6 +148,8 @@ mrQuit(0); % make sure there are no open MLR views % export subject overlays to subject-specific scan spaces +cOverlayConcat = 0; +cOverlayConcatLRcombined = 0; 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) @@ -157,9 +160,8 @@ thisView = mrLoadRet(params.mrLoadRetSubjectLastView,'No GUI'); cOverlay = 0; - exportNames{iSubj} = cell(0); - convertNames{iSubj} = cell(0); - convertFlippedNames{iSubj} = cell(0); + exportNames = cell(0); + convertNames = cell(0,1+params.combineLeftAndRight); for iOverlay = 1:nOverlays thisView = viewSet(thisView,'curGroup',params.subjectOverlayGroups(iOverlay)); thisView = viewSet(thisView,'curAnalysis',params.subjectOverlayAnalyses(iOverlay)); @@ -192,13 +194,13 @@ end end % export overlays to NIFTI in scan space - exportNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj})); - convertNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); + exportNames{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj})); + convertNames{cOverlay,1} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); if params.combineLeftAndRight - convertFlippedNames{iSubj}{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); + convertNames{cOverlay,2} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); end if ~params.dryRun - mrExport2SR(thisView,exportNames{iSubj}{cOverlay},0); + mrExport2SR(thisView,exportNames{cOverlay},0); end end end @@ -207,75 +209,80 @@ % transform subject scans to group space fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.freesurferTemplateID); fsSphericalParams = freesurferSphericalNormalizationVolumes([],'justGetParams'); - fsSphericalParams.sourceVol = exportNames{iSubj}; + fsSphericalParams.sourceVol = exportNames; fsSphericalParams.fsSourceSubj = params.freesurferSubjectIDs{iSubj}; fsSphericalParams.fsDestSubj = params.freesurferTemplateID; - fsSphericalParams.destVol = convertNames{iSubj}; + fsSphericalParams.destVol = convertNames(:,1); fsSphericalParams.interpMethod = 'linear'; fsSphericalParams.dryRun = params.dryRun; fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); - fsSphericalParamsOut.destSurfRelaxVolume = fullfile(mrGetPref('volumeDirectory'),params.freesurferTemplateID,'surfRelax',[params.freesurferTemplateID '_mprage_pp.nii']); % DELETE if params.combineLeftAndRight fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.lrFlippedFreesurferTemplateID); fsSphericalParams.fsDestSubj = params.lrFlippedFreesurferTemplateID; - fsSphericalParams.destVol = convertFlippedNames{iSubj}; + fsSphericalParams.destVol = convertNames(:,2); freesurferSphericalNormalizationVolumes(fsSphericalParams); end -end -if ~params.dryRun + if ~params.dryRun - % delete subject-space exported NIFTI files - for iSubj =1:nSubjects + % delete subject-space exported NIFTI files for iOverlay = 1:nOverlays - delete(exportNames{iSubj}{iOverlay}); + delete(exportNames{iOverlay}); end - end - % Concatenate normalized subject data - cOverlay = 0; - for iOverlay = 1:nOverlays - cOverlay = cOverlay+1; - templateOverlayName{cOverlay} = params.templateOverlayNewNames{iOverlay}; - fprintf('\n(mlrSphericalNormGroup) Concatenating subject data for overlay %s\n',templateOverlayName{cOverlay}); - for iSubj = 1:nSubjects - [data(:,:,:,iSubj),hdr] = mlrImageLoad(convertNames{iSubj}{iOverlay}); - end - hdr.dim = [hdr.dim nSubjects]; - hdr.filename = fullfile(templateTseriesFolder,[templateOverlayName{cOverlay} '.nii']); - mlrImageSave(hdr.filename,data,hdr); - if params.combineLeftAndRight - cOverlay = cOverlay+1; - templateOverlayName{cOverlay} = [params.templateOverlayNewNames{iOverlay} '_LRcombined']; - fprintf('\n(mlrSphericalNormGroup) Concatenating hemisphere and subject data for overlay %s\n',templateOverlayName{cOverlay}); - for iSubj = 1:nSubjects - [data(:,:,:,nSubjects+iSubj),hdr] = mlrImageLoad(convertFlippedNames{iSubj}{iOverlay}); + % Concatenate normalized subject data + fprintf('\n(mlrSphericalNormGroup) Concatenating transformed data for subject %s...\n',params.mrLoadRetSubjectIDs{iSubj}); + for iOverlay = 1:nOverlays + cOverlayConcat = cOverlayConcat+1; + cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; + if iSubj==1 && iOverlay == 1 + templateScanName{1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s',mat2str(params.subjectOverlays),mat2str(params.subjectOverlayAnalyses),mat2str(params.subjectOverlayGroups)); + if params.combineLeftAndRight + templateScanName{2} = [templateScanName{1} '_LRcombined']; + end + end + [data,hdr] = cbiReadNifti(convertNames{iOverlay,1}); + hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything + cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{1} '.nii']),data,hdr,'',{[],[],[],cOverlayConcat}); + % for log file + logfile.overlay{cOverlayConcat,1} = params.templateOverlayNewNames{iOverlay}; + logfile.subject{cOverlayConcat,1} = params.mrLoadRetSubjectIDs{iSubj}; + + if params.combineLeftAndRight + cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{2} '.nii']),data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); + logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; + logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'normal'; + logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; + cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; + [data,hdr] = cbiReadNifti(convertNames{iOverlay,2}); + hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything + cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{2} '.nii']),data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); + logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; + logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'reversed'; + logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; end - hdr.dim = [hdr.dim nSubjects*2]; - hdr.filename = fullfile(templateTseriesFolder,[templateOverlayName{cOverlay} '.nii']); - mlrImageSave(hdr.filename,data,hdr); end - clear('data'); - end - % delete temporary converted subject files - for iSubj =1:nSubjects + + % delete temporary converted subject files for iOverlay = 1:nOverlays - delete(convertNames{iSubj}{iOverlay}); - if params.combineLeftAndRight - delete(convertFlippedNames{iSubj}{iOverlay}); + for iCombine = 1:1+params.combineLeftAndRight + delete(convertNames{iOverlay,iCombine}); end end end +end +if ~params.dryRun fprintf('(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); - nTemplateOverlays = length(templateOverlayName); + nScans = length(templateScanName); % initialize mrLoadRet view/import group cd(mrLoadRetTemplateFolder); if initMrLoadRetGroup + scanList = 1:nScans; subjectSessionParams = load(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{1},'mrSession.mat')); [sessionParams, groupParams] = mrInit([],[],'justGetParams=1','defaultParams=1'); - for iScan = 1:nTemplateOverlays - groupParams.description{iScan} = templateOverlayName{iScan}; + for iScan = scanList + groupParams.description{iScan} = templateScanName{iScan}; end sessionParams.magnet = subjectSessionParams.session.magnet; sessionParams.coil = subjectSessionParams.session.coil; @@ -304,36 +311,36 @@ thisView = viewSet(thisView,'corticalDepth',[0.2 0.8]); %set the range of of cortical depths used for displaying the overlays end - else + else %%%%%%%%%%%%% NOT TESTED thisView = mrLoadRet(params.mrLoadRetTemplateLastView,'No GUI'); thisView = viewSet(thisView,'newGroup',params.templateOverlaysGroup); - [~,params] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1'); - for iOverlay = 1:nOverlays - params.pathname = fullfile(templateTseriesFolder,[overlayBaseName{iOverlay} '.nii']); - [~,params] = importTSeries(thisView,params); - if params.combineLeftAndRight - params.pathname = fullfile(templateTseriesFolder,[overlayBaseName{iOverlay} '_LRcombined.nii']); - [~,params] = importTSeries(thisView,params); - end + [~,importParams] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1'); + for iCombine = 1:1+params.combineLeftAndRight + importParams.pathname = templateFileName{iCombine}; + importTSeries(thisView,importParams); + end + scanList = viewGet(thisView,'nScans')-[nScans:-1:1]+1; + end + + % create log files indicating which volumes correspond to which overlays and subjects + for iCombine = 1:1+params.combineLeftAndRight + if iCombine==2 + logfile = logfileCombined; end + logFilename = [templateScanName{iCombine} '.mat']; + save(fullfile(mrLoadRetTemplateFolder,'Etc',logFilename),'-struct','logfile'); + % link log file to concatenated scan + fprintf('Linking %s to scan %d\n',logFilename,scanList(iCombine)); + viewSet(thisView,'stimfilename',logFilename, scanList(iCombine)); end if params.computeGroupAverage - statsParams.groupName = params.templateOverlaysGroup; - statsParams.scanList = 1:nTemplateOverlays; - thisView = timeSeriesStats(thisView,statsParams); - % replace 0s by NaNs (because NaNs are written out in the Nifti files as 0; this is probably an MLR problem that can be fixed in mlrImageSave) - [~,params] = combineTransformOverlays(thisView,[],'justGetParams=1','defaultParams=1'); - params.combineFunction = '@(x)replaceByNaNs(x,0)'; - for iOverlay = 1:3 % only do the first 3 overlays (mean, median and std-deviation) - params.outputName = ' '; - params.overlayList = iOverlay; - thisView = combineTransformOverlays(thisView,params); - end - thisView = viewSet(thisView,'deleteOverlay',1:6); % remove first 6 overlays - thisView = viewSet(thisView,'curOverlay',1); % mean + [~,averageParams] = mlrGroupAverage(thisView,[],'justGetParams'); + averageParams.factors = {'overlay'}; + averageParams.scanList = scanList; + thisView = mlrGroupAverage(thisView,averageParams); + thisView = viewSet(thisView,'curOverlay',1); thisView = viewSet(thisView,'clipAcrossOverlays',false); - end mrSaveView(thisView); From 791fdd6056a6be214b695120c498d3403a1e6179 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Aug 2020 17:23:57 +0300 Subject: [PATCH 088/254] cbiCreateNiftiHeader.m: corrected bug introduced in dff4720 --- mrUtilities/File/Nifti/cbiCreateNiftiHeader.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m b/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m index 0c76f3d6f..4eca3e143 100644 --- a/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m +++ b/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m @@ -163,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 @@ -194,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,:); From 1f1b4b65d14dd9540627ccea8dc28b1a6a10512f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 13 Aug 2020 10:40:13 +0300 Subject: [PATCH 089/254] freesurferSphericalNormalizationVolumes.m: added option to crop output volume to smallest volume containing surfaces --- .../freesurferSphericalNormalizationVolumes.m | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index aaa989ebf..71856ac39 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -22,6 +22,7 @@ % 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.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) @@ -65,6 +66,9 @@ if ieNotDefined('recomputeRemapping') params.recomputeRemapping = false; end +if ieNotDefined('cropDestVolume') + params.cropDestVolume = true; +end if fieldIsNotDefined(params,'dryRun') params.dryRun = false; end @@ -220,6 +224,7 @@ 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 @@ -235,12 +240,14 @@ 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} '.off']); destSurf{iSurf} = loadSurfOFF([sourcePath '/surfRelax/' params.fsSourceSubj '_' side{iSide} '_' surfs{iSurf} '_' 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); @@ -255,20 +262,41 @@ 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,nSides); + 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 source surface and destination volume + % compute mapping between destination surface and destination volume destCoords = permute(destCoords,[1 4 3 2]); - flat2volumeMap{iSide} = inverseBaseCoordMap(destCoords,destHdr.dim(2:4)'); + 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); @@ -280,14 +308,14 @@ % interpolate data to destination volume for iSource = 1:nSources - destData = nan(destHdr.dim(2:4)'); + destData = nan(uncroppedDestDims); % get source volume data [sourceData,sourceHdr] = mlrImageReadNifti(params.sourceVol{iSource}); 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),params.interpMethod); % transform surface data to destination volume - thisData = applyInverseBaseCoordMap(flat2volumeMap{iSide},destHdr.dim(2:4)',surfData); + thisData = applyInverseBaseCoordMap(surf2volumeMap{iSide},uncroppedDestDims,surfData); 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 @@ -300,6 +328,9 @@ 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 From a2d081672de220fa5c1a45b2750e2b1561f978bd Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 13 Aug 2020 19:41:50 +0300 Subject: [PATCH 090/254] cbiWriteNifti.m: issue error if provided data and subset size differ --- mrUtilities/File/Nifti/cbiWriteNifti.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mrUtilities/File/Nifti/cbiWriteNifti.m b/mrUtilities/File/Nifti/cbiWriteNifti.m index ba364a426..222c1d3de 100644 --- a/mrUtilities/File/Nifti/cbiWriteNifti.m +++ b/mrUtilities/File/Nifti/cbiWriteNifti.m @@ -97,6 +97,10 @@ 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); From 0800c411c40a373ab447f3b78cb85d52486ab8c8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 13 Aug 2020 19:45:30 +0300 Subject: [PATCH 091/254] cbiReadNifti.m: output data size was wrong when readin restricted subset in x,y,z for restricted number of volumes because data file was read from start of file instead of first volume to read --- mrUtilities/File/Nifti/cbiReadNifti.m | 51 +++++++++++++++------------ 1 file changed, 28 insertions(+), 23 deletions(-) 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); From 4c1e3ce21d71800e416f790364854ec4009a53db Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 13 Aug 2020 19:47:35 +0300 Subject: [PATCH 092/254] mlrSphericalNormGroup.m: added option to crop concatenated scans to smallest volume containing data from all subjects --- .../groupAnalysis/mlrSphericalNormGroup.m | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 75b112731..dcedf6787 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -30,6 +30,7 @@ % params.lrFlippedFreesurferTemplateID If combineLeftAndRight is true, name of the Freesurfer folder containing the left-right flipped surfaces of the left-right-flipped template % params.computeGroupAverage If true, normalized overlays will be averaged across subjects (optionally hemispheres) (default: true) % params.templateOverlayNewNames New names of the converted overlays +% 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) @@ -93,6 +94,9 @@ if fieldIsNotDefined(params,'templateOverlayNewNames') params.templateOverlayNewNames = {}; % New names of the converted overlays 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 @@ -207,7 +211,7 @@ deleteView(thisView); % close view without saving (note that because there should be only one view in global variable MLR, this deletes MLR too % transform subject scans to group space - fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.freesurferTemplateID); + 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}; @@ -217,7 +221,7 @@ fsSphericalParams.dryRun = params.dryRun; fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); if params.combineLeftAndRight - fprintf('(mlrSphericalNormGroup) Converting data to %s template space ...\n',params.lrFlippedFreesurferTemplateID); + 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); @@ -237,26 +241,28 @@ cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; if iSubj==1 && iOverlay == 1 templateScanName{1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s',mat2str(params.subjectOverlays),mat2str(params.subjectOverlayAnalyses),mat2str(params.subjectOverlayGroups)); + scanFileName{1} = fullfile(templateTseriesFolder,[templateScanName{1} '.nii']); if params.combineLeftAndRight templateScanName{2} = [templateScanName{1} '_LRcombined']; + scanFileName{2} = fullfile(templateTseriesFolder,[templateScanName{2} '.nii']); end end [data,hdr] = cbiReadNifti(convertNames{iOverlay,1}); hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything - cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{1} '.nii']),data,hdr,'',{[],[],[],cOverlayConcat}); + cbiWriteNifti(scanFileName{1},data,hdr,'',{[],[],[],cOverlayConcat}); % for log file logfile.overlay{cOverlayConcat,1} = params.templateOverlayNewNames{iOverlay}; logfile.subject{cOverlayConcat,1} = params.mrLoadRetSubjectIDs{iSubj}; if params.combineLeftAndRight - cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{2} '.nii']),data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); + cbiWriteNifti(scanFileName{2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'normal'; logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; [data,hdr] = cbiReadNifti(convertNames{iOverlay,2}); hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything - cbiWriteNifti(fullfile(templateTseriesFolder,[templateScanName{2} '.nii']),data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); + cbiWriteNifti(scanFileName{2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'reversed'; logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; @@ -273,8 +279,35 @@ end if ~params.dryRun - fprintf('(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); + nScans = length(templateScanName); + + if params.cropScans + for iScan = 1:nScans + fprintf('\n(mlrSphericalNormGroup) Cropping scan %d\n',iScan); + cropBox = [inf -inf;inf -inf;inf -inf]; + croppedScanFileName = fullfile(templateTseriesFolder,[templateScanName{iScan} '_cropped.nii']); + scanHdr = cbiReadNiftiHeader(scanFileName{iScan}); + for iVolume = 1:scanHdr.dim(5) + data = cbiReadNifti(scanFileName{iScan},{[],[],[],iVolume}); + [X,Y,Z] = ind2sub(scanHdr.dim(2:4)',find(~isnan(data))); % 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{iScan},{cropBox(1,:),cropBox(2,:),cropBox(3,:),iVolume}); + cbiWriteNifti(croppedScanFileName,data,scanHdr,'',{[],[],[],iVolume}); + end + movefile(croppedScanFileName,scanFileName{iScan}); + end + end + + fprintf('\n(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); % initialize mrLoadRet view/import group cd(mrLoadRetTemplateFolder); if initMrLoadRetGroup From 96564d6e91a0903c3634be98adf89ea8de2747b4 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 14 Aug 2020 14:24:18 +0300 Subject: [PATCH 093/254] mlrGroupAverage.m: now works for multiple factors and interaction between factors --- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 86 +++++++++++++++-------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index 071478783..79962c53a 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -70,9 +70,13 @@ if fieldIsNotDefined(params,'factors') params.factors = allFactors; +elseif ischar(params.factors) + params.factors = {params.factors}; end if fieldIsNotDefined(params,'averagingMode') params.averagingMode = 'marginal'; % options are 'marginal' or 'interaction' +elseif ~ismember(params.averagingMode,{'marginal','interaction'}) + mrErrorDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); end if justGetParams, return; end @@ -82,11 +86,9 @@ if ~all(ismember(params.factors,commonFactors)) mrErrorDlg('(mlrGroupAverage) Cannot compute averages because factors are missing in some scans'); end - factorNames{1} = params.factors; + whichFactors = {1: length(params.factors)}; else - for iFactor = 1:length(params.factors) - factorNames{iFactor} = params.factors(iFactor); - end + whichFactors = num2cell(1:length(params.factors)); end thisView = viewSet(thisView,'curGroup',params.groupNum); @@ -94,28 +96,53 @@ for iScan = 1:length(params.scanList) tseriesPath{iScan} = viewGet(thisView,'tseriespathstr',params.scanList(iScan)); hdr{iScan} = cbiReadNiftiHeader(tseriesPath{iScan}); - for iFactor = 1:length(factorNames) - levels{iScan} = []; - for jFactor = 1:length(factorNames{iFactor}) - % check that the number of volumes matches the number of elements in the factor variables - if length(factors{iScan}.(factorNames{iFactor}{jFactor})) ~= hdr{iScan}.dim(5) - mrErrorDlg(sprintf('(mlrGroupAverage) Scan %d: Mismatched number of volumes between .mat variable ''%s'' (%d) and time series file (%d)', ... - params.scanList(iScan),factorNames{iFactor}{jFactor},length(factors{iScan}.(factorNames{iFactor}{jFactor})),hdr{iScan}.dim(5))); - end - if size(factors{iScan}.(factorNames{iFactor}{jFactor}),1)==1 - factors{iScan}.(factorNames{iFactor}{jFactor}) = factors{iScan}.(factorNames{iFactor}{jFactor})'; % make sure the factor is a column cell array - end - levels{iScan} = [levels{iScan} factors{iScan}.(factorNames{iFactor}{jFactor})]; + for iFactor = 1:length(params.factors) + % check that the field exists for this scan + if ~isfield(factors{iScan},params.factors{iFactor}) + mrErrorDlg(sprintf('(mlrGroupAverage) Variable ''%s'' does not exist in scan %d', params.factors{iFactor}, params.scanList(iScan))); + end + % 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) + mrErrorDlg(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))); end - if iFactor == 1 && iScan ==1 - uniqueLevels = unique(levels{iScan},'rows','stable'); - else - uniqueLevels = union(uniqueLevels,levels{iScan},'rows','stable'); + 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 + if iScan ==1 + allLevels = levels{iScan}; + else + allLevels = [allLevels; levels{iScan}]; end end -nLevels = size(uniqueLevels,1); +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'); % do 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 = 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)] = unique(levelNums{iScan}(:,whichFactors{iFactor}),'rows'); + whichOverlay{iScan}(:,iFactor) = whichOverlay{iScan}(:,iFactor) + nLevels; + end + % get corresponding unique level names + for iLevel = 1:size(uniqueLevelNums,1) + uniqueLevels{nLevels+iLevel} = [allLevels{uniqueLevelIndices(iLevel),whichFactors{iFactor}}]; + end + nLevels = nLevels + size(uniqueLevelNums,1); +end + +% initialize scans with zeros for iOverlay = 1:nLevels for iScan = 1:length(params.scanList) overlays(iOverlay).data{params.scanList(iScan)} = zeros(hdr{iScan}.dim(2:4)'); @@ -126,14 +153,17 @@ maxOverlay = -1*inf(nLevels,1); for iScan = 1:length(params.scanList) volumeCount = zeros(nLevels,1); - for iVolume = 1:hdr{iScan}.dim(5) - [~,whichOverlay] = ismember(levels{iScan}(iVolume),uniqueLevels); - % add data - overlays(whichOverlay).data{params.scanList(iScan)} = overlays(whichOverlay).data{params.scanList(iScan)} + cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); - volumeCount(whichOverlay) = volumeCount(whichOverlay) + 1; + for iVolume = 1:hdr{iScan}.dim(5) %for each volume in the scan + data = cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); % read the data + for iFactor = 1:length(whichFactors) % add it to the appropriate overlay(s) + overlays(whichOverlay{iScan}(iVolume,iFactor)).data{params.scanList(iScan)} = overlays(whichOverlay{iScan}(iVolume,iFactor)).data{params.scanList(iScan)} + data; + volumeCount(whichOverlay{iScan}(iVolume,iFactor)) = volumeCount(whichOverlay{iScan}(iVolume,iFactor)) + 1; + end end - for iOverlay = 1:nLevels - overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}/volumeCount(whichOverlay),mrGetPref('defaultPrecision')); + for iOverlay = 1:nLevels %for each overlay + % divide by the number of added overlays + overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}/volumeCount(iOverlay),mrGetPref('defaultPrecision')); + % get min and max minOverlay(iOverlay) = min(minOverlay(iOverlay),min(overlays(iOverlay).data{params.scanList(iScan)}(:))); maxOverlay(iOverlay) = max(maxOverlay(iOverlay),max(overlays(iOverlay).data{params.scanList(iScan)}(:))); end From 69eb51aacae830c3c92544cb6ca93a69f6541095 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 14 Aug 2020 15:09:15 +0300 Subject: [PATCH 094/254] mlrGroupAverage.m: takes NaNs into account when averaging and optionally outputs map of actual number of subjects averaged --- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 50 +++++++++++++++-------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index 79962c53a..c7f905249 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -25,6 +25,8 @@ % 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.outputNonNaNcount : 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) @@ -78,6 +80,9 @@ elseif ~ismember(params.averagingMode,{'marginal','interaction'}) mrErrorDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); end +if fieldIsNotDefined(params,'outputNonNaNcount') + params.outputNonNaNcount = false; +end if justGetParams, return; end @@ -142,39 +147,48 @@ nLevels = nLevels + size(uniqueLevelNums,1); end -% initialize scans with zeros -for iOverlay = 1:nLevels - for iScan = 1:length(params.scanList) - overlays(iOverlay).data{params.scanList(iScan)} = zeros(hdr{iScan}.dim(2:4)'); - end -end + minOverlay = inf(nLevels,1); maxOverlay = -1*inf(nLevels,1); +maxCount = 0; for iScan = 1:length(params.scanList) - volumeCount = zeros(nLevels,1); - for iVolume = 1:hdr{iScan}.dim(5) %for each volume in the scan - data = cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); % read the data - for iFactor = 1:length(whichFactors) % add it to the appropriate overlay(s) - overlays(whichOverlay{iScan}(iVolume,iFactor)).data{params.scanList(iScan)} = overlays(whichOverlay{iScan}(iVolume,iFactor)).data{params.scanList(iScan)} + data; - volumeCount(whichOverlay{iScan}(iVolume,iFactor)) = volumeCount(whichOverlay{iScan}(iVolume,iFactor)) + 1; - end - end for iOverlay = 1:nLevels %for each overlay + overlays(iOverlay).data{params.scanList(iScan)} = zeros(hdr{iScan}.dim(2:4)'); % initialize scans with zeros + volumeCount = zeros(hdr{iScan}.dim(2:4)'); + for iVolume = find(whichOverlay{iScan}(:,iFactor)==iOverlay)' %for each volume in the scan matching this (combination of) condition(s) + data = cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); % read the data + isNotNaN = ~isnan(data); + for iFactor = 1:length(whichFactors) + % add non-NaN values to the appropriate overlay(s) + overlays(iOverlay).data{params.scanList(iScan)}(isNotNaN) = ... + overlays(iOverlay).data{params.scanList(iScan)}(isNotNaN) + data(isNotNaN); + volumeCount = volumeCount + isNotNaN; + end + end % divide by the number of added overlays - overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}/volumeCount(iOverlay),mrGetPref('defaultPrecision')); + overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}./volumeCount,mrGetPref('defaultPrecision')); % get min and max minOverlay(iOverlay) = min(minOverlay(iOverlay),min(overlays(iOverlay).data{params.scanList(iScan)}(:))); maxOverlay(iOverlay) = max(maxOverlay(iOverlay),max(overlays(iOverlay).data{params.scanList(iScan)}(:))); + if params.outputNonNaNcount + overlays(nLevels+iOverlay).data{params.scanList(iScan)} = volumeCount; + maxCount = max(maxCount,max(volumeCount(:))); + end end end %add overlays' missing fields -for iOverlay = 1:nLevels - overlays(iOverlay).name = uniqueLevels{iOverlay}; +for iOverlay = 1:nLevels*(1+params.outputNonNaNcount) + if iOverlay<=nLevels + overlays(iOverlay).name = uniqueLevels{iOverlay}; + overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; + else + overlays(iOverlay).name = sprintf('N (%s)',uniqueLevels{iOverlay-nLevels}); + overlays(iOverlay).range = [0 maxCount]; + end overlays(iOverlay).groupName = viewGet(thisView,'groupName'); overlays(iOverlay).params = params; - overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; overlays(iOverlay).type = 'Group average'; overlays(iOverlay).function = 'mlrGroupAverage'; overlays(iOverlay).interrogator = ''; From 49437ee3ffd4bab445bc33c6d72131f1f87efa4f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 15 Aug 2020 15:58:44 +0300 Subject: [PATCH 095/254] copyNiftiFile.m: if the file path is identical to the file itself, skip the copying --- mrUtilities/File/Nifti/copyNiftiFile.m | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mrUtilities/File/Nifti/copyNiftiFile.m b/mrUtilities/File/Nifti/copyNiftiFile.m index a6cd6a7b0..ecd879ed3 100644 --- a/mrUtilities/File/Nifti/copyNiftiFile.m +++ b/mrUtilities/File/Nifti/copyNiftiFile.m @@ -64,12 +64,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; From 479eded0074b3688d96db509a93e3b967d3a0c50 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 15 Aug 2020 16:02:27 +0300 Subject: [PATCH 096/254] importTSeries.m, saveNewTSeries.m, copyNiftiFile.m: added option to overwrite without prompt (for scripting) --- mrLoadRet/File/importTSeries.m | 3 ++- mrLoadRet/File/saveNewTSeries.m | 11 ++++++----- mrUtilities/File/Nifti/copyNiftiFile.m | 12 +++++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/File/importTSeries.m b/mrLoadRet/File/importTSeries.m index 15e3308c4..376d88168 100644 --- a/mrLoadRet/File/importTSeries.m +++ b/mrLoadRet/File/importTSeries.m @@ -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/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/mrUtilities/File/Nifti/copyNiftiFile.m b/mrUtilities/File/Nifti/copyNiftiFile.m index ecd879ed3..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); From 523149a33db6b332c91dc81fc8f9205856851592 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 15 Aug 2020 16:07:56 +0300 Subject: [PATCH 097/254] viewGet.m (case groupNum): do not disply message if group does not exist, as this might be the expected result and a warning message would be misleading in this case --- mrLoadRet/View/viewGet.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index d17b4f0d7..5506c7239 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -289,9 +289,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')) From 300795638540cbc89c7fff78143e6bfeae69026b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 15 Aug 2020 16:11:01 +0300 Subject: [PATCH 098/254] mlrGroupAverage.m: returns if any scan has no linked log file --- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index c7f905249..3ec67990e 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -51,24 +51,30 @@ %read log files associated with scans cScan = 0; +noLinkedFile=false; for iScan = params.scanList cScan= cScan+1; logFileName = viewGet(thisView,'stimfilename',iScan); - if isempty(logFileName{1}) + if isempty(logFileName) mrWarnDlg(sprintf('(mlrGroupAverage) No mat file linked to scan %d', iScan)); - end - factors{cScan} = load(logFileName{1}); - if isempty(factors{cScan}) - mrWarnDlg(sprintf('(mlrGroupAverage) Cannot open file %s for scan %d', logFileName, iScan)); - end - if iScan == 1 - commonFactors = fieldnames(factors{cScan}); - allFactors = commonFactors; + noLinkedFile = true; else - commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); - allFactors = union(allFactors,fieldnames(factors{cScan})); + factors{cScan} = load(logFileName{1}); + if isempty(factors{cScan}) + mrWarnDlg(sprintf('(mlrGroupAverage) Cannot open file %s for scan %d', logFileName, iScan)); + end + if iScan == 1 + commonFactors = fieldnames(factors{cScan}); + allFactors = commonFactors; + else + commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); + allFactors = union(allFactors,fieldnames(factors{cScan})); + end end end +if noLinkedFile + return; +end if fieldIsNotDefined(params,'factors') params.factors = allFactors; From 1c82f475372db21ca345bd143916939590f1f1f1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 18 Aug 2020 22:04:36 +0300 Subject: [PATCH 099/254] mlrSphericalNorGroup.m: added option to apply one of several transformation functions to data that gets LR-flipped + modified warning messages in freesurferSphericalNormalizationVolumes.m --- .../groupAnalysis/mlrSphericalNormGroup.m | 472 ++++++++++++------ .../freesurferSphericalNormalizationVolumes.m | 6 +- 2 files changed, 318 insertions(+), 160 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index dcedf6787..79172878f 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -1,4 +1,4 @@ -% function params = mlrSphericalNormGroup(params,<'justGetParams'>) +% function [success,params] = mlrSphericalNormGroup(params,<'justGetParams'>) % % goal: Exports overlay data from multiple subjects located in the same study folder % into a common template space using Freesurfer's spherical normalization (keeping @@ -14,7 +14,7 @@ % 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.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) @@ -22,12 +22,16 @@ % params.freesurferTemplateID Freesurfer subject IF 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 (deafult: 'Spherical Normalization') % params.mrLoadRetTemplateLastView Name of the saved last view in the template MLR folder (default: mrLastView.mat) -% params.subjectOverlayGroups Group numbers of the overlays to normalize -% params.subjectOverlayAnalyses Analysis numbers of the overlays to normalize -% params.subjectOverlays Overlay numbers to normalize -% params.templateOverlaysGroup In what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported +% 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.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 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.cropScans Whether to crop concatenated scans to smallest volume including all subject's data (default = true) @@ -35,8 +39,9 @@ % % author: julien besle (28/07/2020) -function params = mlrSphericalNormGroup(params,varargin) +function [success,params] = mlrSphericalNormGroup(params,varargin) +success = true; eval(evalargs(varargin)); if ieNotDefined('justGetParams'),justGetParams = 0;end @@ -79,8 +84,11 @@ if fieldIsNotDefined(params,'subjectOverlays') params.subjectOverlays = 1; % overlay numbers to normalize end -if fieldIsNotDefined(params,'templateOverlaysGroup') - params.templateOverlaysGroup = params.freesurferTemplateID; % in what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported +if fieldIsNotDefined(params,'templateOverlayGroupNums') + params.templateOverlayGroupNums = 1; % 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; % in what group of the group mrLoadRet folder the group-normalized overlays (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 @@ -88,6 +96,12 @@ if fieldIsNotDefined(params,'lrFlippedFreesurferTemplateID') params.lrFlippedFreesurferTemplateID = ''; % 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 @@ -101,28 +115,74 @@ params.dryRun = false; % if true, just check whether the overlays and surfaces exist end -if justGetParams, return; end +if justGetParams + return; +else + success = false; +end nSubjects = length(params.mrLoadRetSubjectIDs); nOverlays = length(params.subjectOverlays); +nTemplateGroups = max(params.templateOverlayGroupNums); + +if numel(params.subjectOverlayGroups)==1 + params.subjectOverlayGroups = repmat(params.subjectOverlayGroups,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.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 nOverlays ~= length(params.subjectOverlayGroups) - mrWarnDlg('(mlrSphericalNormGroup) Specify the same number of overlays and groups') + 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.subjectOverlayGroups) - mrWarnDlg('(mlrSphericalNormGroup) Specify the same number of overlays and analyses') +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 nOverlays ~= length(params.lrFlipTransform) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) lrFlipTransform must be a scalar or a vector of same length as the number of subject overlays (%d)',nOverlays)) + return; +end +if 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 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; @@ -136,29 +196,51 @@ % create mrLoadRet group folder mrLoadRetTemplateFolder = fullfile(params.studyDir,params.mrLoadRetTemplateID); -templateTseriesFolder = fullfile(mrLoadRetTemplateFolder,params.templateOverlaysGroup,'TSeries'); +temporaryTseriesFolder = fullfile(params.studyDir,'tempTSeries'); +mkdir(temporaryTseriesFolder); +for iGroup = 1: nTemplateGroups + templateTseriesFolder{iGroup} = fullfile(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{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', params.templateOverlaysGroup)); + 'operator=Created by mlrSphericalNormGroup','defaultParams=1',sprintf('defaultGroup=%s', params.templateOverlayGroupNames{1})); + for iGroup = 2: nTemplateGroups + mkdir(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{iGroup}); + mkdir(templateTseriesFolder{iGroup}); + end else initMrLoadRetGroup = false; - mkdir(mrLoadRetTemplateFolder,params.templateOverlaysGroup); - mkdir(fullfile(mrLoadRetTemplateFolder,params.templateOverlaysGroup),'TSeries'); + cd(mrLoadRetTemplateFolder) + thisView = mrLoadRet([],'No GUI'); + groupExists = false; + for iGroup = 1: nTemplateGroups + if isempty(viewGet(thisView,'groupnum',params.templateOverlayGroupNames{iGroup})) + mkdir(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{iGroup}); + mkdir(templateTseriesFolder{iGroup}); + else + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Group %s already exists in template folder %s',params.templateOverlayGroupNames{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 -cOverlayConcat = 0; -cOverlayConcatLRcombined = 0; +cOverlayConcat = zeros(nTemplateGroups,1); +cOverlayConcatLRcombined = zeros(nTemplateGroups,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(templateTseriesFolder)+1) - (length(params.mrLoadRetSubjectIDs{iSubj})+1) ... - - (max(params.combineLeftAndRight*(length(params.lrFlippedFreesurferTemplateID)),length(params.freesurferTemplateID))+1) - 7; + maxNcharacters = 259 - (length(temporaryTseriesFolder)+1) - (length(params.mrLoadRetSubjectIDs{iSubj})+1) ... + - (max(params.combineLeftAndRight*(length(params.lrFlippedFreesurferTemplateID)),length(params.freesurferTemplateID))+1) - 4; cd(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{iSubj})); thisView = mrLoadRet(params.mrLoadRetSubjectLastView,'No GUI'); @@ -166,67 +248,119 @@ cOverlay = 0; exportNames = cell(0); convertNames = cell(0,1+params.combineLeftAndRight); + overlayExists = false(1,nOverlays); for iOverlay = 1:nOverlays - thisView = viewSet(thisView,'curGroup',params.subjectOverlayGroups(iOverlay)); - thisView = viewSet(thisView,'curAnalysis',params.subjectOverlayAnalyses(iOverlay)); - if params.subjectOverlays(iOverlay) <= viewGet(thisView,'nOverlays') - thisView = viewSet(thisView,'curOverlay',params.subjectOverlays(iOverlay)); - fprintf('(mlrSphericalNormGroup) Will export Group %d, Analysis %d, overlay %d: %s\n',params.subjectOverlayGroups(iOverlay),params.subjectOverlayAnalyses(iOverlay),params.subjectOverlays(iOverlay),viewGet(thisView,'overlayName')); - cOverlay = cOverlay + 1; - overlayExists = true; - else - mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find overlay %d in group %d, analysis %d...',params.subjectOverlays(iOverlay),params.subjectOverlayGroups(iOverlay),params.subjectOverlayAnalyses(iOverlay))); - if ~params.dryRun - return; + 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 - overlayExists=false; + groupNum = params.subjectOverlayGroups{iOverlay}; end + else + groupNum = params.subjectOverlayGroups(iOverlay); end - - if overlayExists - groupName = viewGet(thisView,'groupName'); - if endsWith(groupName,'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} = sprintf('%02d_%s',iOverlay,fixBadChars(viewGet(thisView,'overlayName'),badCharFixlist,[],maxNcharacters)); - if iSubj==1 - if isempty(params.templateOverlayNewNames) - params.templateOverlayNewNames{iOverlay} = overlayBaseName{iOverlay}; + 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'); + 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 - params.templateOverlayNewNames{iOverlay} = sprintf('%02d_%s',iOverlay,fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters)); + 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,'AnalysisNum',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 + + if overlayExists(iOverlay) + if endsWith(groupName,'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 iSubj==1 + if isempty(params.templateOverlayNewNames) + params.templateOverlayNewNames{iOverlay} = overlayBaseName{iOverlay}; + else + params.templateOverlayNewNames{iOverlay} = 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 - % export overlays to NIFTI in scan space - exportNames{cOverlay} = fullfile(templateTseriesFolder,sprintf('%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj})); - convertNames{cOverlay,1} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); - if params.combineLeftAndRight - convertNames{cOverlay,2} = fullfile(templateTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); - end - if ~params.dryRun - mrExport2SR(thisView,exportNames{cOverlay},0); 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 - % 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.fsDestSubj = params.freesurferTemplateID; - fsSphericalParams.destVol = convertNames(:,1); - fsSphericalParams.interpMethod = 'linear'; - 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); + if all(overlayExists) || 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.fsDestSubj = params.freesurferTemplateID; + fsSphericalParams.destVol = convertNames(:,1); + fsSphericalParams.interpMethod = 'linear'; + 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 @@ -236,43 +370,47 @@ % Concatenate normalized subject data fprintf('\n(mlrSphericalNormGroup) Concatenating transformed data for subject %s...\n',params.mrLoadRetSubjectIDs{iSubj}); - for iOverlay = 1:nOverlays - cOverlayConcat = cOverlayConcat+1; - cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; - if iSubj==1 && iOverlay == 1 - templateScanName{1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s',mat2str(params.subjectOverlays),mat2str(params.subjectOverlayAnalyses),mat2str(params.subjectOverlayGroups)); - scanFileName{1} = fullfile(templateTseriesFolder,[templateScanName{1} '.nii']); - if params.combineLeftAndRight - templateScanName{2} = [templateScanName{1} '_LRcombined']; - scanFileName{2} = fullfile(templateTseriesFolder,[templateScanName{2} '.nii']); + for iGroup = 1:nTemplateGroups + overlays = find(params.templateOverlayGroupNums==iGroup); + for iOverlay = overlays + cOverlayConcat(iGroup) = cOverlayConcat(iGroup)+1; + cOverlayConcatLRcombined(iGroup) = cOverlayConcatLRcombined(iGroup)+1; + if iSubj==1 && iOverlay == overlays(1) + templateScanName{iGroup,1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s', ... + mat2str(params.subjectOverlays(overlays)),mat2str(params.subjectOverlayAnalyses(overlays)),mat2str(params.subjectOverlayGroups(overlays))); + scanFileName{iGroup,1} = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,1} '.nii']); + if params.combineLeftAndRight + templateScanName{iGroup,2} = [templateScanName{iGroup,1} '_LRcombined']; + scanFileName{iGroup,2} = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,2} '.nii']); + end end - end - [data,hdr] = cbiReadNifti(convertNames{iOverlay,1}); - hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything - cbiWriteNifti(scanFileName{1},data,hdr,'',{[],[],[],cOverlayConcat}); - % for log file - logfile.overlay{cOverlayConcat,1} = params.templateOverlayNewNames{iOverlay}; - logfile.subject{cOverlayConcat,1} = params.mrLoadRetSubjectIDs{iSubj}; - - if params.combineLeftAndRight - cbiWriteNifti(scanFileName{2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); - logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; - logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'normal'; - logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; - cOverlayConcatLRcombined = cOverlayConcatLRcombined+1; - [data,hdr] = cbiReadNifti(convertNames{iOverlay,2}); + [data,hdr] = cbiReadNifti(convertNames{iOverlay,1}); hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything - cbiWriteNifti(scanFileName{2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined}); - logfileCombined.overlay{cOverlayConcatLRcombined,1} = params.templateOverlayNewNames{iOverlay}; - logfileCombined.leftRightDirection{cOverlayConcatLRcombined,1} = 'reversed'; - logfileCombined.subject{cOverlayConcatLRcombined,1} = params.mrLoadRetSubjectIDs{iSubj}; - end - end - - % delete temporary converted subject files - for iOverlay = 1:nOverlays - for iCombine = 1:1+params.combineLeftAndRight - delete(convertNames{iOverlay,iCombine}); + cbiWriteNifti(scanFileName{iGroup,1},data,hdr,'',{[],[],[],cOverlayConcat(iGroup)}); + % for log file + logfile{iGroup,1}.overlay{cOverlayConcat(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,1}.subject{cOverlayConcat(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; + + if params.combineLeftAndRight + cbiWriteNifti(scanFileName{iGroup,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); + logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'normal'; + logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; + cOverlayConcatLRcombined(iGroup) = cOverlayConcatLRcombined(iGroup)+1; + [data,hdr] = cbiReadNifti(convertNames{iOverlay,2}); + if params.lrFlipTransform(iOverlay) + data = params.lrFlipTransformFunctions{params.lrFlipTransform(iOverlay)}(data); + end + hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything + cbiWriteNifti(scanFileName{iGroup,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); + logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'reversed'; + logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; + end + % delete temporary converted subject files + for iCombine = 1:1+params.combineLeftAndRight + delete(convertNames{iOverlay,iCombine}); + end end end end @@ -280,48 +418,51 @@ if ~params.dryRun - nScans = length(templateScanName); + nScans = size(templateScanName,2); if params.cropScans - for iScan = 1:nScans - fprintf('\n(mlrSphericalNormGroup) Cropping scan %d\n',iScan); - cropBox = [inf -inf;inf -inf;inf -inf]; - croppedScanFileName = fullfile(templateTseriesFolder,[templateScanName{iScan} '_cropped.nii']); - scanHdr = cbiReadNiftiHeader(scanFileName{iScan}); - for iVolume = 1:scanHdr.dim(5) - data = cbiReadNifti(scanFileName{iScan},{[],[],[],iVolume}); - [X,Y,Z] = ind2sub(scanHdr.dim(2:4)',find(~isnan(data))); % 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{iScan},{cropBox(1,:),cropBox(2,:),cropBox(3,:),iVolume}); - cbiWriteNifti(croppedScanFileName,data,scanHdr,'',{[],[],[],iVolume}); + for iGroup = 1:nTemplateGroups + for iScan = 1:nScans + 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))); % 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 - movefile(croppedScanFileName,scanFileName{iScan}); end end - fprintf('\n(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); % initialize mrLoadRet view/import group + fprintf('\n(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); cd(mrLoadRetTemplateFolder); if initMrLoadRetGroup - scanList = 1:nScans; + scanList{1} = 1:nScans; subjectSessionParams = load(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{1},'mrSession.mat')); [sessionParams, groupParams] = mrInit([],[],'justGetParams=1','defaultParams=1'); - for iScan = scanList - groupParams.description{iScan} = templateScanName{iScan}; + 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'); - % load base anatomies + templateGroupNums{1} = 1; + % load base anatomies: % load whole-head MPRAGE anatomy thisView = mrLoadRet(params.mrLoadRetTemplateLastView,'No GUI'); [fsPath,filename,ext] = fileparts(fsSphericalParamsOut.destSurfRelaxVolume); @@ -343,40 +484,57 @@ 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 %%%%%%%%%%%%% NOT TESTED + + else thisView = mrLoadRet(params.mrLoadRetTemplateLastView,'No GUI'); - thisView = viewSet(thisView,'newGroup',params.templateOverlaysGroup); - [~,importParams] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1'); - for iCombine = 1:1+params.combineLeftAndRight - importParams.pathname = templateFileName{iCombine}; - importTSeries(thisView,importParams); - end - scanList = viewGet(thisView,'nScans')-[nScans:-1:1]+1; end - % create log files indicating which volumes correspond to which overlays and subjects - for iCombine = 1:1+params.combineLeftAndRight - if iCombine==2 - logfile = logfileCombined; + % import (other) groups + for iGroup = 1+initMrLoadRetGroup:nTemplateGroups + thisView = viewSet(thisView,'newGroup',params.templateOverlayGroupNames{iGroup}); + templateGroupNums{iGroup} = viewGet(thisView,'groupNum',params.templateOverlayGroupNames{iGroup}); + thisView = viewSet(thisView,'curGroup',templateGroupNums{iGroup}); + for iScan = 1:nScans + [~,importParams] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1',['pathname=' scanFileName{iGroup,iScan}]); + importParams.description = templateScanName{iGroup,iScan}; + importParams.overwrite = 1; + thisView = importTSeries(thisView,importParams); end - logFilename = [templateScanName{iCombine} '.mat']; - save(fullfile(mrLoadRetTemplateFolder,'Etc',logFilename),'-struct','logfile'); - % link log file to concatenated scan - fprintf('Linking %s to scan %d\n',logFilename,scanList(iCombine)); - viewSet(thisView,'stimfilename',logFilename, scanList(iCombine)); + scanList{iGroup} = viewGet(thisView,'nScans')-[nScans:-1:1]+1; end - - if params.computeGroupAverage - [~,averageParams] = mlrGroupAverage(thisView,[],'justGetParams'); - averageParams.factors = {'overlay'}; - averageParams.scanList = scanList; - thisView = mlrGroupAverage(thisView,averageParams); - thisView = viewSet(thisView,'curOverlay',1); - thisView = viewSet(thisView,'clipAcrossOverlays',false); + + for iGroup = 1:nTemplateGroups + % create log files indicating which volumes correspond to which overlays and subjects + for iScan = 1:nScans + 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 params.computeGroupAverage(iGroup) + thisView = viewSet(thisView,'curGroup',templateGroupNums{iGroup}); + [~,averageParams] = mlrGroupAverage(thisView,[],'justGetParams'); + 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/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index 71856ac39..0b89109d3 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -86,7 +86,7 @@ nSources = length(params.sourceVol); for iSource = 1:nSources if ~exist(params.sourceVol{iSource},'file') - mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find destination volume %s',params.sourceVol{iSource})); + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source volume %s',params.sourceVol{iSource})); if ~params.dryRun return; end @@ -152,7 +152,7 @@ sourceSurfRelaxVolumes = [dir(fullfile(sourcePath,'surfRelax','*.nii')); dir(fullfile(sourcePath,'surfRelax','*.img'))]; switch(length(sourceSurfRelaxVolumes)) case 0 - mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find destination volume in %s',fullfile(destPath,'surfRelax'))); + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source surfRelax volume in %s',fullfile(destPath,'surfRelax'))); if ~params.dryRun return; end @@ -215,7 +215,7 @@ if fieldIsNotDefined(params,'destVolTemplate') params.destVolTemplate = params.destSurfRelaxVolume; elseif ~exist(params.destVolTemplate,'file') - mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Cannot find destination volume %s',params.destVolTemplate)); + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Cannot find destination volume template %s',params.destVolTemplate)); if ~params.dryRun return; end From 4149c179c820efba1668c14c9b693d648ecbbe0d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 19 Aug 2020 16:03:14 +0300 Subject: [PATCH 100/254] getNewSpaceOverlay.m: added function name in waitbar text --- mrLoadRet/GUI/getNewSpaceOverlay.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); From a81c6af60b56d3baa426d13ca67a2e25933e525c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 27 Aug 2020 14:11:27 +0300 Subject: [PATCH 101/254] renamed disppercent.m to mlrDispPercent.m and changed all calls --- mrAlign/talairach.m | 8 +-- .../Analysis/ConcatTSeries/concatTSeries.m | 10 ++-- .../EventRelated/eventRelatedHighpass.m | 12 ++-- .../EventRelated/eventRelatedMultiple.m | 18 +++--- .../Analysis/EventRelated/eventRelatedPlot.m | 10 ++-- .../Analysis/EventRelated/getGlmContrast.m | 12 ++-- mrLoadRet/Analysis/EventRelated/getr2.m | 12 ++-- mrLoadRet/Analysis/makeCorrelationMap.m | 6 +- mrLoadRet/Analysis/makeFlat.m | 16 ++--- mrLoadRet/Analysis/projectOutMeanVector.m | 18 +++--- mrLoadRet/Edit/editOverlayGUImrParams.m | 8 +-- mrLoadRet/File/importGroupScans.m | 6 +- mrLoadRet/File/loadROITSeries.m | 12 ++-- mrLoadRet/File/mlrExportForAnalysis.m | 4 +- mrLoadRet/File/mlrExportOFF.m | 30 +++++----- mrLoadRet/File/scanInfo.m | 6 +- mrLoadRet/GUI/mrFlatViewer.m | 22 +++---- mrLoadRet/GUI/mrLoadRetGUI.m | 8 +-- mrLoadRet/GUI/mrPrint.m | 14 ++--- mrLoadRet/GUI/mrQuit.m | 4 +- mrLoadRet/GUI/mrSaveView.m | 6 +- mrLoadRet/GUI/mrSurfViewer.m | 14 ++--- mrLoadRet/GUI/refreshMLRDisplay.m | 58 +++++++++---------- mrLoadRet/Plot/mlrDisplayEPI.m | 4 +- mrLoadRet/Plot/mlrSpikeDetector.m | 24 ++++---- .../Plugin/GLM_v2/newGlmAnalysis/glmPlot.m | 4 +- .../Plugin/mlrAnatomy/mlrAnatomyPlugin.m | 12 ++-- mrLoadRet/Plugin/pRF/pRFFit.m | 4 +- .../Plugin/pRF/pRFGetStimImageFromStimfile.m | 6 +- mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 4 +- .../eventRelatedROIClassification.m | 6 +- .../roiClassification.m | 6 +- .../searchlightClassification.m | 12 ++-- .../PluginAlt/mlrAnatDB/mlrAnatDBPlugin.m | 6 +- mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPush.m | 12 ++-- mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPut.m | 4 +- mrLoadRet/PluginAlt/mlrLife/mlrLifePlugin.m | 12 ++-- mrLoadRet/ROI/convertROICorticalDepth.m | 8 +-- mrLoadRet/View/mrOpenWindow.m | 20 +++---- mrLoadRet/View/viewSet.m | 6 +- mrUtilities/File/Caret/mlrImportCaret.m | 4 +- mrUtilities/File/Varian/fid2nifti.m | 6 +- mrUtilities/File/Varian/getfid.m | 10 ++-- mrUtilities/File/Varian/getfidk.m | 10 ++-- mrUtilities/File/mlrImage/mlrImageReslice.m | 6 +- mrUtilities/File/mlrImage/mlrVol.m | 12 ++-- mrUtilities/ImageProcessing/mrUpSample.m | 12 ++-- .../{disppercent.m => mlrDispPercent.m} | 28 ++++----- mrUtilities/MatlabUtilities/mrCloseDlg.m | 4 +- mrUtilities/MatlabUtilities/mrDisp.m | 2 +- mrUtilities/MatlabUtilities/mrWaitBar.m | 14 ++--- mrUtilities/surfUtils/calcCurvature.m | 6 +- mrUtilities/surfUtils/calcSurfaceNormals.m | 12 ++-- 53 files changed, 295 insertions(+), 295 deletions(-) rename mrUtilities/MatlabUtilities/{disppercent.m => mlrDispPercent.m} (88%) 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..562db9f94 100644 --- a/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m +++ b/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m @@ -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 e79e52961..9ff683cba 100644 --- a/mrLoadRet/Analysis/makeFlat.m +++ b/mrLoadRet/Analysis/makeFlat.m @@ -167,15 +167,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. @@ -272,9 +272,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 @@ -288,7 +288,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 @@ -315,9 +315,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/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index afb546e86..6a5968e66 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -142,12 +142,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 @@ -342,12 +342,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/File/importGroupScans.m b/mrLoadRet/File/importGroupScans.m index 6e40bd913..c644e4607 100644 --- a/mrLoadRet/File/importGroupScans.m +++ b/mrLoadRet/File/importGroupScans.m @@ -149,7 +149,7 @@ 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; @@ -199,9 +199,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; 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/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 22cc036a5..7a16ce12e 100644 --- a/mrLoadRet/File/mlrExportOFF.m +++ b/mrLoadRet/File/mlrExportOFF.m @@ -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/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/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/mrLoadRetGUI.m b/mrLoadRet/GUI/mrLoadRetGUI.m index abce4e081..68a92e123 100644 --- a/mrLoadRet/GUI/mrLoadRetGUI.m +++ b/mrLoadRet/GUI/mrLoadRetGUI.m @@ -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'); @@ -2677,7 +2677,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 +2723,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 index 943c04335..367c810b4 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -25,9 +25,9 @@ % grab the image -disppercent(-inf,'(mrPrint) Rerendering image'); +mlrDispPercent(-inf,'(mrPrint) Rerendering image'); [img base roi overlays altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); -disppercent(inf); +mlrDispPercent(inf); % validate rois validROIs = {}; @@ -363,7 +363,7 @@ if baseType ~= 2 sliceNum = viewGet(v,'currentSlice'); label = {}; - disppercent(-inf,'(mrPrint) Rendering ROIs'); + mlrDispPercent(-inf,'(mrPrint) Rendering ROIs'); visibleROIs = viewGet(v,'visibleROIs'); for rnum = 1:length(roi) % check for lines @@ -440,7 +440,7 @@ set(h,'FontSize',10); set(h,'HorizontalAlignment','center'); end - disppercent(inf); + mlrDispPercent(inf); end % bring up print dialog @@ -495,7 +495,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 @@ -589,9 +589,9 @@ roiRGB(:,:,2) = roiRGB(:,:,2) + roiBits.*color(2); roiRGB(:,:,3) = roiRGB(:,:,3) + roiBits.*color(3); roiMask = roiMask | roiBits; - disppercent(r/length(roi)); + mlrDispPercent(r/length(roi)); end end end -disppercent(inf); +mlrDispPercent(inf); diff --git a/mrLoadRet/GUI/mrQuit.m b/mrLoadRet/GUI/mrQuit.m index 1f356876e..cc862c6bd 100644 --- a/mrLoadRet/GUI/mrQuit.m +++ b/mrLoadRet/GUI/mrQuit.m @@ -82,9 +82,9 @@ fprintf('(mrQuit) Closing %i open views',viewCount); end drawnow - disppercent(-inf,sprintf('(mrQuit) Saving %s',mrDefaultsFilename)); + mlrDispPercent(-inf,sprintf('(mrQuit) Saving %s',mrDefaultsFilename)); saveMrDefaults; - disppercent(inf); + mlrDispPercent(inf); else if ~isempty(v) closereq; diff --git a/mrLoadRet/GUI/mrSaveView.m b/mrLoadRet/GUI/mrSaveView.m index 891bc01c5..eab8a19c7 100644 --- a/mrLoadRet/GUI/mrSaveView.m +++ b/mrLoadRet/GUI/mrSaveView.m @@ -37,7 +37,7 @@ function mrSaveView(v,lastViewFile) homeDir = viewGet(v,'homeDir'); try - disppercent(-inf,sprintf('(mrSaveView) Saving %s/%s',homeDir,lastViewFile)); + 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 @@ -56,8 +56,8 @@ function mrSaveView(v,lastViewFile) save(fullfile(homeDir,lastViewFile), 'view', 'viewSettings', '-v7.3'); end % save .mrDefaults in the home directory - disppercent(inf); + mlrDispPercent(inf); catch - disppercent(inf); + 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..ecbb78721 100644 --- a/mrLoadRet/GUI/mrSurfViewer.m +++ b/mrLoadRet/GUI/mrSurfViewer.m @@ -108,7 +108,7 @@ filepath = ''; -disppercent(-inf,'(mrSurfViewer) Loading surfaces'); +mlrDispPercent(-inf,'(mrSurfViewer) Loading surfaces'); % load the surface gSurfViewer.outerSurface = loadSurfOFF(sprintf('%s.off',stripext(outerSurface{1}))); @@ -200,7 +200,7 @@ if isempty(gSurfViewer.curv) return end -disppercent(inf); +mlrDispPercent(inf); curv{end+1} = 'Find file'; % guess any nifti file for anatomy @@ -716,11 +716,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 +729,7 @@ function switchAnatomy(params) gSurfViewer.whichSurface = 5; set(gParams.ui.varentry{1},'Value',gSurfViewer.whichSurface) refreshFlatViewer([],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%%% %% switchOverlays %% @@ -771,7 +771,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 +818,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 4faebe9e4..87639d58d 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -53,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 @@ -193,7 +193,7 @@ end end -if verbose>1,disppercent(-inf,'rendering');end +if verbose>1,mlrDispPercent(-inf,'rendering');end if ~isempty(fig) %this is really stupid: the ListboxTop property of listbox controls seems to be updated only when the control is drawn @@ -213,7 +213,7 @@ end end -if verbose>1,disppercent(inf);end +if verbose>1,mlrDispPercent(inf);end if verbose,toc,end % set pointer back @@ -230,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 @@ -250,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] = ... @@ -302,9 +302,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 @@ -323,7 +323,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 @@ -333,14 +333,14 @@ 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' @@ -402,7 +402,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') @@ -447,7 +447,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 @@ -545,10 +545,10 @@ 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 ROIs nROIs = viewGet(v,'numberOfROIs'); @@ -578,7 +578,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); @@ -600,7 +600,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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -770,13 +770,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 @@ -791,7 +791,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'); @@ -831,14 +831,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 @@ -854,7 +854,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) 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 verbose, mlrDispPercent(-inf,'(refreshMLRDisplay) Computing perimeter'); end baseCoordMap = viewGet(view,'baseCoordMap'); newy = []; for i = 1:length(y) @@ -870,9 +870,9 @@ function displayColorbar(gui,cmap,cbarRange,verbose) if numNeighbors(i) ~= numROINeighbors(i) newy = union(newy,baseCoordMap.tris(row(1),:)); end - if verbose, disppercent(i/length(y)); end; + if verbose, mlrDispPercent(i/length(y)); end; end - if verbose, disppercent(-inf); end; + if verbose, mlrDispPercent(-inf); end; disp(sprintf('%i/%i edges',length(newy),length(y))); y = newy; end @@ -1016,7 +1016,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/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/newGlmAnalysis/glmPlot.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m index 26ece2402..ed386dc19 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m @@ -515,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))); @@ -656,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 diff --git a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m index d48fb70e5..17ef42c3e 100644 --- a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m +++ b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m @@ -1006,7 +1006,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 @@ -1028,9 +1028,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,:); @@ -1377,7 +1377,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) @@ -1400,9 +1400,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/pRFFit.m b/mrLoadRet/Plugin/pRF/pRFFit.m index e81a57df7..8bf6cdd3b 100755 --- 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/pRFGetStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m index 47afd1b7b..4b2b13a6e 100755 --- 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_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 764dce5f8..219816e60 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -168,7 +168,7 @@ if ~isfield(fitParams.prefit,'modelResponse') % get number of workers nProcessors = mlrNumWorkers; - disppercent(-inf,sprintf('(pRF_somatoFit) Computing %i prefit model responses using %i processors',fitParams.prefit.n,nProcessors)); + 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; @@ -194,7 +194,7 @@ 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 - disppercent(inf); + mlrDispPercent(inf); fitParams.prefit.modelResponse = allModelResponse; clear allModelResponse; 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/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/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index 7541197a6..7dcfd6b41 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -104,7 +104,7 @@ % now go through and convert anything the user selected for roinum = params.roiList roiName = viewGet(v,'roiname', roinum); - disppercent(-inf,sprintf('(convertROICorticalDepth) Processing ROI %i:%s',roinum,roiName)); + 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 @@ -136,7 +136,7 @@ % get the roiBaseCoords roiBaseCoords = getROICoordinates(v,roinum,[],[],'baseNum',baseNum); if isempty(roiBaseCoords) - disppercent(inf); + mlrDispPercent(inf); mrWarnDlg(sprintf('(convertROICorticalDepth) %s has no coordinates on this flat',roiName)); continue; end @@ -160,7 +160,7 @@ % 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); + 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 @@ -250,7 +250,7 @@ % modifyROI to do it since we have called modifyROI twice) v = viewSet(v,'prevROIcoords',curROICoords); end - disppercent(inf); + 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/View/mrOpenWindow.m b/mrLoadRet/View/mrOpenWindow.m index b1e91965e..b185f78a6 100644 --- a/mrLoadRet/View/mrOpenWindow.m +++ b/mrLoadRet/View/mrOpenWindow.m @@ -110,11 +110,11 @@ 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); @@ -127,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 @@ -144,7 +144,7 @@ %try to load [view,baseLoaded] = loadAnatomy(view); end - disppercent(inf); + mlrDispPercent(inf); end % change group view = viewSet(view,'curGroup',mrLastView.curGroup); @@ -165,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 @@ -184,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 @@ -198,7 +198,7 @@ if ~fieldIsNotDefined(mrLastView,'roiGroup') view = viewSet(view,'roiGroup',mrLastView.roiGroup); end - disppercent(inf); + mlrDispPercent(inf); end if noGUI @@ -218,9 +218,9 @@ end % add here, to load more info... % and refresh - disppercent(-inf,sprintf('(mrOpenWindow) Refreshing MLR display')); + mlrDispPercent(-inf,sprintf('(mrOpenWindow) Refreshing MLR display')); refreshMLRDisplay(view.viewNum); - disppercent(inf); + mlrDispPercent(inf); end end diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 50189fc5b..dbd735176 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -1569,7 +1569,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; @@ -1663,9 +1663,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); 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/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/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/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/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/MatlabUtilities/disppercent.m b/mrUtilities/MatlabUtilities/mlrDispPercent.m similarity index 88% rename from mrUtilities/MatlabUtilities/disppercent.m rename to mrUtilities/MatlabUtilities/mlrDispPercent.m index 5c8f5f629..fc899c6aa 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 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.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/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/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..a47ae804d 100644 --- a/mrUtilities/surfUtils/calcSurfaceNormals.m +++ b/mrUtilities/surfUtils/calcSurfaceNormals.m @@ -20,7 +20,7 @@ % % % %---------- 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 +29,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 +71,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); From 0a490b86388f7cf71eb03165168f1d974ce3ead0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 22 Dec 2020 17:12:07 +0200 Subject: [PATCH 102/254] drawROI.m: use figure handle explicitly so that the function can be used from a script (from which mrLoadRet figure/axes are invisible). Only tested for 'line' ROIs --- mrLoadRet/ROI/drawROI.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index 482b3591a..aa60ce03f 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -279,9 +279,9 @@ % to see the lines connecting the points. [x y a] = getimage; 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 +302,7 @@ case 'line' % grab two points from the image; - [xi yi] = getpts; + [xi yi] = getpts(fig); xii=[]; yii=[]; for p=1:length(xi)-1 @@ -312,7 +312,7 @@ end if ~isempty(xii) - line(xii, yii); + line(xii, yii,'parent',gui.axis); drawnow; end From f936ea9e03fe9f32b0d358371ea8dbc9e7810a66 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 23 Dec 2020 12:09:37 +0200 Subject: [PATCH 103/254] mlrXFormFromHeader.m, freeSurfer2off.m: added some comments --- mrUtilities/File/FreeSurfer/freeSurfer2off.m | 15 +++++++++------ mrUtilities/File/Nifti/mlrXFormFromHeader.m | 12 ++++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) 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/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 From 9abe6e49bf89e30275707b088c65a117747a645d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 24 Dec 2020 16:10:40 +0200 Subject: [PATCH 104/254] drawROI.m: use getptsNoDoubleClick for lines if roiPolygoneMethod set in mrPrefs --- mrLoadRet/ROI/drawROI.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index aa60ce03f..a60012f2a 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -302,7 +302,11 @@ case 'line' % grab two points from the image; - [xi yi] = getpts(fig); + if strcmp(mrGetPref('roiPolygonMethod'),'getptsNoDoubleClick') + [xi, yi] = getptsNoDoubleClick(fig); + else + [xi, yi] = getpts(fig); + end xii=[]; yii=[]; for p=1:length(xi)-1 From a992b4b81248869355ae8feb1d32bb17df63bbc2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 24 Dec 2020 16:11:59 +0200 Subject: [PATCH 105/254] findLinePoints.m: return single point if input points are identical, instead of an error --- mrLoadRet/ROI/findLinePoints.m | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) 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; From 29c4167e54489f56a315247d9c2b8fe0306f8cde Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 24 Dec 2020 16:13:36 +0200 Subject: [PATCH 106/254] spatialSmooth.m: correctly handle FWHM=0 on any dimension --- .../spatialSmooth.m | 16 +++++++++++++++- .../Plugin/GLM_v2/combineTransformOverlays.m | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index 073aa4d42..204a52e39 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -23,6 +23,20 @@ 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 +if 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/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 4b8a98a20..3dc0f2095 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -1,5 +1,5 @@ function [thisView,params] = combineTransformOverlays(thisView,params,varargin) -% [thisView,params] = combineTransformOverlays(thisView,thisView,overlayNum,scanNum,x,y,z) +% [thisView,params] = combineTransformOverlays(thisView,params,overlayNum,scanNum,x,y,z) % % combines (masked) Overlays according to matlab or custom operators in current view and current analysis % From c6d91b65c016166e3cf6c6c833d53dc4e09f7718 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 26 Jan 2021 13:01:29 +0200 Subject: [PATCH 107/254] refreshMLRDisplay.m: outputs the view as 6th argument for use in scripts --- mrLoadRet/GUI/refreshMLRDisplay.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index 87639d58d..f08df40b7 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 From eb5cc15e5ab83ed362d04ea1bae6f47af0ac8b60 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 29 Jan 2021 12:46:21 +0200 Subject: [PATCH 108/254] mrQuit.m: do not attempt to close figure if view has been opened without GUI --- mrLoadRet/GUI/mrQuit.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/GUI/mrQuit.m b/mrLoadRet/GUI/mrQuit.m index cc862c6bd..28b33b081 100644 --- a/mrLoadRet/GUI/mrQuit.m +++ b/mrLoadRet/GUI/mrQuit.m @@ -72,7 +72,7 @@ viewCount = 0; for viewNum = 1:length(views) view = views{viewNum}; - if isview(view) + if isview(view) && ~isempty(view.figure) viewCount = viewCount+1; delete(view.figure); % deletes object, but apparently we still need closereq; % a closereq to remove the window in r2014b From 98d9dcf254bc59114c6a066f0e501e557ee93600 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 29 Jan 2021 13:16:16 +0200 Subject: [PATCH 109/254] combineTransformOverlays.m: if additional argument cannot be evaluate, use string as is in all cases (whatever the error message) --- .../Plugin/GLM_v2/combineTransformOverlays.m | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 3dc0f2095..688651bf4 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -662,17 +662,13 @@ 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 + nArgs = nArgs+1; + [token,remain] = strtok(remain, separator); + try + arguments{nArgs} = eval(token); + catch + arguments{nArgs} = token; + end end From aa9220254e2c6059d95e4133679191366dea3be3 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 11:57:48 +0200 Subject: [PATCH 110/254] importTSeries.m: replaced function contains() by isempty(strfind()) for compatibility with older versions of Matlab --- mrLoadRet/File/importTSeries.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/File/importTSeries.m b/mrLoadRet/File/importTSeries.m index 376d88168..37f1de7dd 100644 --- a/mrLoadRet/File/importTSeries.m +++ b/mrLoadRet/File/importTSeries.m @@ -64,7 +64,7 @@ for iFile = 1:length(filename) - if contains(stripext(filename{iFile}),'.') && ~contains(filename{iFile},'.nii.gz') %make an exception for gziped NIFTI files + 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 From 017083762f70481766d7e0607de9158ac90c273f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 16:48:03 +0200 Subject: [PATCH 111/254] mlrExportROI.m: corrected error in vertex numbers when orientation of base anatomy does not match dimensions in baseCoordMap --- mrLoadRet/File/mlrExportROI.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index e78f92e44..b0a459210 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -127,11 +127,13 @@ function mlrExportROI(v,saveFilename,varargin) roiBaseCoords = roiBaseCoords(:,xCheck & yCheck & sCheck); end - % convert to linear coordinates - roiBaseCoordsLinear = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats and surfaces, use basecoordmap to transform ROI from canonical base to multi-depth flat map + roiBaseCoordsLinear = mrSub2ind(baseCoordMap.dims',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); roiBaseCoordsLinear = ismember(baseCoordsLinear,roiBaseCoordsLinear); + else + % convert to linear coordinates + roiBaseCoordsLinear = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); end % check roiBaseCoords From 1e1bb6c4a773932b3736fa8ce1c76ccdd9e744af Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 16:50:20 +0200 Subject: [PATCH 112/254] mlrExportROI.m: when exporting to label file, only consider voxels in the central part of the cortical ribbon (but really, this should be added as an option/parameter) --- mrLoadRet/File/mlrExportROI.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index b0a459210..37dfb38a6 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -147,8 +147,10 @@ function mlrExportROI(v,saveFilename,varargin) % reshape to vertices * depths roiBaseCoordsLinear = reshape(roiBaseCoordsLinear, [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: - roiBaseCoordsLinear = find(any(roiBaseCoordsLinear,2)); + % 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,2); + roiBaseCoordsLinear = find(any(roiBaseCoordsLinear(:,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,:)+baseCoordMap.outerVtcs(roiBaseCoordsLinear,:))/2; % change vertex coordinates to freesurfer system: 0 is in the middle of the volume and coordinates are in mm From 43ea12e62fb92c7a706f89b5d96716a7620cc8de Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 16:52:43 +0200 Subject: [PATCH 113/254] mlrExportROI.m: took out mlrWriteFreeSurferLabel.m --- mrLoadRet/File/mlrExportROI.m | 26 ++++++++++--------- .../File/FreeSurfer/mlrWriteFreesurferLabel.m | 13 ++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 mrUtilities/File/FreeSurfer/mlrWriteFreesurferLabel.m diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 37dfb38a6..0e84d8399 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -157,19 +157,21 @@ function mlrExportROI(v,saveFilename,varargin) % (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) - - % a Freesurfer label file is text file with a list of vertex numbers and coordinates - fileID = fopen(saveFilename{iRoi},'w'); - freesurferName = extractBetween(baseCoordMap.path,'subjects\','\surfRelax'); - if isempty(freesurferName) - freesurferName= '[????]'; - elseif iscell(freesurferName) %in newer versions of Matlab, extractBetween may reutrn a cell array - freesurferName = freesurferName{1}; + + % 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 - fprintf(fileID,'#!ascii label, ROI exported from subject %s using mrTools (mrLoadRet v%.1f)\n', freesurferName, mrLoadRetVersion); - fprintf(fileID,'%d\n', size(vertexCoords,1)); - fprintf(fileID,'%d %f %f %f 1\n', [roiBaseCoordsLinear-1, vertexCoords]'); - fclose(fileID); + 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-1, vertexCoords, ones(size(vertexCoords,1),1)]) else % set all the roi coordinates to 1 d(roiBaseCoordsLinear) = 1; 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); + From d905b38bb79458773009edf2110d4f483e8eb78c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 16:56:30 +0200 Subject: [PATCH 114/254] mlrImportFreesurferLabel.m: took out mlrReadFreesurferLabel.m --- .../FreeSurfer/mlrImportFreesurferLabel.m | 20 ++------------ .../File/FreeSurfer/mlrReadFreesurferLabel.m | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 mrUtilities/File/FreeSurfer/mlrReadFreesurferLabel.m diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m index c76ca6254..43e009a4c 100644 --- a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m @@ -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 @@ -75,23 +75,7 @@ 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) 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 From 6e4420aef1a615c4534c061934babebd1440f047 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 18 Feb 2021 17:00:40 +0200 Subject: [PATCH 115/254] mlrAnatomyPlugin.m/mlrAnatomyImportFreesurferLabel, mlrImportFreesurferLabel.m, mlrGetSurfaceNames: when importing multiple label file, assume that they all correspond to the same surface files + exit gracefully if use cancels surface selection menu --- .../Plugin/mlrAnatomy/mlrAnatomyPlugin.m | 21 ++++++++++++++----- .../File/FreeSurfer/mlrGetSurfaceNames.m | 8 +++++-- .../FreeSurfer/mlrImportFreesurferLabel.m | 17 ++++++++------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m index 17ef42c3e..b70cd7491 100644 --- a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m +++ b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m @@ -506,19 +506,30 @@ function mlrAnatomyImportFreesurferLabel(hObject,eventdata) v = viewGet(getfield(guidata(hObject),'viewNum'),'view'); %get file names -[labelFilenames,pathname] = uigetfile({'*.label','Freesurfer label files'},'Select Freesurfer label file(s)', '*.*','multiselect','on'); +[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 - roi = mlrImportFreesurferLabel(fullfile(pathname,labelFilenames{iROI})); - % Add ROI to view - v = viewSet(v,'newROI',roi); - v = viewSet(v,'currentROI',viewGet(getMLRView,'nrois')); + % 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); 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/mlrImportFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m index 43e009a4c..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 @@ -97,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 From 6dec1d0b3730ce0d68d7943f9844d56f44c1039d Mon Sep 17 00:00:00 2001 From: Sarah Khalife Date: Tue, 2 Mar 2021 10:20:54 +0200 Subject: [PATCH 116/254] viewSet.m (case overlaycmap): added option to set user-defined overlay colormap --- mrLoadRet/View/viewSet.m | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index dbd735176..a843c3726 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -1831,18 +1831,25 @@ 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)']; + view.analyses{analysisNum}.overlays(overlayNum).colormap = eval(evalstr); + end + elseif ~ischar(val) && size(val,2)==3 % if you want to add a new user defined color map + view.analyses{analysisNum}.overlays(overlayNum).colormap = val; + else + mrWarnDlg(sprintf('Unknown Color Map')); + end + case {'overlaymin'} % view = viewSet(view,'overlaymin',number,[overlayNum]); curOverlay = viewGet(view,'currentOverlay'); From 89fb17a8e04f9d40308c14b67b835ddf8e9fa6d1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 2 Mar 2021 10:25:03 +0200 Subject: [PATCH 117/254] mrPrint.m: default ROI contour line width is now taken from the mrPrefs + returns figure handle --- mrLoadRet/GUI/mrPrint.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 367c810b4..e0b560d2c 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -1,12 +1,12 @@ % mrPrint.m % % $Id$ -% usage: mrPrint(v) +% usage: mrPrint(v,<'useDefault=1'>,<'roiSmooth=0'>,<'roiLabels=0'>) % by: justin gardner % date: 10/04/07 % purpose: puts a printable version of the data into the graph win % -function retval = mrPrint(v,varargin) +function f = mrPrint(v,varargin) % check arguments if nargin < 1 @@ -68,7 +68,7 @@ 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} = {'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.'}; 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'}; From 7434a6fe7fa70dbcefc7bb39e705b11cb9fdbb48 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 2 Mar 2021 10:32:24 +0200 Subject: [PATCH 118/254] eventRelatedHighpass.m: handles cases were notchFilterForTSense field is missing from d structure --- mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m b/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m index 562db9f94..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; From 7ef2e0c533fd9e48fa6341cbc9f01894f6e44a27 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 2 Mar 2021 10:53:40 +0200 Subject: [PATCH 119/254] mrPrint.m: do not attempt to draw colorbar if no GUI associated with view --- mrLoadRet/GUI/mrPrint.m | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index e0b560d2c..028cc8914 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -94,15 +94,19 @@ % get the gui, so that we can extract colorbar fig = viewGet(v,'figNum'); -gui = guidata(fig); - -% 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'); +if ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it + gui = guidata(fig); + + % 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'); + end + cmap=squeeze(cmap(1,:,:)); +else + cmap = []; end -cmap=squeeze(cmap(1,:,:)); % display in graph window f = selectGraphWin; @@ -217,7 +221,9 @@ img(img<0) = 0;img(img>1) = 1; % set the colormap -colormap(cmap); +if ~isempty(cmap) + colormap(cmap); +end % now display the images if baseType == 2 @@ -330,7 +336,7 @@ end % display the colormap -if ~strcmp(params.colorbarLoc,'None') +if ~strcmp(params.colorbarLoc,'None') && ~isempty(cmap) H = colorbar(params.colorbarLoc); % set the colorbar ticks, making sure to switch % them if we have a vertical as opposed to horizontal bar From 0282d392961712ff7cd7279f8228c2fa3cd43ea1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 3 Mar 2021 12:45:21 +0200 Subject: [PATCH 120/254] mlrSphericalNormGroup.m: removed call to endsWith.m for backward compatibility with older versions of Matlab --- mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 79172878f..6174dbae6 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -313,7 +313,7 @@ end if overlayExists(iOverlay) - if endsWith(groupName,'Volume') + 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 From 02960889f3194d5135b9ea69b6b74af71201c5e1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 3 Mar 2021 12:46:12 +0200 Subject: [PATCH 121/254] mlrSphericalNormGroup.m: corrected bug with empty templateOverlayNewNames parameter --- mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 6174dbae6..5dc53cd34 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -320,9 +320,9 @@ overlayBaseName{iOverlay} = fixBadChars(viewGet(thisView,'overlayName'),badCharFixlist,[],maxNcharacters); if iSubj==1 if isempty(params.templateOverlayNewNames) - params.templateOverlayNewNames{iOverlay} = overlayBaseName{iOverlay}; + templateOverlayNewNames{iOverlay} = overlayBaseName{iOverlay}; else - params.templateOverlayNewNames{iOverlay} = fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters); + templateOverlayNewNames{iOverlay} = fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters); end end % export overlays to NIFTI in scan space @@ -388,12 +388,12 @@ hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything cbiWriteNifti(scanFileName{iGroup,1},data,hdr,'',{[],[],[],cOverlayConcat(iGroup)}); % for log file - logfile{iGroup,1}.overlay{cOverlayConcat(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,1}.overlay{cOverlayConcat(iGroup),1} = templateOverlayNewNames{iOverlay}; logfile{iGroup,1}.subject{cOverlayConcat(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; if params.combineLeftAndRight cbiWriteNifti(scanFileName{iGroup,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); - logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = templateOverlayNewNames{iOverlay}; logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'normal'; logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; cOverlayConcatLRcombined(iGroup) = cOverlayConcatLRcombined(iGroup)+1; @@ -403,7 +403,7 @@ end hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything cbiWriteNifti(scanFileName{iGroup,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); - logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = params.templateOverlayNewNames{iOverlay}; + logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = templateOverlayNewNames{iOverlay}; logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'reversed'; logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; end From fd3f81eb95a3d927506390f58a0c76990fb09a0d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 3 Mar 2021 12:52:51 +0200 Subject: [PATCH 122/254] freesurferSphericalNormalizationVolumes.m: do not issue warning that exported volume is missing if dryRun is true --- mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index 0b89109d3..f881eacbd 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -86,8 +86,8 @@ nSources = length(params.sourceVol); for iSource = 1:nSources if ~exist(params.sourceVol{iSource},'file') - mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source volume %s',params.sourceVol{iSource})); if ~params.dryRun + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source volume %s',params.sourceVol{iSource})); return; end else From 897a6dc7f7cd7ef8fd2dbaa3e37ef6cab8f23ee6 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 6 Mar 2021 13:12:21 +0200 Subject: [PATCH 123/254] applyInverseBaseCoordMap.m, inverseBaseCoordMap.m: optimized code for speed --- .../Plugin/GLM_v2/applyInverseBaseCoordMap.m | 31 ++++-- mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m | 97 +++++-------------- 2 files changed, 47 insertions(+), 81 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m index ef66e0dac..a42e9da75 100644 --- a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m @@ -1,23 +1,36 @@ -% function volumeData = applyInverseBaseCoordMap(flat2volumeMap,flatData) +% function volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) % -% Transforms data from flattened cortical patch space to volume space -% according to the mapping in flat2volumeMap. +% 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. flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) ) +% (e.g. surf2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) ) % % Taken out of combineTransformOverlays.m (22/07/2020) % -function volumeData = applyInverseBaseCoordMap(flat2volumeMap,volumeDims,flatData) +function volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) + +hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from surface to volume'); volumeData = zeros(volumeDims); datapoints = zeros(volumeDims); -hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from surface to volume'); -maxInstances = size(flat2volumeMap,2); + +% 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(surf2volumeMap(:,end)); +for iRow = longestRow % in case there are several such rows (unlikely) + volumeData(iRow) = volumeData(iRow) + sum(surfData(surf2volumeMap(iRow,:))); + datapoints(iRow) = size(surf2volumeMap,2); +end +surf2volumeMap(longestRow,:) = 0; +surf2volumeMap(:,sum(surf2volumeMap>0)==0) = []; + +% now do the rest colum by column +maxInstances = size(surf2volumeMap,2); for i=1:maxInstances mrWaitBar( i/maxInstances, hWaitBar); - thisBaseCoordsMap = full(flat2volumeMap(:,i)); - newData = flatData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); + thisBaseCoordsMap = surf2volumeMap(:,i); + newData = surfData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); indices = find(thisBaseCoordsMap); notNaN = ~isnan(newData); volumeData(indices(notNaN)) = volumeData(indices(notNaN)) + newData(notNaN); diff --git a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m index 46dc86464..7b830569e 100644 --- a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m @@ -1,18 +1,18 @@ -% function flat2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) +% function surf2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,) % % Computes a correspondence map between each voxel in a given volume and corresponding -% voxels of a flat map and outputs it as a sparse matrix -% By default this is computed for the base space on which the flat map is based. +% 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(flat2volumeMap,flatData) +% volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) % % Taken out of combineTransformOverlays.m (22/07/2020) -function flat2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,xform) +function surf2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,xform) if ieNotDefined('xform') xform = eye(4); @@ -23,77 +23,30 @@ coordsMap(all(~coordsMap,2),:)=NaN; coordsMap = round(coordsMap); coordsMap(any(coordsMap>repmat(volumeDims,size(coordsMap,1),1)|coordsMap<1,2),:)=NaN; -% convert overlay coordinates to overlay indices for manipulation ease -overlayIndexMap = sub2ind(volumeDims, coordsMap(:,1), coordsMap(:,2), coordsMap(:,3)); +% convert volume coordinates to linear indices for manipulation ease +volIndexMap = sub2ind(volumeDims, coordsMap(:,1), coordsMap(:,2), coordsMap(:,3)); clearvars('coordsMap'); % save memory -% 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) +% now make a coordinate map of which volume index correspond to which surface voxels/vertices indices +% (there will be several maps because each volume voxels might correspond to several surface voxel/vertex indices) -% % %METHOD 1 -% % %sort base indices -% % [sortedOverlayIndices,whichBaseIndices] = sort(overlayIndexMap); -% % %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(~isnan(overlayIndexMap))); -nBaseVoxels = numel(sortedIndices); -% find the first instance of each unique index -firstInstances = sortedIndices(1:end-1) ~= sortedIndices(2:end); +% 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 overlay index -% (= number of different base indices for each unique overlay index) +% 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); -% Now for each set of unique overlay indices, find the corresponding base indices -hWaitBar = mrWaitBar(-inf,'(inverseBaseCoordMap) Inverting baseCoordMap'); -% flat2volumeMap = sparse(prod(volumeDims),maxInstances); % I used to create the sparse mapping matrix, -% % but supposedly it's faster to first gather the matrix's indices and data and create it later (doesn't -% % make much of a difference though, presumably because I minimized the number of iteratiosnin the loop) -allUniqueOverlayIndices = zeros(nBaseVoxels,1); -allWhichBaseIndices = zeros(nBaseVoxels,1); -allInstances = zeros(nBaseVoxels,1); -n = 0; -for i=1:maxInstances - mrWaitBar( i/maxInstances, hWaitBar); - % find set of unique instances of overlay indices - [uniqueOverlayIndices, whichBaseIndices]= unique(overlayIndexMap); - % remove NaNs - whichBaseIndices(isnan(uniqueOverlayIndices))=[]; - uniqueOverlayIndices(isnan(uniqueOverlayIndices))=[]; - nUniqueIndices = length(whichBaseIndices); - % keep aside to fill sparse matrix later - allUniqueOverlayIndices(n+(1:nUniqueIndices)) = uniqueOverlayIndices; - allWhichBaseIndices(n+(1:nUniqueIndices)) = whichBaseIndices; - allInstances(n+(1:nUniqueIndices)) = i; -% % for each overlay voxel found, set the corresponding base index -% flat2volumeMap(uniqueOverlayIndices,i)=whichBaseIndices; - % remove instances that were found from the overlay index map before going through the loop again - overlayIndexMap(whichBaseIndices)=NaN; - n = n+nUniqueIndices; -end +% 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 -flat2volumeMap = sparse(allUniqueOverlayIndices,allInstances,allWhichBaseIndices,prod(volumeDims),maxInstances); -mrCloseDlg(hWaitBar); +surf2volumeMap = sparse(sortedVolIndices,instanceIndices,whichSurfIndices,prod(volumeDims),maxInstances); From 4c6f48c717a2aad303faa79692c2000f1c4e1938 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 9 Mar 2021 16:02:12 +0200 Subject: [PATCH 124/254] mlrDispPercent.m: added pause when refreshing message or counter, otherwise counter stays stuck to 0.00 --- mrUtilities/MatlabUtilities/mlrDispPercent.m | 1 + 1 file changed, 1 insertion(+) diff --git a/mrUtilities/MatlabUtilities/mlrDispPercent.m b/mrUtilities/MatlabUtilities/mlrDispPercent.m index fc899c6aa..7fc4c0c85 100644 --- a/mrUtilities/MatlabUtilities/mlrDispPercent.m +++ b/mrUtilities/MatlabUtilities/mlrDispPercent.m @@ -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 From e3d48648d9e3759408cc63f2b87390482b6299f2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 10 Mar 2021 16:54:55 +0200 Subject: [PATCH 125/254] combineTransformOverlays.m, maskOverlay.m: removed hidden assumption that scans have the same size and xform --- mrLoadRet/GUI/maskOverlay.m | 2 +- .../Plugin/GLM_v2/combineTransformOverlays.m | 69 +++++++++---------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/mrLoadRet/GUI/maskOverlay.m b/mrLoadRet/GUI/maskOverlay.m index 9b873268b..3f8ce6967 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 diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 688651bf4..c782e4c60 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -84,7 +84,7 @@ {'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'},... }; @@ -174,17 +174,17 @@ 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 + baseCoordsMap=cell(nScans,1); + for iScan = 1:nScans + 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) if ~isempty(overlayData(iOverlay).data{iScan}) - [overlayData(iOverlay).data{iScan}, voxelSize, baseCoordsMap{iScan}] = getBaseSpaceOverlay(thisView, overlayData(iOverlay).data{iScan},[],[],baseSpaceInterp); + [overlayData(iOverlay).data{iScan}, voxelSize, baseCoordsMap{iScan}] = getBaseSpaceOverlay(thisView, overlayData(iOverlay).data{iScan},iScan,[],baseSpaceInterp); end end end @@ -199,7 +199,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; @@ -214,14 +213,17 @@ 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 = 1:nScans + 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)); @@ -230,11 +232,14 @@ alphaOverlayNum(iOverlay) = viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay); end end - mask = maskOverlay(thisView,alphaOverlayNum,1:nScans,boxInfo); - for iScan = 1:length(mask) + for iScan = 1:nScans + 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 @@ -488,18 +493,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)) || baseType > 0) - if viewGet(thisView,'basetype')==1 - baseCoordsOverlay=cell(nScans,1); - for iScan=1:nScans + baseCoordsOverlay=cell(nScans,1); + for iScan=1:nScans + 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); + baseCoordsOverlay{iScan} = inverseBaseCoordMap(baseCoordsMap{iScan},scanDims{iScan},base2scan{iScan}); end + else + keyboard %not implemented end - else - keyboard %not implemented end end @@ -603,20 +608,10 @@ maxValue = max(outputOverlay(iOverlay).data(outputOverlay(iOverlay).data-inf)); end - if ~params.exportToNewGroup && params.baseSpace && (any(any((base2scan - eye(4))>1e-6)) || baseType > 0) %put back into scan/overlay space - for iScan=1:nScans + for iScan=1:nScans + 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{iScan}); -% datapoints=zeros(prod(scanDims{iScan}),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{iScan}); -% outputOverlay(iOverlay).data{iScan} = data ./datapoints; outputOverlay(iOverlay).data{iScan} = applyInverseBaseCoordMap(baseCoordsOverlay{iScan},scanDims{iScan},outputOverlay(iOverlay).data{iScan}); else keyboard %not implemented From 983f45228cf5afad7b624d8a411d90fd00663fee Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 12 Mar 2021 14:11:05 +0200 Subject: [PATCH 126/254] combineTransformOverlays.m: can select a subset of scans (if there are more than 1) --- .../Plugin/GLM_v2/combineTransformOverlays.m | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index c782e4c60..170314578 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -10,6 +10,8 @@ % [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$ @@ -56,6 +58,7 @@ baseSpaceInterpMenu = {'Same as display','nearest','linear','spline','cubic'}; params.exportToNewGroup = 0; params.outputName = ''; + nScans = viewGet(thisView,'nScans'); if viewGet(thisView,'basetype')~=1 baseSpaceOption='enable=0'; else @@ -64,6 +67,9 @@ if ieNotDefined('overlayList') overlayList = viewGet(thisView,'curOverlay'); end + if ieNotDefined('scanList') + scanList = 1:nScans; + end if ieNotDefined('roiList') roiList = viewGet(thisView,'curROI'); end @@ -125,6 +131,7 @@ askForParams = 0; if defaultParams params.overlayList = overlayList; + params.scanlist = scanList; params.roiList = roiList; else askForOverlays = 1; @@ -135,6 +142,14 @@ 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 + end if ~strcmp(params.roiMask,'None') params.roiList = selectInList(thisView,'rois','',roiList); if isempty(params.roiList) @@ -153,6 +168,10 @@ if ~ieNotDefined('overlayList') params.overlayList = overlayList; end +if ~ieNotDefined('scanList') + params.scanList = scanList; +end + if strcmp(params.combineFunction,'User Defined') params.combineFunction = params.customCombineFunction; end @@ -170,13 +189,12 @@ %get the overlay data -nScans = viewGet(thisView,'nScans'); overlayData = viewGet(thisView,'overlays'); overlayData = overlayData(params.overlayList); if params.baseSpace baseType = viewGet(thisView,'basetype'); baseCoordsMap=cell(nScans,1); - for iScan = 1:nScans + for iScan = 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 @@ -213,7 +231,7 @@ end if params.clip - for iScan = 1:nScans + for iScan = scanList if params.baseSpace && baseType==1 boxInfo.base2overlay = base2scan{iScan}; end @@ -232,7 +250,7 @@ alphaOverlayNum(iOverlay) = viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay); end end - for iScan = 1:nScans + for iScan = scanList if params.baseSpace && baseType==1 boxInfo.base2overlay = base2scan{iScan}; end @@ -247,7 +265,7 @@ % mask overlay using ROI(s) if ~strcmp(params.roiMask,'None') && ~isempty(params.roiList) - for iScan = 1:nScans + for iScan = scanList mask = false(size(overlayData(1).data{iScan})); roiCoords = getROICoordinates(thisView, params.roiList(1),iScan)'; for iRoi = params.roiList(2:end) @@ -289,7 +307,7 @@ case {'3D Array','4D Array','Scalar'} newOverlayData = cell(nScans,length(params.overlayList)); for iOverlay = 1:length(params.overlayList) - for iScan = 1:nScans + for iScan = scanList newOverlayData{iScan,iOverlay} = overlayData(iOverlay).data{iScan}; end end @@ -314,7 +332,7 @@ %convert overlays to cell arrays if scalar function if strcmp(params.inputOutputType,'Scalar') - for iScan = 1:nScans + for iScan = scanList for iOverlay = 1:length(params.overlayList) overlayData{iScan,iOverlay} = num2cell(overlayData{iScan,iOverlay}); %convert overlays to cell arrays end @@ -430,7 +448,7 @@ %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 = scanList outputData{iScan,iOutput,iOperations}.data{jScan} = outputData{iScan,iOutput,iOperations}.data{jScan}+0; end case {'3D Array','4D Array','Scalar'} @@ -494,7 +512,7 @@ %pre-compute coordinates map to put values back from base space to overlay space baseCoordsOverlay=cell(nScans,1); - for iScan=1:nScans + for iScan=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); @@ -608,7 +626,7 @@ maxValue = max(outputOverlay(iOverlay).data(outputOverlay(iOverlay).data-inf)); end - for iScan=1:nScans + for iScan=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 @@ -628,7 +646,7 @@ if params.exportToNewGroup && nScans>1 cOverlay = 0; for iOverlay = 1:size(outputData,2) - for iScan = 1:nScans + for iScan = scanList if ~isempty(outputOverlay(iOverlay).data{iScan}) cOverlay = cOverlay+1; outputOverlay2(cOverlay) = defaultOverlay; From 162a582385c9a21efaa05762db2d225773ca7201 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 12 Mar 2021 17:15:00 +0200 Subject: [PATCH 127/254] spatialSmooth.m: added option to fill NaNs with smoothed values (when available) --- .../spatialSmooth.m | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index 204a52e39..b8e24e08e 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -1,15 +1,23 @@ % -% function smoothed = spatialSmooth(overlay,FWHM) +% 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,keepNaNs) -function smoothed = spatialSmooth(overlay,FWHM) +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) From d0fcdda8f31611324274809408343abe033ba35e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 20 Mar 2021 12:58:13 +0200 Subject: [PATCH 128/254] mlrExportROI.m: added option to export to space of any loaded base or scan --- mrLoadRet/File/mlrExportROI.m | 68 ++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 0e84d8399..127a3571e 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -1,12 +1,14 @@ % mlrExportROI.m % % $Id$ -% usage: mlrExportROI(v,saveFilename,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>) +% usage: mlrExportROI(v,saveFilename,<'baseNum',baseNum>,<'scanNum',scanNum>,<'groupNum',groupNum>,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>) % by: justin gardner % date: 07/14/09 % purpose: Export ROI(s) to a nifti image or Freesurfer label file. Uses -% current roi(s) and current base in view to export. Pass in a nifti -% header as hdr argument if you want to use a different header +% 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 % function mlrExportROI(v,saveFilename,varargin) @@ -17,7 +19,7 @@ function mlrExportROI(v,saveFilename,varargin) end % optional arguments -getArgs(varargin,{'hdr=[]','exportToFreesurferLabel=0'}); +getArgs(varargin,{'baseNum=[]','scanNum=[]','groupNum=[]','hdr=[]','exportToFreesurferLabel=0'}); % get the roi we are being asked to export roiNum = viewGet(v,'currentroi'); @@ -34,20 +36,37 @@ function mlrExportROI(v,saveFilename,varargin) return end +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 passedInHeader = false; 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 ~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 @@ -57,7 +76,7 @@ function mlrExportROI(v,saveFilename,varargin) if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats, or when exporting to freesurfer label file, use basecoordmap if baseType == 1 && ~exportToFreesurferLabel - [~,baseCoords,baseCoordsHomogeneous] = getBaseSlice(v,1,3,viewGet(v,'baseRotate'),viewGet(v,'curBase'),baseType); + [~,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, ... @@ -65,21 +84,21 @@ function mlrExportROI(v,saveFilename,varargin) [3 1 4 2]); ones(1,size(baseCoordMap.coords,1)*size(baseCoordMap.coords,2),size(baseCoordMap.coords,5))]; end - baseDims = size(baseCoords); - baseDims = baseDims ([1 2 4]); + 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',baseDims(1),baseDims(2),baseDims(3))); + 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(baseDims)); + baseCoordsHomogeneous = reshape(baseCoordsHomogeneous,4,prod(volDims)); baseCoordsHomogeneous = round(baseCoordsHomogeneous); baseCoordsLinear = mrSub2ind(baseCoordMap.dims,baseCoordsHomogeneous(1,:),baseCoordsHomogeneous(2,:),baseCoordsHomogeneous(3,:)); 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',viewGet(v,'curBase')); + 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]); @@ -98,24 +117,28 @@ function mlrExportROI(v,saveFilename,varargin) end end else - baseDims = hdr.dim(2:4)'; + volDims = hdr.dim(2:4)'; end if ~passedInHeader && ~exportToFreesurferLabel - b = viewGet(v,'base'); + b = viewGet(v,'base',baseNum); end for iRoi = 1:length(roiNum) roiName = viewGet(v,'roiName',roiNum(iRoi)); % tell the user what is going on - fprintf('(mlrExportROI) Exporting ROI %s to %s with dimensions set to match base %s: [%i %i %i]\n',roiName, saveFilename{iRoi},viewGet(v,'baseName'),baseDims(1),baseDims(2),baseDims(3)); + fprintf('(mlrExportROI) Exporting ROI %s to %s with dimensions set to match %s: [%i %i %i]\n',roiName, saveFilename{iRoi},exportVolumeString,volDims(1),volDims(2),volDims(3)); - % get roi coordinates in base coordinates - roiBaseCoords = getROICoordinates(v,roiNum(iRoi),0); + % 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 if ~exportToFreesurferLabel % create a data structure that has all 0's - d = zeros(baseDims); + d = zeros(volDims); % make sure we are inside the base dimensions @@ -138,7 +161,7 @@ function mlrExportROI(v,saveFilename,varargin) % check roiBaseCoords if isempty(roiBaseCoords) || ~nnz(roiBaseCoordsLinear) - mrWarnDlg(sprintf('(mlrExportROI) This ROI (%s) does not have any coordinates in the base',roiName)); + mrWarnDlg(sprintf('(mlrExportROI) This ROI (%s) does not have any coordinates in the %s',roiName,exportVolumeString)); else @@ -177,9 +200,6 @@ function mlrExportROI(v,saveFilename,varargin) 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); From a41d71b1cd3022813e01c0f870ce0624676ac524 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 21 Mar 2021 16:45:12 +0200 Subject: [PATCH 129/254] makeContrastNames.m: now insensitive to capitalization of tTestSide option --- .../Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m index 9b8c723f8..0b2b80e9f 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m @@ -23,17 +23,17 @@ 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 From 29973b12413cfc256873ffdb36298eb72eafbcd8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 21 Mar 2021 16:48:02 +0200 Subject: [PATCH 130/254] transformStatistic.m: do not compute FDR- or FWE-adjusted if respective output arguments are not requested in function call --- mrLoadRet/Analysis/transformStatistic.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/Analysis/transformStatistic.m b/mrLoadRet/Analysis/transformStatistic.m index 4a4276294..1b1772f5a 100644 --- a/mrLoadRet/Analysis/transformStatistic.m +++ b/mrLoadRet/Analysis/transformStatistic.m @@ -37,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); @@ -47,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'}) @@ -66,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; @@ -76,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; From d8aff14f64fadb377fa94ca8343be723dbc764c2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Mar 2021 10:25:22 +0200 Subject: [PATCH 131/254] mlrGroupAverage.m: many improvements, including fixing bug in finding unique factor levels or combination of levels --- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 168 ++++++++++++++-------- 1 file changed, 106 insertions(+), 62 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index 3ec67990e..2c5575891 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -25,7 +25,7 @@ % 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.outputNonNaNcount : whether to output a map of the the voxelwise number of subjects entering in the average for +% 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) @@ -42,33 +42,39 @@ 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:viewGet(thisView,'nScans'); + params.scanList = 1:nScans; end %read log files associated with scans cScan = 0; noLinkedFile=false; for iScan = params.scanList - cScan= cScan+1; - logFileName = viewGet(thisView,'stimfilename',iScan); - if isempty(logFileName) - mrWarnDlg(sprintf('(mlrGroupAverage) No mat file linked to scan %d', iScan)); + if iScan < 0 || iScan > nScans + mrWarnDlg(sprintf('(mlrGroupAverage) Scan %d does not exist in group %d', iScan, params.groupNum)); noLinkedFile = true; else - factors{cScan} = load(logFileName{1}); - if isempty(factors{cScan}) - mrWarnDlg(sprintf('(mlrGroupAverage) Cannot open file %s for scan %d', logFileName, iScan)); - end - if iScan == 1 - commonFactors = fieldnames(factors{cScan}); - allFactors = commonFactors; + 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 - commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); - allFactors = union(allFactors,fieldnames(factors{cScan})); + 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 @@ -86,8 +92,8 @@ elseif ~ismember(params.averagingMode,{'marginal','interaction'}) mrErrorDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); end -if fieldIsNotDefined(params,'outputNonNaNcount') - params.outputNonNaNcount = false; +if fieldIsNotDefined(params,'outputSampleSize') + params.outputSampleSize = false; end if justGetParams, return; end @@ -102,25 +108,30 @@ whichFactors = num2cell(1:length(params.factors)); 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}) - mrErrorDlg(sprintf('(mlrGroupAverage) Variable ''%s'' does not exist in scan %d', params.factors{iFactor}, params.scanList(iScan))); - end - % 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) - mrErrorDlg(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))); - 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 + 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 - levels{iScan}(:,iFactor) = factors{iScan}.(params.factors{iFactor}); end if iScan ==1 allLevels = levels{iScan}; @@ -129,68 +140,92 @@ 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'); % do not support cell arrays + [~,~,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 = 0; +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)] = unique(levelNums{iScan}(:,whichFactors{iFactor}),'rows'); - whichOverlay{iScan}(:,iFactor) = whichOverlay{iScan}(:,iFactor) + nLevels; + [~,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{nLevels+iLevel} = [allLevels{uniqueLevelIndices(iLevel),whichFactors{iFactor}}]; + uniqueLevels{sum(nLevels)+iLevel} = [allLevels{uniqueLevelIndices(iLevel),whichFactors{iFactor}}]; end - nLevels = nLevels + size(uniqueLevelNums,1); + nLevels(iFactor) = size(uniqueLevelNums,1); + nLevelsAcrossFactors = nLevelsAcrossFactors + nLevels(iFactor); end - -minOverlay = inf(nLevels,1); -maxOverlay = -1*inf(nLevels,1); +minOverlay = inf(sum(nLevels),1); +maxOverlay = -1*inf(sum(nLevels),1); maxCount = 0; -for iScan = 1:length(params.scanList) - for iOverlay = 1:nLevels %for each overlay - overlays(iOverlay).data{params.scanList(iScan)} = zeros(hdr{iScan}.dim(2:4)'); % initialize scans with zeros - volumeCount = zeros(hdr{iScan}.dim(2:4)'); - for iVolume = find(whichOverlay{iScan}(:,iFactor)==iOverlay)' %for each volume in the scan matching this (combination of) condition(s) - data = cbiReadNifti(tseriesPath{iScan},{[],[],[],iVolume},'double'); % read the data - isNotNaN = ~isnan(data); - for iFactor = 1:length(whichFactors) - % add non-NaN values to the appropriate overlay(s) - overlays(iOverlay).data{params.scanList(iScan)}(isNotNaN) = ... - overlays(iOverlay).data{params.scanList(iScan)}(isNotNaN) + data(isNotNaN); - volumeCount = volumeCount + isNotNaN; +cScan = 0; +for iScan = 1:viewGet(thisView,'nScans') + if ismember(iScan,params.scanList) + fprintf('(mlrGroupAverage) Computing averages for scan %d... ',iScan); + cScan = cScan+1; + end + cOverlay = 0; + for iFactor = 1:length(whichFactors) + for iOverlay = 1:nLevels(iFactor) %for each overlay + cOverlay = cOverlay + 1; + if ismember(iScan,params.scanList) + overlays(cOverlay).data{iScan} = zeros(hdr{cScan}.dim(2:4)'); % initialize scans with zeros + volumeCount = zeros(hdr{cScan}.dim(2:4)'); + for iVolume = find(ismember(whichOverlay{cScan}(:,iFactor),cOverlay,'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) + overlays(cOverlay).data{iScan}(isNotNaN) = ... + overlays(cOverlay).data{iScan}(isNotNaN) + data(isNotNaN); + volumeCount = volumeCount + isNotNaN; + end + % divide by the number of added overlays + overlays(cOverlay).data{iScan} = cast(overlays(cOverlay).data{iScan}./volumeCount,mrGetPref('defaultPrecision')); + % get min and max + minOverlay(cOverlay) = min(minOverlay(cOverlay),min(overlays(cOverlay).data{iScan}(:))); + maxOverlay(cOverlay) = max(maxOverlay(cOverlay),max(overlays(cOverlay).data{iScan}(:))); + if params.outputSampleSize + overlays(sum(nLevels)+cOverlay).data{iScan} = volumeCount; + maxCount = max(maxCount,max(volumeCount(:))); + end + else + overlays(cOverlay).data{iScan} = []; + if params.outputSampleSize + overlays(sum(nLevels)+cOverlay).data{iScan} = []; + end end end - % divide by the number of added overlays - overlays(iOverlay).data{params.scanList(iScan)} = cast(overlays(iOverlay).data{params.scanList(iScan)}./volumeCount,mrGetPref('defaultPrecision')); - % get min and max - minOverlay(iOverlay) = min(minOverlay(iOverlay),min(overlays(iOverlay).data{params.scanList(iScan)}(:))); - maxOverlay(iOverlay) = max(maxOverlay(iOverlay),max(overlays(iOverlay).data{params.scanList(iScan)}(:))); - if params.outputNonNaNcount - overlays(nLevels+iOverlay).data{params.scanList(iScan)} = volumeCount; - maxCount = max(maxCount,max(volumeCount(:))); - end + end + if ismember(iScan,params.scanList) + fprintf('Done\n'); end end %add overlays' missing fields -for iOverlay = 1:nLevels*(1+params.outputNonNaNcount) - if iOverlay<=nLevels +for iOverlay = 1:sum(nLevels)*(1+params.outputSampleSize) + + if iOverlay<=sum(nLevels) overlays(iOverlay).name = uniqueLevels{iOverlay}; overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; else - overlays(iOverlay).name = sprintf('N (%s)',uniqueLevels{iOverlay-nLevels}); + overlays(iOverlay).name = sprintf('N (%s)',uniqueLevels{iOverlay-sum(nLevels)}); overlays(iOverlay).range = [0 maxCount]; end overlays(iOverlay).groupName = viewGet(thisView,'groupName'); @@ -198,14 +233,23 @@ overlays(iOverlay).type = 'Group average'; overlays(iOverlay).function = 'mlrGroupAverage'; overlays(iOverlay).interrogator = ''; + + 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 % set or create analysis -analysisNum = viewGet(thisView,'analysisName',params.analysisName); -if isempty(viewGet(thisView,'analysisNum',analysisNum)) +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); From 75534bf8989bbc46d963449750d518787117dd05 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Mar 2021 10:26:52 +0200 Subject: [PATCH 132/254] applyInverseBaseCoordMap.m: can now be applied to 4D data --- .../Plugin/GLM_v2/applyInverseBaseCoordMap.m | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m index a42e9da75..881bcc173 100644 --- a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m @@ -12,30 +12,38 @@ hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from surface to volume'); -volumeData = zeros(volumeDims); -datapoints = zeros(volumeDims); +nVolumes = size(surfData,4); +volumeData = zeros([volumeDims nVolumes]); -% 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(surf2volumeMap(:,end)); -for iRow = longestRow % in case there are several such rows (unlikely) - volumeData(iRow) = volumeData(iRow) + sum(surfData(surf2volumeMap(iRow,:))); - datapoints(iRow) = size(surf2volumeMap,2); -end -surf2volumeMap(longestRow,:) = 0; -surf2volumeMap(:,sum(surf2volumeMap>0)==0) = []; +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(surf2volumeMap,2); -for i=1:maxInstances - mrWaitBar( i/maxInstances, hWaitBar); - thisBaseCoordsMap = surf2volumeMap(:,i); - newData = surfData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); - indices = find(thisBaseCoordsMap); - notNaN = ~isnan(newData); - volumeData(indices(notNaN)) = volumeData(indices(notNaN)) + newData(notNaN); - datapoints(indices(notNaN)) = datapoints(indices(notNaN)) + 1; + % 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 -datapoints = reshape(datapoints,volumeDims); -volumeData = volumeData ./datapoints; -mrCloseDlg(hWaitBar); \ No newline at end of file + +mrCloseDlg(hWaitBar); From 23875f2d7d9f6e9e2503d6c63fdec46f85c3dc37 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Mar 2021 10:29:43 +0200 Subject: [PATCH 133/254] freesurferSphericalNormalizationVolumes.m: added option to binarize normalized data --- .../freesurferSphericalNormalizationVolumes.m | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index f881eacbd..6177fb2bc 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -21,6 +21,7 @@ % 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) @@ -60,13 +61,16 @@ if fieldIsNotDefined(params,'hemisphere') params.hemisphere = 'both'; end -if ieNotDefined('interpMethod') +if fieldIsNotDefined(params,'interpMethod') params.interpMethod = mrGetPref('interpMethod'); end -if ieNotDefined('recomputeRemapping') +if fieldIsNotDefined(params,'outputBinaryData') + params.outputBinaryData = false; +end +if fieldIsNotDefined(params,'recomputeRemapping') params.recomputeRemapping = false; end -if ieNotDefined('cropDestVolume') +if fieldIsNotDefined(params,'cropDestVolume') params.cropDestVolume = true; end if fieldIsNotDefined(params,'dryRun') @@ -311,11 +315,23 @@ 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),params.interpMethod); + 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 From 646c6a89d138e97e3ed70cc90b23ef6eef5b36a5 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Mar 2021 10:30:59 +0200 Subject: [PATCH 134/254] mlrSphericalNormGroup.m: added option to normalize ROI masks and compute ROI probability maps --- .../groupAnalysis/mlrSphericalNormGroup.m | 438 +++++++++++++----- 1 file changed, 334 insertions(+), 104 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 5dc53cd34..102b5d8b6 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -1,12 +1,12 @@ % function [success,params] = mlrSphericalNormGroup(params,<'justGetParams'>) % -% goal: Exports overlay data from multiple subjects located in the same study folder +% 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 across all subjects. +% (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 the group-average using mrLoadRet timeseries analysis. +% Optionally computes group-average overlays and ROI probability maps. % % usage: % params = mlrSphericalNormGroup(params,'justGetParams') %returns default parameters @@ -25,15 +25,18 @@ % 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.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 data when left-right flipping, useful for orientation sensitive measures +% 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) % @@ -75,26 +78,41 @@ 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 = 1; % group numbers of the overlays to normalize + 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 = 1; % analysis numbers of the overlays to normalize + params.subjectOverlayAnalyses = ones(size(params.subjectOverlays)); % analysis numbers of the overlays to normalize end -if fieldIsNotDefined(params,'subjectOverlays') - params.subjectOverlays = 1; % overlay numbers to normalize +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,'templateOverlayGroupNums') - params.templateOverlayGroupNums = 1; % in what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported + 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; % in what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported + 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 = ''; % combineLeftAndRight is true, name of the Freesurfer folder containing the left-right flipped surfaces of the left-right-flipped template + 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 = {}; @@ -108,6 +126,12 @@ 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 @@ -122,19 +146,39 @@ 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 + 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.lrFlipTransform)==1 params.lrFlipTransform = repmat(params.lrFlipTransform,1,nOverlays); end @@ -143,6 +187,7 @@ end + if nSubjects ~= length(params.freesurferSubjectIDs) mrWarnDlg('(mlrSphericalNormGroup) There must be the same number of mrLoadRet and freesurfer subject IDs') return; @@ -151,6 +196,10 @@ 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; @@ -159,11 +208,38 @@ 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 (%d)',nOverlays)) + 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 min(params.templateOverlayGroupNums)~=1 || (length(unique(params.templateOverlayGroupNums))>1 && any(diff(unique(params.templateOverlayGroupNums)))~=1) +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 @@ -171,6 +247,10 @@ 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; @@ -193,21 +273,38 @@ 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: not data will be exported or written to disc'); +end % create mrLoadRet group folder mrLoadRetTemplateFolder = fullfile(params.studyDir,params.mrLoadRetTemplateID); temporaryTseriesFolder = fullfile(params.studyDir,'tempTSeries'); -mkdir(temporaryTseriesFolder); -for iGroup = 1: nTemplateGroups - templateTseriesFolder{iGroup} = fullfile(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{iGroup},'TSeries'); +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', params.templateOverlayGroupNames{1})); - for iGroup = 2: nTemplateGroups - mkdir(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{iGroup}); + '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 @@ -215,12 +312,12 @@ cd(mrLoadRetTemplateFolder) thisView = mrLoadRet([],'No GUI'); groupExists = false; - for iGroup = 1: nTemplateGroups - if isempty(viewGet(thisView,'groupnum',params.templateOverlayGroupNames{iGroup})) - mkdir(mrLoadRetTemplateFolder,params.templateOverlayGroupNames{iGroup}); + 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',params.templateOverlayGroupNames{iGroup},params.mrLoadRetTemplateID)); + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Group ''%s'' already exists in template folder ''%s''',templateGroupFolder{iGroup},params.mrLoadRetTemplateID)); groupExists = true; end end @@ -234,14 +331,14 @@ mrQuit(0); % make sure there are no open MLR views % export subject overlays to subject-specific scan spaces -cOverlayConcat = zeros(nTemplateGroups,1); -cOverlayConcatLRcombined = zeros(nTemplateGroups,1); +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) - 4; - + - (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'); @@ -249,6 +346,8 @@ 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}) @@ -268,45 +367,64 @@ else thisView = viewSet(thisView,'curGroup',groupNum); groupName = viewGet(thisView,'groupName'); - 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 + + 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 - analysisNum = params.subjectOverlayAnalyses{iOverlay}; + thisScanHdr = viewGet(thisView,'niftiHdr'); 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)); + 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 - thisView = viewSet(thisView,'curAnalysis',analysisNum); - analysisName = viewGet(thisView,'analysisName'); - if iscell(params.subjectOverlays) - if ischar(params.subjectOverlays{iOverlay}) - overlayNum = viewGet(thisView,'AnalysisNum',params.subjectOverlays{iOverlay}); - if isempty(overlayNum) - mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find overlay ''%s'' in group ''%s'', analysis ''%s''',params.subjectOverlays{iOverlay},groupName,analysisName)); + 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 - overlayNum = params.subjectOverlays(iOverlay); + analysisNum = params.subjectOverlayAnalyses{iOverlay}; end else - overlayNum = params.subjectOverlays(iOverlay); + analysisNum = params.subjectOverlayAnalyses(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)); + if ~isempty(analysisNum) + if analysisNum > viewGet(thisView,'nAnalyses') + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find analysis %d in group ''%s''',analysisNum,groupName)); 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; + 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 @@ -318,11 +436,14 @@ 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{iOverlay} = overlayBaseName{iOverlay}; + templateOverlayNewNames{cOverlay} = overlayBaseName{iOverlay}; else - templateOverlayNewNames{iOverlay} = fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters); + templateOverlayNewNames{cOverlay} = fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters); end end % export overlays to NIFTI in scan space @@ -338,9 +459,76 @@ 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) || params.dryRun + % 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'); @@ -349,6 +537,7 @@ fsSphericalParams.fsDestSubj = params.freesurferTemplateID; fsSphericalParams.destVol = convertNames(:,1); fsSphericalParams.interpMethod = 'linear'; + fsSphericalParams.outputBinaryData = false; % UNCLEAR IF THIS SHOULD BE TRUE OR FALSE (ONLY MATTERS FOR ROIs) fsSphericalParams.dryRun = params.dryRun; fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); if params.combineLeftAndRight @@ -364,52 +553,95 @@ if ~params.dryRun % delete subject-space exported NIFTI files - for iOverlay = 1:nOverlays - delete(exportNames{iOverlay}); + 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 - overlays = find(params.templateOverlayGroupNums==iGroup); - for iOverlay = overlays - cOverlayConcat(iGroup) = cOverlayConcat(iGroup)+1; - cOverlayConcatLRcombined(iGroup) = cOverlayConcatLRcombined(iGroup)+1; - if iSubj==1 && iOverlay == overlays(1) - templateScanName{iGroup,1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s', ... - mat2str(params.subjectOverlays(overlays)),mat2str(params.subjectOverlayAnalyses(overlays)),mat2str(params.subjectOverlayGroups(overlays))); + 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{iOverlay,1}); + [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,'',{[],[],[],cOverlayConcat(iGroup)}); + cbiWriteNifti(scanFileName{iGroup,1},data,hdr,'',{[],[],[],cNiftiConcat(iGroup,1)}); % for log file - logfile{iGroup,1}.overlay{cOverlayConcat(iGroup),1} = templateOverlayNewNames{iOverlay}; - logfile{iGroup,1}.subject{cOverlayConcat(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; + 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 - cbiWriteNifti(scanFileName{iGroup,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); - logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = templateOverlayNewNames{iOverlay}; - logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'normal'; - logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; - cOverlayConcatLRcombined(iGroup) = cOverlayConcatLRcombined(iGroup)+1; - [data,hdr] = cbiReadNifti(convertNames{iOverlay,2}); - if params.lrFlipTransform(iOverlay) - data = params.lrFlipTransformFunctions{params.lrFlipTransform(iOverlay)}(data); + 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,2},data,hdr,'',{[],[],[],cOverlayConcatLRcombined(iGroup)}); - logfile{iGroup,2}.overlay{cOverlayConcatLRcombined(iGroup),1} = templateOverlayNewNames{iOverlay}; - logfile{iGroup,2}.leftRightDirection{cOverlayConcatLRcombined(iGroup),1} = 'reversed'; - logfile{iGroup,2}.subject{cOverlayConcatLRcombined(iGroup),1} = params.mrLoadRetSubjectIDs{iSubj}; + 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{iOverlay,iCombine}); + delete(convertNames{iFile,iCombine}); end end end @@ -418,20 +650,18 @@ if ~params.dryRun - nScans = size(templateScanName,2); - if params.cropScans - for iGroup = 1:nTemplateGroups - for iScan = 1:nScans + 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))); % 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 + [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); @@ -451,7 +681,7 @@ fprintf('\n(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); cd(mrLoadRetTemplateFolder); if initMrLoadRetGroup - scanList{1} = 1:nScans; + 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} @@ -490,22 +720,22 @@ end % import (other) groups - for iGroup = 1+initMrLoadRetGroup:nTemplateGroups - thisView = viewSet(thisView,'newGroup',params.templateOverlayGroupNames{iGroup}); - templateGroupNums{iGroup} = viewGet(thisView,'groupNum',params.templateOverlayGroupNames{iGroup}); + 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 + 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:-1:1]+1; + scanList{iGroup} = viewGet(thisView,'nScans')-(nScans(iGroup):-1:1)+1; end - for iGroup = 1:nTemplateGroups + for iGroup = 1:nTemplateGroups+(nROIs>0) % create log files indicating which volumes correspond to which overlays and subjects - for iScan = 1:nScans + for iScan = 1:nScans(iGroup) logfileStruct = logfile{iGroup,iScan}; logFilename = [templateScanName{iGroup,iScan} '.mat']; save(fullfile(mrLoadRetTemplateFolder,'Etc',logFilename),'-struct','logfileStruct'); @@ -514,7 +744,7 @@ viewSet(thisView,'stimfilename',logFilename, scanList{iGroup}(iScan),templateGroupNums{iGroup}); end - if params.computeGroupAverage(iGroup) + if ( iGroup>nTemplateGroups && params.computeROIprobabilityMaps ) || params.computeGroupAverage(iGroup) thisView = viewSet(thisView,'curGroup',templateGroupNums{iGroup}); [~,averageParams] = mlrGroupAverage(thisView,[],'justGetParams'); averageParams.factors = {'overlay'}; From 7c9c4d0dd125aeb3c0300f885d6ea9fa5f402fbf Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 22 Mar 2021 10:31:42 +0200 Subject: [PATCH 135/254] added mlrGroupTtest.m --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 549 ++++++++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 mrLoadRet/groupAnalysis/mlrGroupTtest.m diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m new file mode 100644 index 000000000..398db9434 --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -0,0 +1,549 @@ + +% [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 (default = [1 1 1]. Set to 0 for no smoothing. +% params.smoothingSpace: space in which to smooth the data, 0 = current scan, any other number: base number (default: 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.thresholdEstimates: whether to clip contrast estimate overlays using corresponding p-value (using most conservative correction) +% 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 = [1 1 1]; +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,'thresholdEstimates') + params.thresholdEstimates = true; +end +if fieldIsNotDefined(params,'outputStatistic') + params.outputStatistic = false; +end +if fieldIsNotDefined(params,'outputContrastEstimates') + params.outputContrastEstimates = true; +end +if fieldIsNotDefined(params,'outputContrastSte') + params.outputContrastSte = true; +end +if fieldIsNotDefined(params,'outputSampleSize') + params.outputSampleSize = false; +end + +uniqueLevels = {}; + +if justGetParams && fieldIsNotDefined(params,'factors'), return; end + +%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)) + mrErrorDlg('(mlrGroupTtest) Cannot run t-tests because factors are missing in some scans'); + end + whichFactors = {1: length(params.factors)}; +else + whichFactors = num2cell(1:length(params.factors)); +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') + params.contrasts = eye(sum(nLevels)); +end + +if justGetParams + thisView = viewSet(thisView,'curGroup',currentGroup); + return; +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, swithing smoothing space to current scan'); + params.smoothingSpace = 0; + end +end + + + +%-------------------------------------- Compute contrasts and run t-tests +contrastNames = makeContrastNames(params.contrasts,uniqueLevels,params.testSide); +nContrasts = size(params.contrasts,1); +minOverlay = inf(nContrasts* (1 + params.fweAdjustment + params.fdrAdjustment + ... + params.outputStatistic + params.outputContrastEstimates + params.outputContrastSte + params.outputSampleSize),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 estiamtes, 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 iOverlay = 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)) + keyboard % Ns are not equal across levels at all voxels + end + end + sampleSize = sampleSize(:,1); + 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 = 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(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; + 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(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 + + 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(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; + 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(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; + 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 + overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(sampleSize,outputPrecision); + overlays(nOutputs*nContrasts+iContrast).name = ['N: ' 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; + 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 + end +end + +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 + + +%add overlays' missing fields +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 average'; + 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 + if params.thresholdEstimates + 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 + overlays(iOverlay).alphaOverlayExponent = alphaOverlayExponent; + end + 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 + +% 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); From 65ad6bfd78c78229ce5b943c20d0ee476e34dc7a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 23 Mar 2021 12:44:56 +0200 Subject: [PATCH 136/254] mrLoadRetGUI.m (editManyROIs): now handles RGB triplet ROI color --- mrLoadRet/GUI/mrLoadRetGUI.m | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/GUI/mrLoadRetGUI.m b/mrLoadRet/GUI/mrLoadRetGUI.m index 68a92e123..cae5f7d72 100644 --- a/mrLoadRet/GUI/mrLoadRetGUI.m +++ b/mrLoadRet/GUI/mrLoadRetGUI.m @@ -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); From 2e3d157a6c30aeedb0302fac3b6b4ec1be31fde0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 25 Mar 2021 18:10:56 +0200 Subject: [PATCH 137/254] mrSurfViewer.m: put selected surface on top of list instead of default surface --- mrLoadRet/GUI/mrSurfViewer.m | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/GUI/mrSurfViewer.m b/mrLoadRet/GUI/mrSurfViewer.m index ecbb78721..ac4dd58c9 100644 --- a/mrLoadRet/GUI/mrSurfViewer.m +++ b/mrLoadRet/GUI/mrSurfViewer.m @@ -32,17 +32,23 @@ event = outerSurface; elseif (nargin == 1) && isstr(outerSurface) if ~isempty(strfind(outerSurface,'WM')) - innerSurface{1} = outerSurface; + [token,remain] = strtok(stripext(outerSurface),'WM'); + remain = remain(3:end); clear outerSurface; - outerSurface{1} = sprintf('%sGM.off',stripext(stripext(innerSurface{1}),'WM')); + innerSurface{1} = sprintf('%sGM%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')); + [token,remain] = strtok(stripext(outerSurface),'GM'); + remain = remain(3: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')); + [token,remain] = strtok(stripext(outerSurface),'Outer'); + remain = remain(3: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; From 291ea89495ff5cbbcc5eb3d458538390bc24f2f0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 25 Mar 2021 18:11:37 +0200 Subject: [PATCH 138/254] mrSurfViewer.m: find curvature file most likely corresponding to selected surface and put it on top of the list --- mrLoadRet/GUI/mrSurfViewer.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mrLoadRet/GUI/mrSurfViewer.m b/mrLoadRet/GUI/mrSurfViewer.m index ac4dd58c9..b75567a3f 100644 --- a/mrLoadRet/GUI/mrSurfViewer.m +++ b/mrLoadRet/GUI/mrSurfViewer.m @@ -182,15 +182,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) From 6fd5cb7ee0057e4f44a2cea0c534a49ef263c620 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 26 Mar 2021 13:22:33 +0200 Subject: [PATCH 139/254] combineTransformOverlays.m: fixed issues withs scanList, overlayList and roiList --- .../Plugin/GLM_v2/combineTransformOverlays.m | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 170314578..f53c73986 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,params,overlayNum,scanNum,x,y,z) % % combines (masked) Overlays according to matlab or custom operators in current view and current analysis % @@ -20,9 +19,20 @@ 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']); @@ -58,21 +68,11 @@ baseSpaceInterpMenu = {'Same as display','nearest','linear','spline','cubic'}; params.exportToNewGroup = 0; params.outputName = ''; - nScans = viewGet(thisView,'nScans'); if viewGet(thisView,'basetype')~=1 baseSpaceOption='enable=0'; else baseSpaceOption='enable=1'; end - if ieNotDefined('overlayList') - overlayList = viewGet(thisView,'curOverlay'); - end - if ieNotDefined('scanList') - scanList = 1:nScans; - end - if ieNotDefined('roiList') - roiList = viewGet(thisView,'curROI'); - end askForParams = 1; while askForParams @@ -131,7 +131,7 @@ askForParams = 0; if defaultParams params.overlayList = overlayList; - params.scanlist = scanList; + params.scanList = scanList; params.roiList = roiList; else askForOverlays = 1; @@ -163,13 +163,16 @@ end end end -end - -if ~ieNotDefined('overlayList') - params.overlayList = overlayList; -end -if ~ieNotDefined('scanList') - params.scanList = scanList; +else + if ~ieNotDefined('scanList') + params.scanList = scanList; + end + if ~ieNotDefined('overlayList') + params.overlayList = overlayList; + end + if ~ieNotDefined('roiList') + params.roiList = roiList; + end end if strcmp(params.combineFunction,'User Defined') @@ -194,7 +197,7 @@ if params.baseSpace baseType = viewGet(thisView,'basetype'); baseCoordsMap=cell(nScans,1); - for iScan = scanList + 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 @@ -231,7 +234,7 @@ end if params.clip - for iScan = scanList + for iScan = params.scanList if params.baseSpace && baseType==1 boxInfo.base2overlay = base2scan{iScan}; end @@ -250,7 +253,7 @@ alphaOverlayNum(iOverlay) = viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay); end end - for iScan = scanList + for iScan = params.scanList if params.baseSpace && baseType==1 boxInfo.base2overlay = base2scan{iScan}; end @@ -265,7 +268,7 @@ % mask overlay using ROI(s) if ~strcmp(params.roiMask,'None') && ~isempty(params.roiList) - for iScan = scanList + for iScan = params.scanList mask = false(size(overlayData(1).data{iScan})); roiCoords = getROICoordinates(thisView, params.roiList(1),iScan)'; for iRoi = params.roiList(2:end) @@ -307,7 +310,7 @@ case {'3D Array','4D Array','Scalar'} newOverlayData = cell(nScans,length(params.overlayList)); for iOverlay = 1:length(params.overlayList) - for iScan = scanList + for iScan = params.scanList newOverlayData{iScan,iOverlay} = overlayData(iOverlay).data{iScan}; end end @@ -332,7 +335,7 @@ %convert overlays to cell arrays if scalar function if strcmp(params.inputOutputType,'Scalar') - for iScan = scanList + for iScan = params.scanList for iOverlay = 1:length(params.overlayList) overlayData{iScan,iOverlay} = num2cell(overlayData{iScan,iOverlay}); %convert overlays to cell arrays end @@ -448,7 +451,7 @@ %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 = scanList + for jScan = params.scanList outputData{iScan,iOutput,iOperations}.data{jScan} = outputData{iScan,iOutput,iOperations}.data{jScan}+0; end case {'3D Array','4D Array','Scalar'} @@ -512,7 +515,7 @@ %pre-compute coordinates map to put values back from base space to overlay space baseCoordsOverlay=cell(nScans,1); - for iScan=scanList + 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); @@ -626,7 +629,7 @@ maxValue = max(outputOverlay(iOverlay).data(outputOverlay(iOverlay).data-inf)); end - for iScan=scanList + 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 @@ -646,7 +649,7 @@ if params.exportToNewGroup && nScans>1 cOverlay = 0; for iOverlay = 1:size(outputData,2) - for iScan = scanList + for iScan = params.scanList if ~isempty(outputOverlay(iOverlay).data{iScan}) cOverlay = cOverlay+1; outputOverlay2(cOverlay) = defaultOverlay; From 56d0b0c1d48ebf77d6638c1b7e14b5801890ec59 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 26 Mar 2021 13:35:31 +0200 Subject: [PATCH 140/254] combineTransformOverlays.m: fixed output overlay range and color range when output overlays for some scans are fully NaNs --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index f53c73986..3cc357689 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -619,7 +619,7 @@ maxValue = -inf; minValue = inf; for iOutput = 1:size(outputData,1) - if ~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 From 6e0e44f8051e05c0da4da0560dc17d31085cc72a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 26 Mar 2021 15:16:28 +0200 Subject: [PATCH 141/254] combineTransformOverlays.m: handles multiple additional array arguments with multiple scans of different dimensions --- .../Plugin/GLM_v2/combineTransformOverlays.m | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 3cc357689..75ca8625a 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -320,12 +320,16 @@ %parse additional array inputs additionalArrayArgs = parseArguments(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 = params.scanList + if all(cellfun(@isnumeric,additionalArrayArgs)) && ismember(params.inputOutputType,{'3D Array','4D Array'}) %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 @@ -349,7 +353,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)]; @@ -395,8 +399,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 From 0d6bf226f525c87814c5c4aacf06d76ad631b954 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 30 Mar 2021 12:16:54 +0300 Subject: [PATCH 142/254] spatialSmooth.m: handle cases where all kernel FWHM values are 0 --- .../GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index b8e24e08e..2503c1dab 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -31,7 +31,9 @@ kernelDims = 2*w+1; kernelCenter = ceil(kernelDims/2); [X,Y,Z] = meshgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); -if sigma_d(2) == 0 && sigma_d(3) == 0 +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 From 3a35fe1eaec1b5774d8595a8f3dad5320a8c04ef Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 30 Mar 2021 12:29:13 +0300 Subject: [PATCH 143/254] mlrGroupTtest.m: run checks on factors even when only getting default parameter values + other inconsequential changes --- .../GLM_v2/combineTransformOverlayFunctions/indexMax.m | 2 +- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 6 ++++-- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 9 +++++---- 3 files changed, 10 insertions(+), 7 deletions(-) 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/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index 2c5575891..e87eea891 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -90,7 +90,8 @@ if fieldIsNotDefined(params,'averagingMode') params.averagingMode = 'marginal'; % options are 'marginal' or 'interaction' elseif ~ismember(params.averagingMode,{'marginal','interaction'}) - mrErrorDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); + mrWarnDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); + return; end if fieldIsNotDefined(params,'outputSampleSize') params.outputSampleSize = false; @@ -101,7 +102,8 @@ if strcmp(params.averagingMode,'interaction') if ~all(ismember(params.factors,commonFactors)) - mrErrorDlg('(mlrGroupAverage) Cannot compute averages because factors are missing in some scans'); + mrWarnDlg('(mlrGroupAverage) Cannot compute averages because factors are missing in some scans'); + return; end whichFactors = {1: length(params.factors)}; else diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index 398db9434..22c9d19ae 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -115,7 +115,7 @@ params.outputContrastEstimates = true; end if fieldIsNotDefined(params,'outputContrastSte') - params.outputContrastSte = true; + params.outputContrastSte = false; end if fieldIsNotDefined(params,'outputSampleSize') params.outputSampleSize = false; @@ -123,8 +123,6 @@ uniqueLevels = {}; -if justGetParams && fieldIsNotDefined(params,'factors'), return; end - %read log files associated with scans cScan = 0; noLinkedFile=false; @@ -169,13 +167,16 @@ if strcmp(params.combinationMode,'interaction') if ~all(ismember(params.factors,commonFactors)) - mrErrorDlg('(mlrGroupTtest) Cannot run t-tests because factors are missing in some scans'); + 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'); From a02f17ec3f83e227266d0c3669679f7b7abb70a8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 30 Mar 2021 12:32:00 +0300 Subject: [PATCH 144/254] remapSurfaces.m: added option to check for specific remapped surfaces + output name of all surfaces that will be remapped when both dryRun and force flags are true --- mrUtilities/surfUtils/remapSurfaces.m | 60 ++++++++++++++------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/mrUtilities/surfUtils/remapSurfaces.m b/mrUtilities/surfUtils/remapSurfaces.m index 3170cb6d3..5c8591644 100644 --- a/mrUtilities/surfUtils/remapSurfaces.m +++ b/mrUtilities/surfUtils/remapSurfaces.m @@ -1,7 +1,7 @@ % remapSurfaces.m % % $Id: remapSurfaces.m 1833 2010-11-13 18:37:14Z julien $ -% usage: remapSurfaces(fssubject,fsaverage,) +% usage: remapSurfaces(fssubject,fsaverage,, , ) % by: julien besle % date: 15/05/2014 % purpose: remaps surface vertices of one Freesurfer subject to another @@ -9,11 +9,13 @@ % 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 [subjPath,fsaveragePath] = remapSurfaces(fssubject,fsaverage,force, dryRun) +function [subjPath,fsaveragePath] = remapSurfaces(fsSubject,fsAverage,force, dryRun, fsSubjectSuffix) freesurferSubjdir = []; subjPath = []; @@ -27,6 +29,9 @@ if ieNotDefined('dryRun') dryRun = false; end +if ieNotDefined('fsSubjectSuffix') + fsSubjectSuffix = ''; +end if isempty(freesurferSubjdir) || ispc freesurferSubjdir = mrGetPref('volumeDirectory'); @@ -39,17 +44,17 @@ fprintf('(remapSurfaces) Assuming that the Freesurfer subject directory is %s\n',freesurferSubjdir); end -subjPath = [freesurferSubjdir '/' fssubject]; +subjPath = [freesurferSubjdir '/' fsSubject]; if isempty(dir(subjPath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fssubject ' does not exist']); + mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsSubject ' does not exist']); subjPath = []; if ~dryRun return end end -fsaveragePath = [freesurferSubjdir '/' fsaverage]; +fsaveragePath = [freesurferSubjdir '/' fsAverage]; if isempty(dir(fsaveragePath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsaverage ' does not exist']); + mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsAverage ' does not exist']); fsaveragePath = []; if ~dryRun return @@ -71,15 +76,15 @@ return end end - -if exist(fullfile(subjPath,'/surfRelax/',[fssubject '_left_GM_' fsaverage '.off']),'file') && ~force - mrWarnDlg(sprintf('(remapSurfaces) Mapping between Freesurfer subjects %s and %s already exists, use optional argument to recompute',fssubject,fsaverage)); + +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 +if dryRun && ~force return end @@ -87,26 +92,25 @@ 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 @@ -120,7 +124,7 @@ 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}]; @@ -135,7 +139,7 @@ %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]; @@ -149,16 +153,16 @@ 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 Date: Wed, 31 Mar 2021 09:24:45 +0300 Subject: [PATCH 145/254] mlrSphericalNormGroup.m, freesurferSphericalNormalizationVolumes.m: added option to use alternative surfaces in surfRelax folder --- .../groupAnalysis/mlrSphericalNormGroup.m | 22 ++++++++++++++++--- .../freesurferSphericalNormalizationVolumes.m | 10 ++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index 102b5d8b6..d161e8786 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -19,8 +19,9 @@ % 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.freesurferTemplateID Freesurfer subject IF 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 (deafult: 'Spherical Normalization') +% 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 @@ -69,6 +70,9 @@ 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 @@ -155,6 +159,10 @@ 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); @@ -179,6 +187,9 @@ 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 @@ -192,6 +203,10 @@ 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; @@ -281,7 +296,7 @@ end if params.dryRun - fprintf('\n(mlrSphericalNormGroup) THIS IS A DRY RUN: not data will be exported or written to disc'); + fprintf('\n(mlrSphericalNormGroup) THIS IS A DRY RUN: no data will be exported or written to disc\n'); end % create mrLoadRet group folder @@ -534,6 +549,7 @@ 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'; diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m index 6177fb2bc..e0e8ee6f1 100644 --- a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -12,6 +12,7 @@ % 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) @@ -43,6 +44,9 @@ if fieldIsNotDefined(params,'fsSourceSubj') params.fsSourceSubj = ''; end +if fieldIsNotDefined(params,'fsSourceSurfSuffix') + params.fsSourceSurfSuffix = ''; +end if fieldIsNotDefined(params,'fsDestSubj') params.fsDestSubj = 'fsaverage'; end @@ -142,7 +146,7 @@ end % first (re)compute mapping between source and destination surfaces (if needed) -[sourcePath, destPath] = remapSurfaces(params.fsSourceSubj,params.fsDestSubj,params.recomputeRemapping,params.dryRun); +[sourcePath, destPath] = remapSurfaces(params.fsSourceSubj,params.fsDestSubj,params.recomputeRemapping,params.dryRun,params.fsSourceSurfSuffix); if (isempty(sourcePath) || isempty(destPath)) && params.dryRun return; end @@ -250,8 +254,8 @@ 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} '.off']); - destSurf{iSurf} = loadSurfOFF([sourcePath '/surfRelax/' params.fsSourceSubj '_' side{iSide} '_' surfs{iSurf} '_' params.fsDestSubj '.off']); % same surface mesh as source, but with destination coordinates + 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); From 2186f7f4b0cad2574e3e977e5d43945a8771121d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 1 Apr 2021 12:13:04 +0300 Subject: [PATCH 146/254] mrLoadRetGUI.m: when choosing bases/analyses/overlays to delete, select none by default --- mrLoadRet/GUI/mrLoadRetGUI.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/GUI/mrLoadRetGUI.m b/mrLoadRet/GUI/mrLoadRetGUI.m index cae5f7d72..1ef4c942d 100644 --- a/mrLoadRet/GUI/mrLoadRetGUI.m +++ b/mrLoadRet/GUI/mrLoadRetGUI.m @@ -1934,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); @@ -1956,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); @@ -1990,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); From 64f8fcc7c8eab12e92f0fa40e4decd1c4078bb72 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 1 Apr 2021 12:23:15 +0300 Subject: [PATCH 147/254] maskOverlay.m: do not mask if clip min=max --- mrLoadRet/GUI/maskOverlay.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mrLoadRet/GUI/maskOverlay.m b/mrLoadRet/GUI/maskOverlay.m index 3f8ce6967..cc066271d 100644 --- a/mrLoadRet/GUI/maskOverlay.m +++ b/mrLoadRet/GUI/maskOverlay.m @@ -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 From 626d4c27f105e6a009cc5bef47d698f7bd6bf896 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 1 Apr 2021 12:28:49 +0300 Subject: [PATCH 148/254] mlrGroupAverage.m: added option to compute contrasts --- .../GLM_v2/newGlmAnalysis/makeContrastNames.m | 3 +- mrLoadRet/groupAnalysis/mlrGroupAverage.m | 174 +++++++++++------- 2 files changed, 112 insertions(+), 65 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m index 0b2b80e9f..2f306d9e6 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m @@ -32,13 +32,14 @@ else connector = ' < '; end - 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/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m index e87eea891..300e6719a 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupAverage.m +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -1,8 +1,10 @@ -% [thisView,params] = mlrGroupAverage(thisView,params,<'justGetParams'>) +% [thisView, params, uniqueLevels] = mlrGroupAverage(thisView,params,<'justGetParams'>) % % goal: averages volumes in "group" scan across subjects according to conditions -% specified in .mat file linked to the scan. +% (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. @@ -15,6 +17,8 @@ % 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: @@ -25,12 +29,15 @@ % 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] = mlrGroupAverage(thisView,params,varargin) +function [thisView, params, uniqueLevels] = mlrGroupAverage(thisView,params,varargin) eval(evalargs(varargin)); if ieNotDefined('justGetParams'),justGetParams = 0;end @@ -49,6 +56,20 @@ 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; @@ -87,18 +108,10 @@ elseif ischar(params.factors) params.factors = {params.factors}; end -if fieldIsNotDefined(params,'averagingMode') - params.averagingMode = 'marginal'; % options are 'marginal' or 'interaction' -elseif ~ismember(params.averagingMode,{'marginal','interaction'}) +if ~ismember(params.averagingMode,{'marginal','interaction'}) mrWarnDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); return; end -if fieldIsNotDefined(params,'outputSampleSize') - params.outputSampleSize = false; -end - -if justGetParams, return; end - if strcmp(params.averagingMode,'interaction') if ~all(ismember(params.factors,commonFactors)) @@ -110,6 +123,8 @@ whichFactors = num2cell(1:length(params.factors)); end +if justGetParams && fieldIsNotDefined(params,'factors'), return; end + currentGroup = viewGet(thisView,'curGroup'); thisView = viewSet(thisView,'curGroup',params.groupNum); @@ -173,76 +188,107 @@ 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 + -minOverlay = inf(sum(nLevels),1); -maxOverlay = -1*inf(sum(nLevels),1); -maxCount = 0; +%-------------------------------------- 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) - fprintf('(mlrGroupAverage) Computing averages for scan %d... ',iScan); + 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 - cOverlay = 0; - for iFactor = 1:length(whichFactors) - for iOverlay = 1:nLevels(iFactor) %for each overlay - cOverlay = cOverlay + 1; + + 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(cOverlay).data{iScan} = zeros(hdr{cScan}.dim(2:4)'); % initialize scans with zeros - volumeCount = zeros(hdr{cScan}.dim(2:4)'); - for iVolume = find(ismember(whichOverlay{cScan}(:,iFactor),cOverlay,'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) - overlays(cOverlay).data{iScan}(isNotNaN) = ... - overlays(cOverlay).data{iScan}(isNotNaN) + data(isNotNaN); - volumeCount = volumeCount + isNotNaN; - end - % divide by the number of added overlays - overlays(cOverlay).data{iScan} = cast(overlays(cOverlay).data{iScan}./volumeCount,mrGetPref('defaultPrecision')); + 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(cOverlay) = min(minOverlay(cOverlay),min(overlays(cOverlay).data{iScan}(:))); - maxOverlay(cOverlay) = max(maxOverlay(cOverlay),max(overlays(cOverlay).data{iScan}(:))); - if params.outputSampleSize - overlays(sum(nLevels)+cOverlay).data{iScan} = volumeCount; - maxCount = max(maxCount,max(volumeCount(:))); - end + 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(cOverlay).data{iScan} = []; - if params.outputSampleSize - overlays(sum(nLevels)+cOverlay).data{iScan} = []; - end + overlays(nContrasts+iLevel).data{iScan} = []; end end end + if ismember(iScan,params.scanList) - fprintf('Done\n'); + mrCloseDlg(hWaitBar); end + end %add overlays' missing fields -for iOverlay = 1:sum(nLevels)*(1+params.outputSampleSize) - - if iOverlay<=sum(nLevels) - overlays(iOverlay).name = uniqueLevels{iOverlay}; - overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; - else - overlays(iOverlay).name = sprintf('N (%s)',uniqueLevels{iOverlay-sum(nLevels)}); - overlays(iOverlay).range = [0 maxCount]; - end - overlays(iOverlay).groupName = viewGet(thisView,'groupName'); - overlays(iOverlay).params = params; - overlays(iOverlay).type = 'Group average'; - overlays(iOverlay).function = 'mlrGroupAverage'; - overlays(iOverlay).interrogator = ''; - - allScanData = []; % determine the 1st-99th percentile range - for iScan = params.scanList - allScanData = [allScanData;overlays(iOverlay).data{iScan}(~isnan(overlays(iOverlay).data{iScan}))]; +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 - allScanData = sort(allScanData); - overlays(iOverlay).colorRange = allScanData(round([0.01 0.99]*numel(allScanData)))'; - end % set or create analysis From 86bde54effa77a7f0e2b0b8526c90d5bb0b3bbdd Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 1 Apr 2021 12:32:02 +0300 Subject: [PATCH 149/254] mlrGroupTtest.m: set smoothing space to be the current scan if no smoothing --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index 22c9d19ae..b31b81d80 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -33,8 +33,9 @@ % 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 (default = [1 1 1]. Set to 0 for no smoothing. -% params.smoothingSpace: space in which to smooth the data, 0 = current scan, any other number: base number (default: current base) +% 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) @@ -56,7 +57,7 @@ % % author: julien besle (17/03/2021) -function [thisView,params, uniqueLevels] = mlrGroupTtest(thisView,params,varargin) +function [thisView, params, uniqueLevels] = mlrGroupTtest(thisView,params,varargin) eval(evalargs(varargin)); if ieNotDefined('justGetParams'),justGetParams = 0;end @@ -85,7 +86,7 @@ params.contrasts = []; end if fieldIsNotDefined(params,'smoothingFWHM') - params.smoothingFWHM = [1 1 1]; + params.smoothingFWHM = 0; end if fieldIsNotDefined(params,'smoothingSpace') params.smoothingSpace = viewGet(thisView,'curbase'); @@ -251,20 +252,21 @@ 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, swithing smoothing space to current scan'); + 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 contrastNames = makeContrastNames(params.contrasts,uniqueLevels,params.testSide); nContrasts = size(params.contrasts,1); From e68cc19b225228032c169cf1884d0ee525743c50 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 1 Apr 2021 12:33:01 +0300 Subject: [PATCH 150/254] mlrGroupTtest.m: fill overlays with empty data if not on scanlist + other small fixes --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 44 +++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index b31b81d80..53d3efa27 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -243,7 +243,7 @@ nLevelsAcrossFactors = nLevelsAcrossFactors + nLevels(iFactor); end -if fieldIsNotDefined(params,'contrasts') +if fieldIsNotDefined(params,'contrasts') || size(params.contrasts,2)~=sum(nLevels) params.contrasts = eye(sum(nLevels)); end @@ -270,8 +270,9 @@ %-------------------------------------- Compute contrasts and run t-tests contrastNames = makeContrastNames(params.contrasts,uniqueLevels,params.testSide); nContrasts = size(params.contrasts,1); -minOverlay = inf(nContrasts* (1 + params.fweAdjustment + params.fdrAdjustment + ... - params.outputStatistic + params.outputContrastEstimates + params.outputContrastSte + params.outputSampleSize),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') @@ -288,7 +289,7 @@ maxSampleSize = sampleSize; else if maxSampleSize~=sampleSize - mrWarnDlg('(mlrGroupTtest) the number of subject overlays should be identical at all levels or combinations of levels'); + mrWarnDlg('(mlrGroupTtest) The number of subject overlays should be identical at all levels or combinations of levels'); return; end end @@ -302,7 +303,7 @@ hWaitBar = mrWaitBar(-inf,waitString); end - % compute the contrast estiamtes, statistics and p values + % 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); @@ -313,7 +314,7 @@ cLevel = 0; dLevel = 0; for iFactor = 1:length(whichFactors) - for iOverlay = 1:nLevels(iFactor) + for iLevel = 1:nLevels(iFactor) cLevel = cLevel+1; if ismember(cLevel,nonZeroContrastLevels) dLevel = dLevel+1; @@ -329,12 +330,16 @@ end end if numel(nonZeroContrastLevels)>1 + sampleSizesDiffer = false; for i = 2:dLevel if nnz(diff(sampleSize(:,[1 i]),1,2)) - keyboard % Ns are not equal across levels at all voxels + sampleSizesDiffer = true; end end - sampleSize = sampleSize(:,1); + 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 @@ -403,8 +408,8 @@ 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(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); - maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + 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 @@ -415,16 +420,16 @@ 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(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); - maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + 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(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); - maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + 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; @@ -432,8 +437,8 @@ 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(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); - maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + 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 @@ -473,11 +478,16 @@ 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 @@ -501,7 +511,7 @@ overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; overlays(iOverlay).groupName = viewGet(thisView,'groupName'); overlays(iOverlay).params = params; - overlays(iOverlay).type = 'Group average'; + overlays(iOverlay).type = 'Group t-test'; overlays(iOverlay).function = 'mlrGroupTtest'; overlays(iOverlay).interrogator = ''; switch(iOutput) From 15e2ddc235434dbcf9a38b92b3f4aeb3ef66587a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 2 Apr 2021 06:47:34 +0300 Subject: [PATCH 151/254] mlrGroupTtest.m: only output one sampleSize overlay --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index 53d3efa27..c7f493bc3 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -268,6 +268,7 @@ 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 + ... @@ -330,7 +331,6 @@ end end if numel(nonZeroContrastLevels)>1 - sampleSizesDiffer = false; for i = 2:dLevel if nnz(diff(sampleSize(:,[1 i]),1,2)) sampleSizesDiffer = true; @@ -467,11 +467,17 @@ end if params.outputSampleSize - overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(sampleSize,outputPrecision); - overlays(nOutputs*nContrasts+iContrast).name = ['N: ' 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}(:))); + 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 @@ -491,6 +497,7 @@ end end +%add overlays' missing fields switch(params.testOutput) case 'P' clipThreshold = [0 params.pThreshold]; @@ -502,9 +509,6 @@ clipThreshold = [-log10(params.pThreshold) inf]; alphaOverlayExponent = .5; end - - -%add overlays' missing fields for iOutput = 1:nOutputs for iContrast = 1:nContrasts iOverlay = (iOutput-1)*nContrasts + iContrast; @@ -550,6 +554,9 @@ 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) From 08d2cca1bac64e81a9a1fc2d7b809aeaba0bcf9b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 2 Apr 2021 06:49:11 +0300 Subject: [PATCH 152/254] mlrGroupTtest.m: corrected error in computing standard error (was taking variance insstead of standard deviation) --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index c7f493bc3..7f3402562 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -375,8 +375,8 @@ % compute contrast estimates and std error contrastEstimates = nansum(subjectContrastEstimates,4)./sampleSize; - contrastSte = nansum((subjectContrastEstimates-repmat(contrastEstimates,[1 1 1 maxSampleSize])).^2,4) ./ ... % sum of squared errors - (sampleSize-1) ./ ... % divided by N-1 + 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; From 5124b2ba005473db1c5e90faf78da23823e00459 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 2 Apr 2021 10:42:11 +0300 Subject: [PATCH 153/254] mlrGroupTtest.m: added option to choose the level of p-correction to threshold the contrast estimates with --- mrLoadRet/groupAnalysis/mlrGroupTtest.m | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m index 7f3402562..c86649063 100644 --- a/mrLoadRet/groupAnalysis/mlrGroupTtest.m +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -42,7 +42,7 @@ % 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.thresholdEstimates: whether to clip contrast estimate overlays using corresponding p-value (using most conservative correction) +% 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) @@ -106,8 +106,8 @@ if fieldIsNotDefined(params,'fdrAdjustment') params.fdrAdjustment= true; end -if fieldIsNotDefined(params,'thresholdEstimates') - params.thresholdEstimates = true; +if fieldIsNotDefined(params,'thresholdCorrection') + params.thresholdCorrection = 'FDR'; end if fieldIsNotDefined(params,'outputStatistic') params.outputStatistic = false; @@ -532,16 +532,25 @@ overlays(iOverlay).colorRange = [0 -log10(1e-16)]; end case nOutputContrast - if params.thresholdEstimates - 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 + 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 + 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 From 4204aa6535d9399a162642ed8f9f03539c5778e3 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 2 Apr 2021 19:09:08 +0300 Subject: [PATCH 154/254] mlrSphericalNormGroup.m: added option to choose between linear interpolation and (conservative) binarization when resampling ROI masks --- mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m index d161e8786..e4f5a497e 100644 --- a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -27,6 +27,9 @@ % 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) @@ -103,6 +106,9 @@ 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 @@ -553,7 +559,7 @@ fsSphericalParams.fsDestSubj = params.freesurferTemplateID; fsSphericalParams.destVol = convertNames(:,1); fsSphericalParams.interpMethod = 'linear'; - fsSphericalParams.outputBinaryData = false; % UNCLEAR IF THIS SHOULD BE TRUE OR FALSE (ONLY MATTERS FOR ROIs) + fsSphericalParams.outputBinaryData = params.binarizeROIs; fsSphericalParams.dryRun = params.dryRun; fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); if params.combineLeftAndRight @@ -763,6 +769,9 @@ 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); From 3366262a407aec51a1f6e1b7ebf96f7bc0aca691 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 6 Apr 2021 17:22:29 +0300 Subject: [PATCH 155/254] added fillHolesInROI.m --- .../transformROIFunctions/fillHolesInROI.m | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 mrLoadRet/Plugin/GLM_v2/transformROIFunctions/fillHolesInROI.m 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)]'; + + From 6091b96283bbc66378264dd8e1e5b0a45aa1d643 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 8 Apr 2021 14:53:22 +0300 Subject: [PATCH 156/254] took mlrPaseAdditionalArguments.m out of transformROIs.m and combineTransformOverlays.m --- .../Plugin/GLM_v2/combineTransformOverlays.m | 26 +++---------------- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 21 +-------------- .../mlrParseAdditionalArguments.m | 19 ++++++++++++++ 3 files changed, 23 insertions(+), 43 deletions(-) create mode 100644 mrUtilities/MatlabUtilities/mlrParseAdditionalArguments.m diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 75ca8625a..d5c8f928c 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -318,7 +318,7 @@ end %parse additional array inputs -additionalArrayArgs = parseArguments(params.additionalArrayArgs,','); +additionalArrayArgs = mlrParseAdditionalArguments(params.additionalArrayArgs,','); if ~isempty(additionalArrayArgs) for iArg = 1:length(additionalArrayArgs) for iScan = params.scanList @@ -335,7 +335,7 @@ 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') @@ -474,7 +474,7 @@ %name of output overlays for iOutput=1:params.nOutputOverlays if params.nOutputOverlays>1 - name = ['Ouput ' num2str(iOutput) ' - ']; + name = ['Output ' num2str(iOutput) ' - ']; else name = ''; end @@ -672,26 +672,6 @@ 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 - arguments{nArgs} = token; - end - -end - function printHelp(params) if strcmp(params.combineFunction,'User Defined') diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index 10687c8f4..93044456d 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -146,7 +146,7 @@ end %parse other additional inputs -additionalArgs = parseArguments(params.additionalArgs,','); +additionalArgs = mlrParseAdditionalArguments(params.additionalArgs,','); %construct function call functionString=''; for iArg = 1:length(additionalArgs) @@ -182,25 +182,6 @@ refreshMLRDisplay(viewGet(thisView,'viewNum')); 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 -% - strings with double quotes for non-numerical values -% so that it can be used with eval -% Julien Besle, 08/07/2010 -nArgs = 0; -arguments = cell(0); -remain = argumentString; -while ~isempty(remain) - nArgs = nArgs+1; - [token,remain] = strtok(remain, separator); - if ~isempty(str2num(token)) - arguments{nArgs} = token; - else - arguments{nArgs} = ['''' token '''']; - end -end function printHelp(params) 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 From d761b64a18c36be8ae597feb9e196cd39d5d23d0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 21 Apr 2021 11:51:21 +0300 Subject: [PATCH 157/254] getBaseSpaceOverlay.m: added option to get information froma a different group --- mrLoadRet/GUI/getBaseSpaceOverlay.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/GUI/getBaseSpaceOverlay.m b/mrLoadRet/GUI/getBaseSpaceOverlay.m index aa601003c..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); From 282427ba6fe993230c760e8bdc7e70f289a67513 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 21 Apr 2021 11:57:59 +0300 Subject: [PATCH 158/254] mrPrint.m: can now change any parameter from a script + fixed roi number limits in parameters dialog --- mrLoadRet/GUI/mrPrint.m | 137 ++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 028cc8914..af0423bad 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -1,13 +1,19 @@ % mrPrint.m % % $Id$ -% usage: mrPrint(v,<'useDefault=1'>,<'roiSmooth=0'>,<'roiLabels=0'>) +% 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 f = 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 @@ -17,77 +23,70 @@ mrGlobals; % get input arguments -%getArgs(varargin,{'useDefault=1','roiSmooth=0','roiLabels=0'}); -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'); - - -% grab the image -mlrDispPercent(-inf,'(mrPrint) Rerendering image'); -[img base roi overlays altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); -mlrDispPercent(inf); - -% validate rois -validROIs = {}; -for roiNum = 1:length(roi) - if ~isempty(roi{roiNum}) - validROIs{end+1} = roi{roiNum}; +visibleROIs = viewGet(v,'visibleROIs'); + +if ieNotDefined('params') + % 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.'}; 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.'}; -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'}; - end -else - % ROI options for flatmaps and images - if ~isempty(roi) - 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.'}; - 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'}; + % 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(visibleROIs) + paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; end + else + % ROI options for flatmaps and images + if ~isempty(visibleROIs) + 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.'}; + 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 + 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 - 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 useDefault + params = mrParamsDefault(paramsInfo); + else + params = mrParamsDialog(paramsInfo,'Print figure options'); + end end - -if isempty(params),return,end + +if isempty(params) || justGetParams,return,end + +% grab the image +mlrDispPercent(-inf,'(mrPrint) Rerendering image'); +[img, base, roi, overlays, altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); +mlrDispPercent(inf); +roi = roi(visibleROIs); % just so the code won't break. roiSmooth is only fro baseType = 1 if ~isfield(params,'roiSmooth') params.roiSmooth = 0;end @@ -135,7 +134,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); @@ -172,7 +171,7 @@ upSampMask(:,:,2) = upBlur(double(mask(:,:,2)),log2(params.upSampleFactor)); upSampMask(:,:,3) = upBlur(double(mask(:,:,3)),log2(params.upSampleFactor)); img = upSampImage; - mask = upSampMask/max(upSampMask(:));; + mask = upSampMask/max(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; @@ -187,9 +186,9 @@ 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),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)) From 4acc49c32704f9e8af5926e2268d4ba108b9d03f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 21 Apr 2021 11:58:30 +0300 Subject: [PATCH 159/254] mrPrint.m: fixed colorbar ticks --- mrLoadRet/GUI/mrPrint.m | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index af0423bad..e855c3a11 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -339,14 +339,17 @@ H = colorbar(params.colorbarLoc); % set the colorbar ticks, making sure to switch % them if we have a vertical as opposed to horizontal bar + yTicks = get(gui.colorbar,'YTick'); + xTicks = (get(gui.colorbar,'XTick')-0.5)/length(colormap); + xTickLabels = get(gui.colorbar,'XTicklabel'); 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')); + set(H,'XTick',yTicks); + set(H,'Ytick',xTicks); + set(H,'YTickLabel',xTickLabels); else - set(H,'YTick',get(gui.colorbar,'YTick')); - set(H,'Xtick',get(gui.colorbar,'XTick')); - set(H,'XTickLabel',get(gui.colorbar,'XTicklabel')); + set(H,'YTick',yTicks); + set(H,'Xtick',xTicks); + set(H,'XTickLabel',xTickLabels); end set(H,'XColor',foregroundColor); set(H,'YColor',foregroundColor); From 03e7e7b724c87c6cadfa930a12c66d80ab4f9f5c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 21 Apr 2021 11:59:45 +0300 Subject: [PATCH 160/254] viewSet.m (case overlayCmap): can now set (same) colormap for multiple overlays --- mrLoadRet/View/viewSet.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index a843c3726..89c6c0746 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -1842,10 +1842,14 @@ if ~isempty(analysisNum) & ~isempty(overlayNum) & ... ~isempty(view.analyses{analysisNum}.overlays) evalstr = [val,'(256)']; - view.analyses{analysisNum}.overlays(overlayNum).colormap = eval(evalstr); + 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 - view.analyses{analysisNum}.overlays(overlayNum).colormap = val; + for iOverlay = 1:length(overlayNum) + view.analyses{analysisNum}.overlays(overlayNum(iOverlay)).colormap = val; + end else mrWarnDlg(sprintf('Unknown Color Map')); end From b1d2ec8cf906502ca1e60d92f811706e6179ecce Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 21 Apr 2021 14:33:09 +0300 Subject: [PATCH 161/254] mrPrint.m: added option to crop image (and ROIs) + fixed upsampling --- mrLoadRet/GUI/mrPrint.m | 105 +++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index e855c3a11..0bc393c1e 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -28,6 +28,17 @@ % 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 if ieNotDefined('params') % first get parameters that the user wants to display @@ -58,6 +69,9 @@ paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; end else + 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'}; % ROI options for flatmaps and images if ~isempty(visibleROIs) 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.'}; @@ -70,7 +84,6 @@ 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 - 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 @@ -82,6 +95,9 @@ if isempty(params) || justGetParams,return,end +cropX = params.cropX; +cropY = params.cropY; + % grab the image mlrDispPercent(-inf,'(mrPrint) Rerendering image'); [img, base, roi, overlays, altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); @@ -161,26 +177,31 @@ 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)); + if upSampleFactor > 1 + upSampImage(:,:,1) = upSample(img(:,:,1),params.upSampleFactor); + upSampImage(:,:,2) = upSample(img(:,:,2),params.upSampleFactor); + upSampImage(:,:,3) = upSample(img(:,:,3),params.upSampleFactor); + upSampMask(:,:,1) = upBlur(double(mask(:,:,1)),params.upSampleFactor); + upSampMask(:,:,2) = upBlur(double(mask(:,:,2)),params.upSampleFactor); + upSampMask(:,:,3) = upBlur(double(mask(:,:,3)),params.upSampleFactor); img = upSampImage; - mask = upSampMask/max(upSampMask(:)); + 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; % 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 @@ -275,6 +296,16 @@ end end else + + %crop image + cropX(1) = min(cropX(1),size(img,2)); + cropY(1) = min(cropY(1),size(img,1)); + cropX(2) = min(cropX(2), size(img,2) - cropX(1))+1; + cropY(2) = min(cropY(2), size(img,1) - cropY(1))+1; + if cropX(1)>1 || cropY(1)>1 || cropX(2) 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; + if ~params.roiSmooth % draw the lines line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color,'LineWidth',params.roiLineWidth); @@ -486,7 +534,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]); @@ -521,20 +570,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 @@ -552,7 +601,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 Date: Thu, 22 Apr 2021 13:05:03 +0300 Subject: [PATCH 162/254] mrPrint.m: added options to transform and format colorbar ticks and label + reorganized params menu --- mrLoadRet/GUI/mrPrint.m | 94 +++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 0bc393c1e..e49f4d798 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -39,19 +39,15 @@ imageDimensions = imageDimensions(1:2); end end +nOverlays = numel(viewGet(v,'curOverlay')); if ieNotDefined('params') % 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} = {'fontSize',16,'Font size of the title and colorbar title'}; 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.'}; - end - % options for surfaces - if baseType == 2 + 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 @@ -65,15 +61,25 @@ 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(visibleROIs) - paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; - end - else + 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'}; - % ROI options for flatmaps and images - if ~isempty(visibleROIs) + 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 + 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 nOverlays==1 + paramsInfo{end+1} = {'colorbarScale',viewGet(v,'overlayColorRange'),'type=array','Lower and upper limits of the color scale to display on the color bar'}; + paramsInfo{end+1} = {'colorbarScaleFunction','@(x)x','type=string','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',4,'type=numeric','round=1','incdec=[-1 1]','minmax=[2 inf]','Number of ticks on the colorbar'}; + end + if ~isempty(visibleROIs) + if baseType == 2 % ROI options for surfaces + paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; + else % ROI options for flatmaps and images 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.'}; 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.'}; @@ -372,7 +378,30 @@ % them if we have a vertical as opposed to horizontal bar yTicks = get(gui.colorbar,'YTick'); xTicks = (get(gui.colorbar,'XTick')-0.5)/length(colormap); - xTickLabels = get(gui.colorbar,'XTicklabel'); + xTickLabels = str2num(get(gui.colorbar,'XTicklabel')); + if ~fieldIsNotDefined(params,'colorbarTickNumber') + xTicks = linspace(xTicks(1),xTicks(end),params.colorbarTickNumber); + xTickLabels = linspace(xTickLabels(1),xTickLabels(end),params.colorbarTickNumber)'; + end + if ~fieldIsNotDefined(params,'colorbarScale') + colorScale = viewGet(v,'overlayColorRange'); + xTickLabels =(xTickLabels-colorScale(1))/diff(colorScale)*diff(params.colorbarScale)+params.colorbarScale(1); + end + if ~fieldIsNotDefined(params,'colorbarScaleFunction') + colorbarScaleFunction = str2func(params.colorbarScaleFunction); + xTickLabels = colorbarScaleFunction(xTickLabels); + end + xTickLabels = num2str( xTickLabels, '%.3f'); + % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) + totalColNum = size(xTickLabels,2); + for iTick = 1:size(xTickLabels,1) + colNum = totalColNum; + while xTickLabels(iTick,colNum)=='0' || xTickLabels(iTick,colNum)=='.' + colNum=colNum-1; + end + xTickLabels(iTick,:) = circshift(xTickLabels(iTick,:),-colNum); + xTickLabels(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); + end if ismember(params.colorbarLoc,{'EastOutside','WestOutside'}) set(H,'XTick',yTicks); set(H,'Ytick',xTicks); @@ -384,17 +413,38 @@ 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); + %color bar title (label) + set(get(H,'Label'),'String',params.colorbarTitle); + set(get(H,'Label'),'Interpreter','none'); + set(get(H,'Label'),'Color',foregroundColor); + set(get(H,'Label'),'FontSize',params.fontSize-4); + switch(lower(params.colorbarLoc)) % change default position of label depending on location of colorbar + case 'southoutside' + set(get(H,'Label'),'position',[0.5 2]) + case 'northoutside' +% do nothing + case 'eastoutside' + set(get(H,'Label'),'position',[-1 0.5]); + imagePosition = get(axisHandle,'position'); + imagePosition(1) = imagePosition(1)-0.01; + set(axisHandle,'position',imagePosition); + labelPosition = get(H,'position'); + labelPosition(1) = labelPosition(1)+0.01; + set(H,'position',labelPosition); + case 'westoutside' +% set(H,'axisLocation','in') + set(get(H,'Label'),'position',[1 0.5]) + set(get(H,'Label'),'rotation',270) + end end % create a title -H = title(params.title); -set(H,'Interpreter','none'); -set(H,'Color',foregroundColor); -set(H,'FontSize',16); +if ~isempty(params.title) + H = title(params.title); + set(H,'Interpreter','none'); + set(H,'Color',foregroundColor); + set(H,'FontSize',params.fontSize); +end drawnow; From 60912fbf3564eb8ac33382a2c161e7c8a3c66eb4 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 22 Apr 2021 14:44:38 +0300 Subject: [PATCH 163/254] mrPrint.m: fixed code for surfaces after previous changes (including adding default inputs in getBaseSlice.m so it can be called from mrPrint.m) --- mrLoadRet/GUI/getBaseSlice.m | 17 +++++++++++++++++ mrLoadRet/GUI/mrPrint.m | 35 +++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 12 deletions(-) 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/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index e49f4d798..6b888e1ea 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -54,8 +54,9 @@ 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)); + [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; 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.'}; @@ -69,7 +70,7 @@ 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 - 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} = {'colorbarLoc',{'South','North','East','West','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 nOverlays==1 paramsInfo{end+1} = {'colorbarScale',viewGet(v,'overlayColorRange'),'type=array','Lower and upper limits of the color scale to display on the color bar'}; @@ -101,8 +102,10 @@ if isempty(params) || justGetParams,return,end -cropX = params.cropX; -cropY = params.cropY; +if baseType<2 + cropX = params.cropX; + cropY = params.cropY; +end % grab the image mlrDispPercent(-inf,'(mrPrint) Rerendering image'); @@ -373,7 +376,15 @@ % display the colormap if ~strcmp(params.colorbarLoc,'None') && ~isempty(cmap) - H = colorbar(params.colorbarLoc); + + if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) + colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure + else + colorbarLoc = [params.colorbarLoc 'Outside']; + end + H = colorbar(colorbarLoc); + set(H,'axisLocation','out'); %make sure ticks and labels are pointing away from the figure (not the case by default when colorbar is inside the axes) + % set the colorbar ticks, making sure to switch % them if we have a vertical as opposed to horizontal bar yTicks = get(gui.colorbar,'YTick'); @@ -402,7 +413,7 @@ xTickLabels(iTick,:) = circshift(xTickLabels(iTick,:),-colNum); xTickLabels(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); end - if ismember(params.colorbarLoc,{'EastOutside','WestOutside'}) + if ismember(lower(params.colorbarLoc),{'east','west'}) set(H,'XTick',yTicks); set(H,'Ytick',xTicks); set(H,'YTickLabel',xTickLabels); @@ -413,17 +424,18 @@ end set(H,'XColor',foregroundColor); set(H,'YColor',foregroundColor); + %color bar title (label) set(get(H,'Label'),'String',params.colorbarTitle); set(get(H,'Label'),'Interpreter','none'); set(get(H,'Label'),'Color',foregroundColor); set(get(H,'Label'),'FontSize',params.fontSize-4); switch(lower(params.colorbarLoc)) % change default position of label depending on location of colorbar - case 'southoutside' + case 'south' set(get(H,'Label'),'position',[0.5 2]) - case 'northoutside' + case 'north' % do nothing - case 'eastoutside' + case 'east' set(get(H,'Label'),'position',[-1 0.5]); imagePosition = get(axisHandle,'position'); imagePosition(1) = imagePosition(1)-0.01; @@ -431,8 +443,7 @@ labelPosition = get(H,'position'); labelPosition(1) = labelPosition(1)+0.01; set(H,'position',labelPosition); - case 'westoutside' -% set(H,'axisLocation','in') + case 'west' set(get(H,'Label'),'position',[1 0.5]) set(get(H,'Label'),'rotation',270) end From 6f8f5864c5baf77645af0c47d896b3304bc434f6 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 22 Apr 2021 15:16:02 +0300 Subject: [PATCH 164/254] mrPrint.m: do not attempt to print colorbar when multiple overlays --- mrLoadRet/GUI/mrPrint.m | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 6b888e1ea..7c3e49e98 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -70,9 +70,9 @@ 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 - paramsInfo{end+1} = {'colorbarLoc',{'South','North','East','West','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 nOverlays==1 + paramsInfo{end+1} = {'colorbarLoc',{'South','North','East','West','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'}; paramsInfo{end+1} = {'colorbarScale',viewGet(v,'overlayColorRange'),'type=array','Lower and upper limits of the color scale to display on the color bar'}; paramsInfo{end+1} = {'colorbarScaleFunction','@(x)x','type=string','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',4,'type=numeric','round=1','incdec=[-1 1]','minmax=[2 inf]','Number of ticks on the colorbar'}; @@ -118,15 +118,14 @@ % get the gui, so that we can extract colorbar fig = viewGet(v,'figNum'); -if ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it +if nOverlays>1 + mrWarnDlg('(mrPrint) printing colorbar for multiple overlays is not implemented'); + cmap = []; +elseif ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it gui = guidata(fig); - % 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'); - end cmap=squeeze(cmap(1,:,:)); else cmap = []; @@ -375,7 +374,7 @@ end % display the colormap -if ~strcmp(params.colorbarLoc,'None') && ~isempty(cmap) +if ~isempty(cmap) && ~strcmp(params.colorbarLoc,'None') if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure @@ -407,7 +406,10 @@ totalColNum = size(xTickLabels,2); for iTick = 1:size(xTickLabels,1) colNum = totalColNum; - while xTickLabels(iTick,colNum)=='0' || xTickLabels(iTick,colNum)=='.' + while xTickLabels(iTick,colNum)=='0' + colNum=colNum-1; + end + if xTickLabels(iTick,colNum)=='.' colNum=colNum-1; end xTickLabels(iTick,:) = circshift(xTickLabels(iTick,:),-colNum); From 519bfde7b66e044149215c10bc3f1b2e1fdfac8d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 22 Apr 2021 19:11:06 +0300 Subject: [PATCH 165/254] mrPrint.m: warns and returns if multi-axis display is on --- mrLoadRet/GUI/mrPrint.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 7c3e49e98..03a4c7ab5 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -20,6 +20,11 @@ return end +if viewGet(getMLRView,'baseMultiAxis')>0 + mrWarnDlg('(mrPrint) Not implemented for multi-axes display'); + return; +end + mrGlobals; % get input arguments From 598d5045e9f1cd6a061ff150e723c6c6d898e047 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 23 Apr 2021 14:11:26 +0300 Subject: [PATCH 166/254] mrPrint.m: added option to print multiple overlays in the same figure as a mosaic --- mrLoadRet/GUI/mrPrint.m | 777 +++++++++++++++++++++++----------------- 1 file changed, 439 insertions(+), 338 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 03a4c7ab5..f8c3fd0c3 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -44,13 +44,26 @@ imageDimensions = imageDimensions(1:2); end end -nOverlays = numel(viewGet(v,'curOverlay')); +overlayList = viewGet(v,'curOverlay'); +nOverlays = numel(overlayList); if ieNotDefined('params') + % default parameters + defaultTitle = sprintf('%s: %s',getLastDir(MLR.homeDir),viewGet(v,'description')); + defaultFontSize = 16; + defaultMosaicNrows = 0; + defaultMosaicMargins = [.1 .1]; + defaultColorbarScaleFunction = '@(x)x'; + colobarLocs = {'South','North','East','West','None'}; + if nOverlays>1 + colobarLocs = putOnTopOfList('None',colobarLocs); + end + defaultColorbarTickNumber = 4; + % 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} = {'fontSize',16,'Font size of the title and colorbar title'}; + paramsInfo{end+1} = {'title',defaultTitle,'Title of figure'}; + paramsInfo{end+1} = {'fontSize',defaultFontSize,'Font size of the title and colorbar title'}; paramsInfo{end+1} = {'backgroundColor',{'white','black'},'type=popupmenu','Background color, either white or black'}; if baseType == 2 % options for surfaces % if this surface has inf in the name, then guess that it is inflated and default to thresholding @@ -75,13 +88,23 @@ 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',colobarLocs,'type=popupmenu',contingentString,'Location of colorbar, select ''None'' if you do not want a colorbar'}; + paramsInfo{end+1} = {'colorbarTitle',colorbarTitle,contingentString,'Title of the colorbar'}; if nOverlays==1 - paramsInfo{end+1} = {'colorbarLoc',{'South','North','East','West','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'}; - paramsInfo{end+1} = {'colorbarScale',viewGet(v,'overlayColorRange'),'type=array','Lower and upper limits of the color scale to display on the color bar'}; - paramsInfo{end+1} = {'colorbarScaleFunction','@(x)x','type=string','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',4,'type=numeric','round=1','incdec=[-1 1]','minmax=[2 inf]','Number of ticks on the colorbar'}; + 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 baseType == 2 % ROI options for surfaces paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; @@ -103,6 +126,30 @@ 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 if isempty(params) || justGetParams,return,end @@ -111,35 +158,55 @@ cropX = params.cropX; cropY = params.cropY; end +% grab the image(s) +if params.mosaic + nImages = nOverlays; +else + nImages = 1; +end + +if nOverlays>1 && ~params.mosaic + mrWarnDlg('(mrPrint) Printing colorbar for multiple overlays is not implemented'); +end -% grab the image mlrDispPercent(-inf,'(mrPrint) Rerendering image'); -[img, base, roi, overlays, altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); -mlrDispPercent(inf); +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, ~, altBase{iImage}] = refreshMLRDisplay(viewGet(v,'viewNum')); + % get the gui, so that we can extract colorbar + fig = viewGet(v,'figNum'); + if nOverlays>1 && ~params.mosaic + cmap{iImage} = []; + elseif ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it + gui = guidata(fig); + % 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')); + 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 +mlrDispPercent(inf); % just so the code won't break. roiSmooth is only fro baseType = 1 if ~isfield(params,'roiSmooth') params.roiSmooth = 0;end -% get the gui, so that we can extract colorbar -fig = viewGet(v,'figNum'); -if nOverlays>1 - mrWarnDlg('(mrPrint) printing colorbar for multiple overlays is not implemented'); - cmap = []; -elseif ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it - gui = guidata(fig); - % grab the colorbar data - H = get(gui.colorbar,'children'); - cmap = get(H(end),'CData'); - cmap=squeeze(cmap(1,:,:)); -else - cmap = []; -end % display in graph window f = selectGraphWin; clf(f);drawnow; -axisHandle = gca(f); +set(f,'Pointer','watch');drawnow; set(f,'Name','Print figure'); set(f,'NumberTitle','off'); @@ -150,9 +217,9 @@ 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); @@ -193,18 +260,22 @@ upSampleFactor = 2^params.upSampleFactor; % up sample if called for if upSampleFactor > 1 - upSampImage(:,:,1) = upSample(img(:,:,1),params.upSampleFactor); - upSampImage(:,:,2) = upSample(img(:,:,2),params.upSampleFactor); - upSampImage(:,:,3) = upSample(img(:,:,3),params.upSampleFactor); upSampMask(:,:,1) = upBlur(double(mask(:,:,1)),params.upSampleFactor); upSampMask(:,:,2) = upBlur(double(mask(:,:,2)),params.upSampleFactor); upSampMask(:,:,3) = upBlur(double(mask(:,:,3)),params.upSampleFactor); - img = upSampImage; + 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*upSampleFactor; @@ -220,13 +291,15 @@ 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); 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 @@ -237,335 +310,363 @@ 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 -if ~isempty(cmap) - 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 -else - - %crop image - cropX(1) = min(cropX(1),size(img,2)); - cropY(1) = min(cropY(1),size(img,1)); - cropX(2) = min(cropX(2), size(img,2) - cropX(1))+1; - cropY(2) = min(cropY(2), size(img,1) - cropY(1))+1; - if cropX(1)>1 || cropY(1)>1 || cropX(2)1 + if params.mosaicNrows==0 + figPosition = get(f,'position'); + [nRows,nCols] = getArrayDimensions(nImages,figPosition(4)/figPosition(3)); + else + nRows = params.mosaicNrows; + nCols = ceil(nImages/nRows); end - - % display the image (this is for flat maps and images) - image(img); -end - -% set axis -axis(axisHandle,'equal'); -axis(axisHandle,'off'); -axis(axisHandle,'tight'); -hold(axisHandle,'on'); - -% calculate 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]); + xOuterMargin = .2; + yOuterMargin = .2; end -% display the colormap -if ~isempty(cmap) && ~strcmp(params.colorbarLoc,'None') - - if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) - colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure +for iImage = 1:nImages + if nImages>1 + subplotPosition = getSubplotPosition(1+ceil(iImage/nRows),1+iImage-floor((iImage-1)/nRows)*nRows,... + [xOuterMargin ones(1,nCols) xOuterMargin],[yOuterMargin ones(1,nRows) yOuterMargin],... + params.mosaicMargins(1),params.mosaicMargins(2)); + axisHandle{iImage} = axes('parent',f,'position',subplotPosition); else - colorbarLoc = [params.colorbarLoc 'Outside']; - end - H = colorbar(colorbarLoc); - set(H,'axisLocation','out'); %make sure ticks and labels are pointing away from the figure (not the case by default when colorbar is inside the axes) - - % set the colorbar ticks, making sure to switch - % them if we have a vertical as opposed to horizontal bar - yTicks = get(gui.colorbar,'YTick'); - xTicks = (get(gui.colorbar,'XTick')-0.5)/length(colormap); - xTickLabels = str2num(get(gui.colorbar,'XTicklabel')); - if ~fieldIsNotDefined(params,'colorbarTickNumber') - xTicks = linspace(xTicks(1),xTicks(end),params.colorbarTickNumber); - xTickLabels = linspace(xTickLabels(1),xTickLabels(end),params.colorbarTickNumber)'; + axisHandle{iImage} = gca(f); end - if ~fieldIsNotDefined(params,'colorbarScale') - colorScale = viewGet(v,'overlayColorRange'); - xTickLabels =(xTickLabels-colorScale(1))/diff(colorScale)*diff(params.colorbarScale)+params.colorbarScale(1); - end - if ~fieldIsNotDefined(params,'colorbarScaleFunction') - colorbarScaleFunction = str2func(params.colorbarScaleFunction); - xTickLabels = colorbarScaleFunction(xTickLabels); - end - xTickLabels = num2str( xTickLabels, '%.3f'); - % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) - totalColNum = size(xTickLabels,2); - for iTick = 1:size(xTickLabels,1) - colNum = totalColNum; - while xTickLabels(iTick,colNum)=='0' - colNum=colNum-1; - end - if xTickLabels(iTick,colNum)=='.' - colNum=colNum-1; - end - xTickLabels(iTick,:) = circshift(xTickLabels(iTick,:),-colNum); - xTickLabels(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); + + % set the colormap + if ~isempty(cmap{iImage}) + colormap(cmap{iImage}); end - if ismember(lower(params.colorbarLoc),{'east','west'}) - set(H,'XTick',yTicks); - set(H,'Ytick',xTicks); - set(H,'YTickLabel',xTickLabels); + + % 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{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',axisHandle{iImage}); + hold on + % make sure x direction is normal to make right/right + set(axisHandle{iImage},'XDir','reverse'); + set(axisHandle{iImage},'YDir','normal'); + set(axisHandle{iImage},'ZDir','normal'); + % set the camera taret to center of surface + camtarget(axisHandle{iImage},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{iImage},9); + setMLRViewAngle(v,axisHandle{iImage}); + % 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{iImage}); + end + end + end + end else - set(H,'YTick',yTicks); - set(H,'Xtick',xTicks); - set(H,'XTickLabel',xTickLabels); - end - set(H,'XColor',foregroundColor); - set(H,'YColor',foregroundColor); - - %color bar title (label) - set(get(H,'Label'),'String',params.colorbarTitle); - set(get(H,'Label'),'Interpreter','none'); - set(get(H,'Label'),'Color',foregroundColor); - set(get(H,'Label'),'FontSize',params.fontSize-4); - switch(lower(params.colorbarLoc)) % change default position of label depending on location of colorbar - case 'south' - set(get(H,'Label'),'position',[0.5 2]) - case 'north' -% do nothing - case 'east' - set(get(H,'Label'),'position',[-1 0.5]); - imagePosition = get(axisHandle,'position'); - imagePosition(1) = imagePosition(1)-0.01; - set(axisHandle,'position',imagePosition); - labelPosition = get(H,'position'); - labelPosition(1) = labelPosition(1)+0.01; - set(H,'position',labelPosition); - case 'west' - set(get(H,'Label'),'position',[1 0.5]) - set(get(H,'Label'),'rotation',270) + + %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) 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*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; - 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 + % display the colormap + if ~isempty(cmap{1}) && ~strcmpi(params.colorbarLoc,'None') + if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) + colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure + else + colorbarLoc = [params.colorbarLoc 'Outside']; + end + H = colorbar(colorbarLoc); + set(H,'axisLocation','out'); %make sure ticks and labels are pointing away from the figure (not the case by default when colorbar is inside the axes) + + % set the colorbar ticks, making sure to switch + % them if we have a vertical as opposed to horizontal bar + if ~fieldIsNotDefined(params,'colorbarTickNumber') + xTicks{iImage} = linspace(xTicks{iImage}(1),xTicks{iImage}(end),params.colorbarTickNumber); + xTickLabels{iImage} = linspace(xTickLabels{iImage}(1),xTickLabels{iImage}(end),params.colorbarTickNumber)'; + end + if ~fieldIsNotDefined(params,'colorbarScale') + colorScale = viewGet(v,'overlayColorRange'); + xTickLabels{iImage} =(xTickLabels{iImage}-colorScale(1))/diff(colorScale)*diff(params.colorbarScale)+params.colorbarScale(1); + end + if ~fieldIsNotDefined(params,'colorbarScaleFunction') + colorbarScaleFunction = str2func(params.colorbarScaleFunction); + xTickLabels{iImage} = colorbarScaleFunction(xTickLabels{iImage}); + end + xTickLabels{iImage} = num2str( xTickLabels{iImage}, '%.3f'); + % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) + totalColNum = size(xTickLabels{iImage},2); + for iTick = 1:size(xTickLabels{iImage},1) + colNum = totalColNum; + while xTickLabels{iImage}(iTick,colNum)=='0' + colNum=colNum-1; 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; - - if ~params.roiSmooth - % draw the lines - line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color,'LineWidth',params.roiLineWidth); - end - end - end + if xTickLabels{iImage}(iTick,colNum)=='.' + colNum=colNum-1; end + xTickLabels{iImage}(iTick,:) = circshift(xTickLabels{iImage}(iTick,:),-colNum); + xTickLabels{iImage}(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); + end + if ismember(lower(params.colorbarLoc),{'east','west'}) + set(H,'XTick',yTicks{iImage}); + set(H,'Ytick',xTicks{iImage}); + set(H,'YTickLabel',xTickLabels{iImage}); + else + set(H,'YTick',yTicks{iImage}); + set(H,'Xtick',xTicks{iImage}); + set(H,'XTickLabel',xTickLabels{iImage}); + end + set(H,'XColor',foregroundColor); + set(H,'YColor',foregroundColor); + if nImages>1 && params.mosaic + set(H,'tickDirection','out'); % for mosaic display, colorbars are smaller, so orient the ticks outwards + end + + %color bar title (label) + set(get(H,'Label'),'String',params.colorbarTitle); + set(get(H,'Label'),'Interpreter','none'); + set(get(H,'Label'),'Color',foregroundColor); + set(get(H,'Label'),'FontSize',params.fontSize-4); + switch(lower(params.colorbarLoc)) % change default position of label depending on location of colorbar + case 'south' + set(get(H,'Label'),'position',[0.5 2]) + case 'north' + % do nothing + case 'east' + set(get(H,'Label'),'position',[-1 0.5]); + imagePosition = get(axisHandle{iImage},'position'); + imagePosition(1) = imagePosition(1)-0.01; + set(axisHandle{iImage},'position',imagePosition); + labelPosition = get(H,'position'); + labelPosition(1) = labelPosition(1)+0.01; + set(H,'position',labelPosition); + case 'west' + set(get(H,'Label'),'position',[1 0.5]) + set(get(H,'Label'),'rotation',270) 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'); + % create a title + if ~isempty(params.title) && iImage==1 + H = title(params.title); + set(H,'Interpreter','none'); + set(H,'Color',foregroundColor); + set(H,'FontSize',params.fontSize); + end + + % draw the roi + 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 = roi{rnum}.color; + else + color = 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; + 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 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,'LineWidth',params.roiLineWidth); + end + end + + % draw the labels + 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'); + end + end - mlrDispPercent(inf); end +set(f,'Pointer','arrow'); % bring up print dialog global mrPrintWarning From edc1debd8fa2558f0b87fa3e3cae3b35750530c5 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 23 Apr 2021 15:51:05 +0300 Subject: [PATCH 167/254] mrPrint.m: added option to set the figure position --- mrLoadRet/GUI/mrPrint.m | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index f8c3fd0c3..88bb359c7 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -47,6 +47,14 @@ 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')); @@ -63,8 +71,9 @@ % first get parameters that the user wants to display paramsInfo = {}; paramsInfo{end+1} = {'title',defaultTitle,'Title of figure'}; - paramsInfo{end+1} = {'fontSize',defaultFontSize,'Font size of the title and colorbar title'}; + paramsInfo{end+1} = {'fontSize',defaultFontSize,'Font size of the title. Colorbar title will be set to this value minus 2'}; 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'); @@ -152,7 +161,16 @@ end end -if isempty(params) || justGetParams,return,end +if ~isempty(params) && ~justGetParams + set(f,'color',params.backgroundColor); + set(f,'position',params.figurePosition); +end +set(f,'unit','pixels'); % set units back to pixels because this is what selectGraphWin assumes + +if isempty(params) || justGetParams + close(f); + return +end if baseType<2 cropX = params.cropX; @@ -199,18 +217,11 @@ end mlrDispPercent(inf); -% just so the code won't break. roiSmooth is only fro baseType = 1 -if ~isfield(params,'roiSmooth') params.roiSmooth = 0;end - - -% display in graph window -f = selectGraphWin; -clf(f);drawnow; +figure(f); % bring figure to the foreground set(f,'Pointer','watch');drawnow; -set(f,'Name','Print figure'); -set(f,'NumberTitle','off'); -set(f,'color',params.backgroundColor) +% 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; From 2d76b0ffa427b7892a5d2b4258798ad8f8e59073 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 23 Apr 2021 18:27:14 +0300 Subject: [PATCH 168/254] mrGetPref.m, editOverlayGUImrParams.m: added mrpref for adding custom colormap folders or overlays + updated matlab predefined colormaps --- mrLoadRet/Edit/editOverlayGUImrParams.m | 49 +++++++++++++- mrUtilities/MatlabUtilities/mrGetPref.m | 87 +++++++++++++------------ 2 files changed, 90 insertions(+), 46 deletions(-) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index 6a5968e66..b0ba5a9c1 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -55,10 +55,27 @@ 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 + fid = fopen(fullfile(matlabroot,'\toolbox\matlab\graph3d\contents.m')); + if fid + colormaps = textscan(fid,'%% %s %s %*[^\n]'); + fclose(fid); + if ~isempty(colormaps) + colormapStart = find(ismember(colormaps{1},'Color') & ismember(colormaps{2},'maps.'))+1; + colormapEnd = find(ismember(colormaps{1},'') & ismember(colormaps{2},'')); + colormapEnd = colormapEnd(find(colormapEnd>colormapStart,1,'first'))-1; + colormaps = colormaps{1}(colormapStart:colormapEnd)'; + end + else + colormaps = {}; + end + if isempty(colormaps) + colormaps = {'default','hot','hsv','pink','cool','bone','copper','flag','gray','jet'}; + 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/']; @@ -67,6 +84,27 @@ function editOverlayGUImrParams(viewNum) 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 = {}; @@ -272,7 +310,12 @@ function mrCmapCallback(params,viewNum) 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 diff --git a/mrUtilities/MatlabUtilities/mrGetPref.m b/mrUtilities/MatlabUtilities/mrGetPref.m index e05837a55..ca9345555 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 From 2c4ac949cf7438cf010457c07cf83ba07b8cf925 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 24 Apr 2021 15:56:44 +0300 Subject: [PATCH 169/254] mrPrint.m: fixed colorscales when multiple overlays printed as separate images have different colorscales --- mrLoadRet/GUI/mrPrint.m | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 88bb359c7..d021b392f 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -365,11 +365,6 @@ axisHandle{iImage} = gca(f); end - % set the colormap - if ~isempty(cmap{iImage}) - colormap(cmap{iImage}); - end - % now display the images if baseType == 2 % this is the surface display @@ -491,13 +486,15 @@ end % display the colormap - if ~isempty(cmap{1}) && ~strcmpi(params.colorbarLoc,'None') + if ~isempty(cmap{iImage}) && ~strcmpi(params.colorbarLoc,'None') if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure else colorbarLoc = [params.colorbarLoc 'Outside']; end H = colorbar(colorbarLoc); + % set the colormap + colormap(axisHandle{iImage},cmap{iImage}); set(H,'axisLocation','out'); %make sure ticks and labels are pointing away from the figure (not the case by default when colorbar is inside the axes) % set the colorbar ticks, making sure to switch From 9677d074be0aef255979c2fd7f3b6f152e1acac7 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 24 Apr 2021 16:11:37 +0300 Subject: [PATCH 170/254] mrPrint.m: when calling from a script and printing overlays as separate images, can specify one image title and one colormap title per image --- mrLoadRet/GUI/mrPrint.m | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index d021b392f..726603cff 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -70,7 +70,7 @@ % first get parameters that the user wants to display paramsInfo = {}; - paramsInfo{end+1} = {'title',defaultTitle,'Title of figure'}; + paramsInfo{end+1} = {'title',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} = {'fontSize',defaultFontSize,'Font size of the title. Colorbar title will be set to this value minus 2'}; 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]).'}; @@ -108,7 +108,7 @@ colorbarTitle = viewGet(v,'overlayName'); end paramsInfo{end+1} = {'colorbarLoc',colobarLocs,'type=popupmenu',contingentString,'Location of colorbar, select ''None'' if you do not want a colorbar'}; - paramsInfo{end+1} = {'colorbarTitle',colorbarTitle,contingentString,'Title of the colorbar'}; + 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 @@ -169,7 +169,30 @@ if isempty(params) || justGetParams close(f); - return + return; +end + +if 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 +end +if ischar(params.title) + params.title = {params.title}; +end +if params.mosaic + if length(params.title)>1 && length(params.title)~=nOverlays + mrWarnDlg('(mrPrint) The number of colorbar titles must match the number of overlays') + close(f) + return; + end end if baseType<2 @@ -541,7 +564,7 @@ end %color bar title (label) - set(get(H,'Label'),'String',params.colorbarTitle); + set(get(H,'Label'),'String',params.colorbarTitle{iImage}); set(get(H,'Label'),'Interpreter','none'); set(get(H,'Label'),'Color',foregroundColor); set(get(H,'Label'),'FontSize',params.fontSize-4); @@ -565,8 +588,8 @@ end % create a title - if ~isempty(params.title) && iImage==1 - H = title(params.title); + if ~isempty(params.title{iImage}) && (iImage==1 || length(params.title{iImage})>1) + H = title(params.title{iImage}); set(H,'Interpreter','none'); set(H,'Color',foregroundColor); set(H,'FontSize',params.fontSize); From 82afb0e05ce98c92dbaa244fb9da4c5921db5105 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 26 Apr 2021 11:18:11 +0300 Subject: [PATCH 171/254] mrPrint.m: added colorbarLoc options 'outsideEW' and 'outsideSN' --- mrLoadRet/GUI/mrPrint.m | 95 ++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 726603cff..004f9f8f2 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -62,9 +62,9 @@ defaultMosaicNrows = 0; defaultMosaicMargins = [.1 .1]; defaultColorbarScaleFunction = '@(x)x'; - colobarLocs = {'South','North','East','West','None'}; + colorbarLocs = {'South','North','East','West','OutsideSN','OutsideEW','None'}; if nOverlays>1 - colobarLocs = putOnTopOfList('None',colobarLocs); + colorbarLocs = putOnTopOfList('None',colorbarLocs); end defaultColorbarTickNumber = 4; @@ -107,7 +107,7 @@ contingentString = ''; colorbarTitle = viewGet(v,'overlayName'); end - paramsInfo{end+1} = {'colorbarLoc',colobarLocs,'type=popupmenu',contingentString,'Location of colorbar, select ''None'' if you do not want a colorbar'}; + 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'}; @@ -172,6 +172,12 @@ return; end +% ------------------- Checks on parameters + +if nOverlays>1 && ~params.mosaic + mrWarnDlg('(mrPrint) Printing colorbar for multiple overlays is not implemented'); +end + if ischar(params.colorbarTitle) params.colorbarTitle = {params.colorbarTitle}; end @@ -195,21 +201,29 @@ end end +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 +end + +% ----------------------- Grab the image(s) if baseType<2 cropX = params.cropX; cropY = params.cropY; end -% grab the image(s) + if params.mosaic nImages = nOverlays; else nImages = 1; end -if nOverlays>1 && ~params.mosaic - mrWarnDlg('(mrPrint) Printing colorbar for multiple overlays is not implemented'); -end - mlrDispPercent(-inf,'(mrPrint) Rerendering image'); for iImage = 1:nImages if nOverlays>1 && params.mosaic @@ -240,6 +254,9 @@ end mlrDispPercent(inf); + +% ----------------------- Print the images + figure(f); % bring figure to the foreground set(f,'Pointer','watch');drawnow; @@ -366,6 +383,7 @@ img{iImage}(img{iImage}<0) = 0;img{iImage}(img{iImage}>1) = 1; end +% ------------- Display the images if nImages>1 if params.mosaicNrows==0 figPosition = get(f,'position'); @@ -380,18 +398,19 @@ for iImage = 1:nImages if nImages>1 - subplotPosition = getSubplotPosition(1+ceil(iImage/nRows),1+iImage-floor((iImage-1)/nRows)*nRows,... + curCol = ceil(iImage/nRows); + curRow = iImage-floor((iImage-1)/nRows)*nRows; + subplotPosition = getSubplotPosition(1+curCol,1+curRow,... [xOuterMargin ones(1,nCols) xOuterMargin],[yOuterMargin ones(1,nRows) yOuterMargin],... params.mosaicMargins(1),params.mosaicMargins(2)); axisHandle{iImage} = axes('parent',f,'position',subplotPosition); else + curCol = 1; + curRow = 1; axisHandle{iImage} = gca(f); end - % now display the images - if baseType == 2 - % this is the surface display - + if baseType == 2 % this is the surface display curBase = viewGet(v,'curBase'); for iBase = 1:viewGet(v,'numBase') if viewGet(v,'baseType',iBase)>=2 @@ -509,11 +528,35 @@ end % display the colormap - if ~isempty(cmap{iImage}) && ~strcmpi(params.colorbarLoc,'None') - if baseType == 2 && ismember(lower(params.colorbarLoc),{'south','north','east'}) - colorbarLoc = params.colorbarLoc; % put the color bar inside the axes for a tighter figure + switch(lower(params.colorbarLoc)) + case 'outsidesn' + if nRows==1 + colorbarLoc = 'South'; + elseif curRow==1 + colorbarLoc = 'North'; + elseif curRow==nRows + colorbarLoc = 'South'; + else + colorbarLoc = 'None'; + end + case 'outsideew' + if nRows==1 + colorbarLoc = 'East'; + elseif curCol==1 + colorbarLoc = 'West'; + elseif curCol==nCols + colorbarLoc = 'East'; + else + colorbarLoc = 'None'; + end + otherwise + colorbarLoc = params.colorbarLoc; + end + if ~isempty(cmap{iImage}) && ~strcmpi(colorbarLoc,'None') + if baseType == 2 && ismember(lower(colorbarLoc),{'south','north','east'}) + % leave as is to put the color bar inside the axes for a tighter figure else - colorbarLoc = [params.colorbarLoc 'Outside']; + colorbarLoc = [colorbarLoc 'Outside']; end H = colorbar(colorbarLoc); % set the colormap @@ -548,7 +591,7 @@ xTickLabels{iImage}(iTick,:) = circshift(xTickLabels{iImage}(iTick,:),-colNum); xTickLabels{iImage}(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); end - if ismember(lower(params.colorbarLoc),{'east','west'}) + if ismember(lower(colorbarLoc),{'east','west','eastoutside','westoutside'}) set(H,'XTick',yTicks{iImage}); set(H,'Ytick',xTicks{iImage}); set(H,'YTickLabel',xTickLabels{iImage}); @@ -567,13 +610,13 @@ set(get(H,'Label'),'String',params.colorbarTitle{iImage}); set(get(H,'Label'),'Interpreter','none'); set(get(H,'Label'),'Color',foregroundColor); - set(get(H,'Label'),'FontSize',params.fontSize-4); - switch(lower(params.colorbarLoc)) % change default position of label depending on location of colorbar - case 'south' - set(get(H,'Label'),'position',[0.5 2]) - case 'north' + set(get(H,'Label'),'FontSize',params.fontSize-2); + switch(lower(colorbarLoc)) % change default position of label depending on location of colorbar + case {'south','southoutside'} + set(get(H,'Label'),'position',[0.5 3]) + case {'north','northoutside'} % do nothing - case 'east' + case {'east','eastoutside'} set(get(H,'Label'),'position',[-1 0.5]); imagePosition = get(axisHandle{iImage},'position'); imagePosition(1) = imagePosition(1)-0.01; @@ -581,7 +624,7 @@ labelPosition = get(H,'position'); labelPosition(1) = labelPosition(1)+0.01; set(H,'position',labelPosition); - case 'west' + case {'west','westoutside'} set(get(H,'Label'),'position',[1 0.5]) set(get(H,'Label'),'rotation',270) end @@ -595,7 +638,7 @@ set(H,'FontSize',params.fontSize); end - % draw the roi + % --------------- Draw the ROIs if baseType ~= 2 if iImage==1 sliceNum = viewGet(v,'currentSlice'); From 1d20bd6f3b3d67b02b0430cb5690a8687eb82ab0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 26 Apr 2021 14:02:46 +0300 Subject: [PATCH 172/254] mrPrint.m: added imageTitleLoc option + renamed parameter 'title' to 'imageTitle' --- mrLoadRet/GUI/mrPrint.m | 65 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 004f9f8f2..a73796e6c 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -58,7 +58,7 @@ if ieNotDefined('params') % default parameters defaultTitle = sprintf('%s: %s',getLastDir(MLR.homeDir),viewGet(v,'description')); - defaultFontSize = 16; + defaultFontSize = 14; defaultMosaicNrows = 0; defaultMosaicMargins = [.1 .1]; defaultColorbarScaleFunction = '@(x)x'; @@ -66,12 +66,14 @@ if nOverlays>1 colorbarLocs = putOnTopOfList('None',colorbarLocs); end + imageTitleLocs = {'South','North','East','West','OutsideSN','OutsideEW','None'}; defaultColorbarTickNumber = 4; % first get parameters that the user wants to display paramsInfo = {}; - paramsInfo{end+1} = {'title',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} = {'fontSize',defaultFontSize,'Font size of the title. Colorbar title will be set to this value minus 2'}; + 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} = {'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 @@ -190,11 +192,11 @@ params.colorbarTitle = repmat(params.colorbarTitle,1,nOverlays); end end -if ischar(params.title) - params.title = {params.title}; +if ischar(params.imageTitle) + params.imageTitle = {params.imageTitle}; end if params.mosaic - if length(params.title)>1 && length(params.title)~=nOverlays + if length(params.imageTitle)>1 && length(params.imageTitle)~=nOverlays mrWarnDlg('(mrPrint) The number of colorbar titles must match the number of overlays') close(f) return; @@ -210,6 +212,15 @@ 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 % ----------------------- Grab the image(s) @@ -631,8 +642,46 @@ end % create a title - if ~isempty(params.title{iImage}) && (iImage==1 || length(params.title{iImage})>1) - H = title(params.title{iImage}); + switch(lower(params.imageTitleLoc)) + case 'outsidesn' + if nRows==1 + imageTitleLoc = 'South'; + elseif curRow==1 + imageTitleLoc = 'North'; + elseif curRow==nRows + imageTitleLoc = 'South'; + else + imageTitleLoc = 'None'; + end + case 'outsideew' + if nRows==1 + imageTitleLoc = 'East'; + elseif curCol==1 + imageTitleLoc = 'West'; + elseif curCol==nCols + 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}); + 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','none'); set(H,'Color',foregroundColor); set(H,'FontSize',params.fontSize); From b45d8573b8abe8b5919ee0b8c67119e90070e83d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 27 Apr 2021 14:02:52 +0300 Subject: [PATCH 173/254] mrPrint.m: now draws colorbar in separate axes for better control of colorbar location --- mrLoadRet/GUI/mrPrint.m | 222 +++++++++++++++++++++++++++------------- 1 file changed, 151 insertions(+), 71 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index a73796e6c..6ed0e7bfe 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -66,7 +66,7 @@ if nOverlays>1 colorbarLocs = putOnTopOfList('None',colorbarLocs); end - imageTitleLocs = {'South','North','East','West','OutsideSN','OutsideEW','None'}; + imageTitleLocs = {'North','South','East','West','OutsideSN','OutsideEW','None'}; defaultColorbarTickNumber = 4; % first get parameters that the user wants to display @@ -227,6 +227,9 @@ if baseType<2 cropX = params.cropX; cropY = params.cropY; +else + cropX = [0 1]; + cropY = [0 1]; end if params.mosaic @@ -395,31 +398,66 @@ end % ------------- Display the images -if nImages>1 - if params.mosaicNrows==0 - figPosition = get(f,'position'); - [nRows,nCols] = getArrayDimensions(nImages,figPosition(4)/figPosition(3)); - else - nRows = params.mosaicNrows; - nCols = ceil(nImages/nRows); - end - xOuterMargin = .2; - yOuterMargin = .2; +if params.mosaicNrows==0 + figPosition = get(f,'position'); + [nImageRows,nImageCols] = getArrayDimensions(nImages,figPosition(4)/figPosition(3)); +else + 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 + +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; + colorbarCols = []; + imageRows = 2; + colorbarRows = []; end - for iImage = 1:nImages - if nImages>1 - curCol = ceil(iImage/nRows); - curRow = iImage-floor((iImage-1)/nRows)*nRows; - subplotPosition = getSubplotPosition(1+curCol,1+curRow,... - [xOuterMargin ones(1,nCols) xOuterMargin],[yOuterMargin ones(1,nRows) yOuterMargin],... - params.mosaicMargins(1),params.mosaicMargins(2)); - axisHandle{iImage} = axes('parent',f,'position',subplotPosition); - else - curCol = 1; - curRow = 1; - axisHandle{iImage} = gca(f); - end + 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'); @@ -448,22 +486,22 @@ 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{iImage}); + 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(axisHandle{iImage},'XDir','reverse'); - set(axisHandle{iImage},'YDir','normal'); - set(axisHandle{iImage},'ZDir','normal'); + set(hImage,'XDir','reverse'); + set(hImage,'YDir','normal'); + set(hImage,'ZDir','normal'); % set the camera taret to center of surface - camtarget(axisHandle{iImage},mean(baseSurface.vtcs)) + 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(axisHandle{iImage},9); - setMLRViewAngle(v,axisHandle{iImage}); + camva(hImage,9); + setMLRViewAngle(v,hImage); % 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{iImage}); + patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', roi{roiNum}.overlayImage,'facecolor','interp','edgecolor','none','FaceAlpha',params.roiAlpha,'Parent',hImage); end end end @@ -484,10 +522,10 @@ end % set axis - axis(axisHandle{iImage},'equal'); - axis(axisHandle{iImage},'off'); - axis(axisHandle{iImage},'tight'); - hold(axisHandle{iImage},'on'); + axis(hImage,'equal'); + axis(hImage,'off'); + axis(hImage,'tight'); + hold(hImage,'on'); % calculate directions params.plotDirections = 0; @@ -541,38 +579,85 @@ % display the colormap switch(lower(params.colorbarLoc)) case 'outsidesn' - if nRows==1 + if nImageRows==1 + colorbarPosition = getSubplotPosition(imageCols(curImageCol),colorbarRows(2),colWidths,rowWidths,0,0); colorbarLoc = 'South'; - elseif curRow==1 + elseif curImageRow==1 + colorbarPosition = getSubplotPosition(imageCols(curImageCol),colorbarRows(1),colWidths,rowWidths,0,0); colorbarLoc = 'North'; - elseif curRow==nRows + elseif curImageRow==nImageRows + colorbarPosition = getSubplotPosition(imageCols(curImageCol),colorbarRows(2),colWidths,rowWidths,0,0); colorbarLoc = 'South'; else + colorbarPosition = []; colorbarLoc = 'None'; end + case {'south','north'} + colorbarPosition = getSubplotPosition(imageCols(curImageCol),colorbarRows(curImageRow),colWidths,rowWidths,0,0); + colorbarLoc = params.colorbarLoc; case 'outsideew' - if nRows==1 + if nImageRows==1 + colorbarPosition = getSubplotPosition(colorbarCols(2),imageRows(curImageRow),colWidths,rowWidths,0,0); colorbarLoc = 'East'; - elseif curCol==1 + elseif curImageCol==1 + colorbarPosition = getSubplotPosition(colorbarCols(1),imageRows(curImageRow),colWidths,rowWidths,0,0); colorbarLoc = 'West'; - elseif curCol==nCols + elseif curImageCol==nImageCols + colorbarPosition = getSubplotPosition(colorbarCols(2),imageRows(curImageRow),colWidths,rowWidths,0,0); colorbarLoc = 'East'; else + colorbarPosition = []; colorbarLoc = 'None'; end - otherwise + case {'east','west'} + colorbarPosition = getSubplotPosition(colorbarCols(curImageCol),imageRows(curImageRow),colWidths,rowWidths,0,0); colorbarLoc = params.colorbarLoc; end - if ~isempty(cmap{iImage}) && ~strcmpi(colorbarLoc,'None') - if baseType == 2 && ismember(lower(colorbarLoc),{'south','north','east'}) - % leave as is to put the color bar inside the axes for a tighter figure - else - colorbarLoc = [colorbarLoc 'Outside']; + if ~isempty(cmap{iImage}) && ~isempty(colorbarPosition) + hColorbar = axes('parent',f,'Position',colorbarPosition); + if baseType < 2 + switch(lower(colorbarLoc)) + case {'north','south'} + xlim([1 size(img{iImage},2)]); + ylim([1 round(size(img{iImage},1)*colorbarRowWidth)]); + case {'east','west'} + xlim([1 round(size(img{iImage},2)*colorbarColWidth)]); + xlim([1 size(img{iImage},1)]); + end + end + set(hColorbar,'visible','off') + axis(hColorbar,'off'); + axis(hColorbar,'equal'); + + % adjust the colorbar to be 40% of the width/height of the colorbar axis (depending on the orientation) + colorbarWidth = .4; + colorbarLength = .8; + switch(lower(colorbarLoc)) + case 'north' + colorbarPosition(1) = colorbarPosition(1) + colorbarPosition(3)*(1-.8)/2; + colorbarPosition(3) = colorbarPosition(3) * colorbarLength; + colorbarPosition(2) = colorbarPosition(2) + colorbarPosition(4)*.1; + colorbarPosition(4) = colorbarPosition(4) * colorbarWidth; + case 'south' + colorbarPosition(1) = colorbarPosition(1) + colorbarPosition(3)*(1-.8)/2; + colorbarPosition(3) = colorbarPosition(3) * colorbarLength; + colorbarPosition(2) = colorbarPosition(2) + colorbarPosition(4)*.5; + colorbarPosition(4) = colorbarPosition(4) * colorbarWidth; + case 'west' + colorbarPosition(1) = colorbarPosition(1) + colorbarPosition(3)*.5; + colorbarPosition(3) = colorbarPosition(3) * colorbarWidth; + colorbarPosition(2) = colorbarPosition(2) + colorbarPosition(4)*(1-.8)/2; + colorbarPosition(4) = colorbarPosition(4) * colorbarLength; + case 'east' + colorbarPosition(1) = colorbarPosition(1) + colorbarPosition(3)*.1; + colorbarPosition(3) = colorbarPosition(3) * colorbarWidth; + colorbarPosition(2) = colorbarPosition(2) + colorbarPosition(4)*(1-.8)/2; + colorbarPosition(4) = colorbarPosition(4) * colorbarLength; end - H = colorbar(colorbarLoc); + H = colorbar(hColorbar,colorbarLoc,'Position',colorbarPosition); % set the colormap - colormap(axisHandle{iImage},cmap{iImage}); - set(H,'axisLocation','out'); %make sure ticks and labels are pointing away from the figure (not the case by default when colorbar is inside the axes) + colormap(hColorbar,cmap{iImage}); + set(H,'axisLocation','in'); %make sure ticks and labels are pointing away from the image (i.e. towards the inside of the colorbar axes) % set the colorbar ticks, making sure to switch % them if we have a vertical as opposed to horizontal bar @@ -624,41 +709,35 @@ set(get(H,'Label'),'FontSize',params.fontSize-2); switch(lower(colorbarLoc)) % change default position of label depending on location of colorbar case {'south','southoutside'} - set(get(H,'Label'),'position',[0.5 3]) + set(get(H,'Label'),'position',[0.5 -1]) case {'north','northoutside'} - % do nothing + set(get(H,'Label'),'position',[0.5 2]) case {'east','eastoutside'} - set(get(H,'Label'),'position',[-1 0.5]); - imagePosition = get(axisHandle{iImage},'position'); - imagePosition(1) = imagePosition(1)-0.01; - set(axisHandle{iImage},'position',imagePosition); - labelPosition = get(H,'position'); - labelPosition(1) = labelPosition(1)+0.01; - set(H,'position',labelPosition); - case {'west','westoutside'} - set(get(H,'Label'),'position',[1 0.5]) set(get(H,'Label'),'rotation',270) + set(get(H,'Label'),'position',[4.4 0.5]); + case {'west','westoutside'} + set(get(H,'Label'),'position',[-1.9 0.5]) end end % create a title switch(lower(params.imageTitleLoc)) case 'outsidesn' - if nRows==1 + if nImageRows==1 imageTitleLoc = 'South'; - elseif curRow==1 + elseif curImageRow==1 imageTitleLoc = 'North'; - elseif curRow==nRows + elseif curImageRow==nImageRows imageTitleLoc = 'South'; else imageTitleLoc = 'None'; end case 'outsideew' - if nRows==1 + if nImageRows==1 imageTitleLoc = 'East'; - elseif curCol==1 + elseif curImageCol==1 imageTitleLoc = 'West'; - elseif curCol==nCols + elseif curImageCol==nImageCols imageTitleLoc = 'East'; else imageTitleLoc = 'None'; @@ -667,7 +746,7 @@ imageTitleLoc = params.imageTitleLoc; end if ~strcmpi(imageTitleLoc,'none') && ~isempty(params.imageTitle{iImage}) && (iImage==1 || length(params.imageTitle{iImage})>1) - H = title(params.imageTitle{iImage}); + H = title(params.imageTitle{iImage},'parent',hImage); switch(lower(imageTitleLoc)) case 'north' % don't change the default position @@ -772,13 +851,13 @@ % 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,'LineWidth',params.roiLineWidth); + line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color,'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); + 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); @@ -788,6 +867,7 @@ end end + drawnow; end set(f,'Pointer','arrow'); From 2633156f7bdc07bd7586ae5be3a99522f9c0ea41 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 28 Apr 2021 12:52:47 +0300 Subject: [PATCH 174/254] mrPrint.m: adjusted alignment of colobar tick labels --- mrLoadRet/GUI/mrPrint.m | 49 +++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 6ed0e7bfe..d1efaa6ec 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -658,9 +658,8 @@ % set the colormap colormap(hColorbar,cmap{iImage}); set(H,'axisLocation','in'); %make sure ticks and labels are pointing away from the image (i.e. towards the inside of the colorbar axes) - - % set the colorbar ticks, making sure to switch - % them if we have a vertical as opposed to horizontal bar + + % apply scaling to ticks and tick labels if ~fieldIsNotDefined(params,'colorbarTickNumber') xTicks{iImage} = linspace(xTicks{iImage}(1),xTicks{iImage}(end),params.colorbarTickNumber); xTickLabels{iImage} = linspace(xTickLabels{iImage}(1),xTickLabels{iImage}(end),params.colorbarTickNumber)'; @@ -674,6 +673,7 @@ xTickLabels{iImage} = colorbarScaleFunction(xTickLabels{iImage}); end xTickLabels{iImage} = num2str( xTickLabels{iImage}, '%.3f'); + % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) totalColNum = size(xTickLabels{iImage},2); for iTick = 1:size(xTickLabels{iImage},1) @@ -687,7 +687,38 @@ xTickLabels{iImage}(iTick,:) = circshift(xTickLabels{iImage}(iTick,:),-colNum); xTickLabels{iImage}(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); end - if ismember(lower(colorbarLoc),{'east','west','eastoutside','westoutside'}) + + % remove spaces and align characters depending on the location of the colorbar + minLeftSpacesEnd = totalColNum; + maxRightSpacesStart = 1; + for iTick = 1:size(xTickLabels{iImage},1) + startCol = find(~isspace(xTickLabels{iImage}(iTick,:)),1,'first'); + switch(lower(colorbarLoc)) + case 'west' + leftSpacesEnd = startCol-1; + rightSpacesStart = totalColNum+1; + case {'north','south'} + leftSpacesEnd = round((startCol-1)/2); + rightSpacesStart = totalColNum-(startCol-1-leftSpacesEnd)+1; + xTickLabels{iImage}(iTick,leftSpacesEnd+1:rightSpacesStart-1) = xTickLabels{iImage}(iTick,startCol:end); + xTickLabels{iImage}(iTick,1:leftSpacesEnd) = ' '; + xTickLabels{iImage}(iTick,rightSpacesStart:end) = ' '; + case 'east' + xTickLabels{iImage}(iTick,1:totalColNum-startCol+1) = xTickLabels{iImage}(iTick,startCol:end); + xTickLabels{iImage}(iTick,totalColNum-startCol+2:end) = ' '; + leftSpacesEnd = 0; + rightSpacesStart = totalColNum-startCol+2; + end + minLeftSpacesEnd = min(minLeftSpacesEnd,leftSpacesEnd); + maxRightSpacesStart = max(maxRightSpacesStart,rightSpacesStart); + end + % remove unnecessary spaces (altough this it not in fact necessary, as they seem to be ignored) + xTickLabels{iImage}(:,maxRightSpacesStart:end) = []; + xTickLabels{iImage}(:,1:minLeftSpacesEnd) = []; + + % set the colorbar ticks, making sure to switch + % them if we have a vertical as opposed to horizontal bar + if ismember(lower(colorbarLoc),{'east','west'}) set(H,'XTick',yTicks{iImage}); set(H,'Ytick',xTicks{iImage}); set(H,'YTickLabel',xTickLabels{iImage}); @@ -708,14 +739,14 @@ 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','southoutside'} + case 'south' set(get(H,'Label'),'position',[0.5 -1]) - case {'north','northoutside'} + case 'north' set(get(H,'Label'),'position',[0.5 2]) - case {'east','eastoutside'} + case 'east' set(get(H,'Label'),'rotation',270) set(get(H,'Label'),'position',[4.4 0.5]); - case {'west','westoutside'} + case 'west' set(get(H,'Label'),'position',[-1.9 0.5]) end end @@ -795,7 +826,7 @@ label{end}.str = viewGet(v,'roiName',visibleROIs(rnum)); label{end}.color = color; end - % if we have a circular apertuer then we need to + % 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 From 05c13214f378dc88fd9e6121e4614684b0e7fb1f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 17 May 2021 22:49:15 +0300 Subject: [PATCH 175/254] mrPrint: fixed ROI default colors --- mrLoadRet/GUI/mrPrint.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index d1efaa6ec..a0909a9e9 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -197,7 +197,7 @@ end if params.mosaic if length(params.imageTitle)>1 && length(params.imageTitle)~=nOverlays - mrWarnDlg('(mrPrint) The number of colorbar titles must match the number of overlays') + mrWarnDlg('(mrPrint) The number of overlay titles must match the number of overlays') close(f) return; end @@ -809,9 +809,9 @@ if params.roiLineWidth > 0 && ~isempty(roi{rnum}) && isfield(roi{rnum},'lines') && ~isempty(roi{rnum}.lines.x) % get color if strcmp(params.roiColor,'default') - color = roi{rnum}.color; + color{rnum} = roi{rnum}.color; else - color = color2RGB(params.roiColor); + color{rnum} = color2RGB(params.roiColor); end % deal with upSample factor roi{rnum}.lines.x = roi{rnum}.lines.x*upSampleFactor; @@ -824,7 +824,7 @@ 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; + 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 @@ -882,7 +882,7 @@ % 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,'LineWidth',params.roiLineWidth,'parent',hImage); + line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color{rnum},'LineWidth',params.roiLineWidth,'parent',hImage); end end From 00b0a6f4a6494dad20b9591c6a9ecdb8ec5b6caa Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Tue, 27 Jul 2021 11:27:54 +0100 Subject: [PATCH 176/254] remove mexmaci64 files to allow flat map generation/loading on newer macOS (catalina/big sur) - mex code breaks now, slight slow down for m code acceptable --- mrUtilities/ImageProcessing/convolve.mexmaci64 | Bin 42812 -> 0 bytes mrUtilities/ImageProcessing/corrDn.m | 2 +- mrUtilities/ImageProcessing/corrDn.mexmaci64 | Bin 30192 -> 0 bytes mrUtilities/ImageProcessing/upConv.m | 3 ++- mrUtilities/ImageProcessing/upConv.mexmaci64 | Bin 30192 -> 0 bytes 5 files changed, 3 insertions(+), 2 deletions(-) delete mode 100755 mrUtilities/ImageProcessing/convolve.mexmaci64 delete mode 100755 mrUtilities/ImageProcessing/corrDn.mexmaci64 delete mode 100755 mrUtilities/ImageProcessing/upConv.mexmaci64 diff --git a/mrUtilities/ImageProcessing/convolve.mexmaci64 b/mrUtilities/ImageProcessing/convolve.mexmaci64 deleted file mode 100755 index 6d9d9f3caf150fb7488d903c2cf21d5cf96c04bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42812 zcmeHw4R{>Yb@q<6*fI!4B7!*7U_ngd1wt%v5-)O64W1#hWY!q4f}L0hvtD7hwya3I zU{cFgkT=!MYK+o`eo8;ms10eA+iKs$u`>W zJ$GjIN0te+>GQSEV?Ey8d+*%2bLZT1-gEA`=U(qU|J#WfHk*)Tv)Quo5%Fog%4Q4T zm;OG1&%9|iTUpsMW%)8*l=;<|nYEdDv}r>#w&~{OvNE4{qc2r=a(k7z#tlH;)vN~BbxxIg3#-+_>4ay72+IUy9y;T3Gy|Ojlb(P*K)SKMi{BIjK zpElc|JjjyzyQF`f4Hea?7oOZ+LD0DM^JWW_&v@Td%RE(9=Bf5ot*v; z_|W^%z4UzRuehpmm2b^w)~$VV{l>djKZ{@E%7>`$O}}Asef!KtyuD!ijK8{yTBnca z&O7feG#^6siJn$}g!6A;STpNC`fRn&yZ-L`)BE+uxQE_z7<4bdufxm}-LL#zp}-Xi zT%o`f3S6PU6$)ITz!eHyp}>DP3Y4hqH_~I$SELeY*`w+=->ydo6}CrX9V*i|CK3r& zvYq+&yVylJxIVWqB-Onyh%YFuvM9%-x^Y2adyyOqDzoBI@r8g7cLd!9LL^I>_r3Ty z3#>tb8mw6wTZ~zV%9L6)5E8fk7`C+>bQcFpw#$yUg35l0g%rnFux5T?=-WCqQXgST zlTvu}+e)ic-yyT%aBPk&4EK$p8?z+G&Y*vuqm%hxl!GO?GK=!7(Y`T9m*fa5?2sb9 zJRv(fWpPQTvXYZF;KW=W4**h}(*>YUgn9?ZfH&WNwfZ{kS zGk5F1VbWle9buKZo0Y=f%Iy13pl12q;+(B~BpX#$Q?Gw1!ITy@EISX1`e$%Q+I5Ys z58<^Vx|-;+&!u|`Lu&9fqO8o$$w8&X#SSM*YRkpPLW(mkHbhW`X9|zX>@1$C6o%5; zlbjJz{|@S)kCOOuCupL1A}-IE%p$TQ3^NzBc8cSKx~ zb74eW9!5tJoszKIE%f*l)e}xNF3u^osh)G0^&|RM1l7||h4%euM81&CTdC`sR@XUA zb&mOWDUO{Io}RV7RTj=li4NI0E^d32C>mTJa(jNO1{HPWelgD;LkgTxASHGdDNPtT zvEkSR_yhE@h|@hadDNggQsi#jv|#XaljwWtBixZJH-Cg1!#M-hF?bOg2b0E|dQ5lvXyZu|eR{c5x2z8p(TeO5VX+={#+SJNT}i5voMgpg$x#V`xm&ZzE<6 zxPiBXK4|^bynx~uNLpN~D zz=v~T!9SeXMmR#87q>kO z!s6{)&?H_;9RWa`rMFXAPgcpotXD{N5LH#yr?QfW?0idX_$|Q&_!c07s-Z;K!jY}G zFqF%NF+35xnK>XtJsJ%f6^qBaFE=2~MTGyGX=F<WxX_gv zLyVuO--R2?Ngt%f>oKE@P7;s9lAZ$7s6|dEXV7Mgzp^&zK0TBk}&KR_7T1x%&f0^5}EZQ z`a{6?Za#p!XaGA*d=E_n#H-GB!gss@)ZuwK$f+R-QCT<#5SN`3;#&Zz-+&D=Fp+Ek+${#GAspM~}fomuEOB=Z8RPvV`IIdM1sxfwibN)!t zD`aem`V+9E_@K4%k>jSIFb495c!BJQsf@#}%zh`nJkAG{hNscdi81DeBO3?(n|1pP zrqq{ed57n7Vxb@d4N4le?7_FL+zFG)@U6e1@;>^#x(kF5y;X{iq!0K+D`f%7sK z5#reU^_+PY;UQC(P8UFOlggXEpQP$pXv=z{Y;t-+`sIHVzYgvK_0s!K%xGv5m2v%1 z#HVs%!{`N$H{aYrGuE$3E2UEDajC4dBXu*!i^=m_nqDcaQ)0VRwhu5`j?@zpIc!e`Pf)Y3zli_!0Gg#+suX^cE|Qi~mlDEjtc@G8~8XKhFga zw-tf_Ui2<74>uU{3v&lXR+GLY!Q8d1QzmwM7z=ty{%8JHTm&;ylN>{cvVnaSP5snpzfvb^vP|qJ#J)Nq)C|h&HUHYc?ZL0Hp_4TTr-|N!loybMS=T4$c zz+t_{4y$#iF5;#_uwB~{ebKPQ-ZlLo`hr4@b)glt?#+udT{Ij3lAdEP$J}^X*D(Fe z?kX2o^x<kd*s%`;AP2!=~5m z7jQU@b9VcYg2L=$%D8hLu5tfTe zc8S|=!`layTv!mTLT5s0DdOB2EO|+6_}BLnNe(g`1jIuw_)jLWk;cvz>BP+tvt2MD zMg6Pb#Z>Rh8#%waTNSt{8m_~&soCuu*o`^@hPyzdPXa3V?c@jZ+v`bwd-KEd+pQ+Q zorIk|iQnG5gx>_tOn*>*bMGRa3)V!zBL>esICy|w9M}PV+h_6Ha3;TU>n;9s zw+dnKPNK_TCAj3kena_&{BaIVt3jYkJgP4SGZAwIxe^DQv>JYu4~9LkDOhpSKYkD! zzCsKQPW(HJLMBZ8ezs`@KK&Q+3+ELvK>o-|jFS&a|1wlI_uxxxhr}jK|E9)%L>gVvwYe6g!t6-b%SoLK zOlsmjs#Y0p6nxq+Nhe?xbr(GfE^nocDq%|3YSEtqtK9%rTukY4XtGxEWp}Zm&)*<@ z-aj}&7?T87l5b0&NA(4QbI+yvd;#=%82UUnrOziwpD);F>hobspO@5X?1)--8v1;} zJrGOMu7*nQhOu6+=UhFtKF`0Lyz~?9u0D{`=Lb^y9Hpc)52Wk!(t1Og6S$hnyrkB@ zbZ{5Fv_&u3*`KD*Pp0s-1p0hLW$n=Cd0e0Go21XNc)(kAv4aj&Br^z3B14~#8v2~P zI?I>sHk9yg==07*cnWmT^Ra&*wp9!Y&^>y;`)3Chtp4j++dDY zZoJQfIhDm{?!+?OkZ*-MA2UR-KOj{L8eA#tkl_%N5}k6_ogR7y02B{5)DJZ{++veM??@a~prHo`0jpqM9S3)}6dqc#hBS^+oq``yp=FtMQb*n&ABXeu}@}Pw_WO ziND_me|N%O9VUC#Uu*Fx-trj&tKh>n7~zbBNVM#X9mwXsnwCiP}cHjy=RS@o4e6PLs!b4IZbJspZQK7(9Le zJRVLM^n=~1ls6K7PhC^LpYwO`W%xTh4JND0+2eoB#P2X`mxUvWFeW9y;Sq7$tsjcR zdna>v(A`2Nufd-U4aD48;6#)pZHeRHFVks8Tnv__2^S`jvSW<=ohdeM1f!ZPi{YnQ z659jsL4heDm&10VYzDp)wu`z%(vpGeAAsio{g;*EtN?W}$?t^fnf!iMjrXZSpS~d5 z*(o+~M@M)N^RsI>zZ0HEfaiHr;JF6Kf_wbAQ(zxJb|#*4)doC2E0K-@p4+FT;d!?u zZ<3xrlk@vtgWvb2`29oTc^Le@m-u~ZJb(Cu@cb0`eXlHxV=~B0K<~lm+~J?D8ZbGl3uWN>SeEh!z;kd@ z0**0@w^>+hAUr37!`)h>lb1r&r^7|USK3XcUyjl&r471*`;RS`@GKOwPKbf{@~|lh zLC$>+Z~*5Am8MVJb*$T`8&YEki_&7+KZZF~6yVmBUO_;Pj=+UXjdK@rJsdMBn?z$J zO_KhgWhQ>m<$zEGdW$al&*D*+wbzCnX?w7HJ6Rg<+20nf{B9)4kk( zaV?jR597ahoy&(#RLgW>v>5gdXyUHDy#L~I_RdH3UzD2qz@M^rc%S@_PWE5yM2F#k z;!}jAO87t*#sMxNmJ0&`Dj$6=`3IXnTK)@+wJ>2>JR^oa;I<4nMrcncZF12INh9VL zg!5OyAIqSf5YvJc!8?^k!@rJ!wiTxQ8syZ#dIwm`%?=vwu{6I1cMZV=F9Yiu>&;Dy z^Mcq=K&(88>8#0U(A}ixL;T5;My?I;o*fT0>}|`Rv0xg)R~qE2L9IH*=MoQX1pFiAu z)#68kV%#<{`0=ZQhtW%zd{|H7{*8WQL3Ma3j9Dwe(a;uqcjxgs* zyn5*_I4KYag+~xuLfp79ZaLRX^ZF7)r8Cj42@&7Y)azqBQ((_yZqh6p(_Z-idz|bQ z*yDG?jf2_dd09Bkxf8R^BjUD?0UZqIMUxN)b6U=gSBWuiNZP-dRgxBo!hw=o&KSU^ z%epqw0nrN&9AcU-Zu-e&zD#kVVY9GKaOB@l!8!S9oRfiK7U%qz@XI9p5(Bi}m+s#n zBL=RiOI#e7vRN*U3|^^nO>%KKxr^i8%egq-17~HrI9^8y=U49E;Pbx!kaNTLNIJKH z|Nj8~jZ5(m9CupWb{YSMH6^sBfrbsx;2Gmq!8do%)FEv>r!n)-TF-fOsmd^)`|O5C zY_{0hrPv^erIw#yTu`+8&46517S2HH$YI#S6{i7tG={ zM)4hH@n*Alxlw$xS^Sh)tQy7Fn#D`a;)T5U3l$GjG!Yp=?09S*uBrML8c|jlirs{q zrhoPY&e=;h8n#A|Fy);Ng*YVkzupmC`@d1+_z(yPX$N0OKR zDS4^cxKy0H^nCJCz_@gG@{%uk=}(PIHzzNxNM3r#xHL0)>5C}%cWei<#qL9DXmMd^ z=re7gh;3+I8}P+8bZZ-J%^SL@%}zfbYrBema@uGQ;m}oWv?WdrpR}v`wSQd#Dg`%X zXKQRst=p0PHDm1$_$j_Tk_gw0PZR&*D5^uhVigY=irfgC6Sw`E3fXYoMAp+cJ*eBe zG)&~a)r-O}>-I2$8O3k?3Yo%gnYBB5j2WjUgy2P*;k!YNA0hwTg1s)bpd0f{ERXny z)w;16;&b1^O)f_SQ}gbHs;=!_SlDmcn`+S42mhQeiZBIVUzAc&N)1qIfKn$Zb&^ss zO2sGzw}US_Oew@m_@ZNKZ~+wOf+&_zvS|(FABR33IJfy@Bn+7UhtFhbY;UaEZL^gY zh7c?p#wwPofBhmUqHt`Qnh3G>y3<)|-9%1xPT@H<(C^m(LdP()mY0cJLv+{CCU**{JWq(YS1?x`s9a%?Rh}A7oH_uS?b#vj#rXLXh4MmjSW(P4wqqqf2+MA~-Pl4Fl!l$CCmoX0g*S}SgQ222wy*@d;$U}>{s0InK|?N=PfXlR_H;tQd= zntGe~LbxD|XqeBjC_S;POAldI_Yp9^%$ zqN`64j<`HMumS9Q1O1rgvZ$zHl_F*=-`cw*ePu4i3+CAoGtPQs=j&U3Ra?`AFaeM} zxW2_#t*{X(enb(qgZ%NSzO=D+dVEHBIr=Z@_L*q)2GZBA0a@6mdJwgkYwrql>r#UC zVq9h6sOmXNZaw=6N%ze(I&dP<&cd?u%$C=*U}>%5I4(O%Bk;^_7oU#-A?kRH45Hs7 zaE?VC!@^i#q{L0n(0>vc=fT`s2VjMSQ4Z^K@BvC5EoXH&hX#h`J@#kGRp2R?=?7 zX}Sl>3G@D4z$&V(mQpqRjwpeCpj?Z>_G9YRy!q2IJ7_{2@C~c$rIFZ1#l*N$s0ep6 zunxBf>2~il;~X%liG6{}%GIee(ta!H)P* zfSkhKGVtaA6d!~3%Ro5tAK3FGeZh6a&%?rT1Y;QJ(y!m2URe^wM16#v2ZtwTffX9 zs)_G%60pe$?g;^!WlVB}5K;c3mJ##G3a~cbnD#l2tY!Be;u;c9g2yVJx z*J#=Jl=uT^qI&ms8jFxM7LQ{rMvbw6;@QD!NZkNTN?JC15q};(0sO#rFX){Vj={XN z=t2G)(Emm}*>zT!P(6FNaQ1*GDx9}qXIXeBIRMZEf;9l`*g|CJm+dy67cvG-26{u2 z?YCgEB!of7%0&tQApb`NC=FivVCR#LoL0D!{ZcDUdqk#jfN^enK z)Y=OX(3IwTruOR23w8ZA))zl4YCW8k=hz9yUN)Wx#}A3FaiL4~(2Fg%TdxPB5PRN6 z{X3yNV9d}a0z}a`PigEOv7yugyXEO%mt27_ZzQ}^>)y^aqfcWO&znYHZfOVgXEGkD z8tYXkz~G<@VLp�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 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/corrDn.mexmaci64 b/mrUtilities/ImageProcessing/corrDn.mexmaci64 deleted file mode 100755 index 24de84c52651ba8aead05b36f16f477348d400a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30192 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∋hdD^KWTTA4Uz)H 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≦>y+Nf=KiM>d^ca2t%|3&FS7w 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 H7{~twBOmg| From 8053825f7e366a27580b66be4f3a211314c61a07 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 18 May 2021 11:00:30 +0300 Subject: [PATCH 177/254] mrPrint.m: added option to use tex/latex interpreter for titles --- mrLoadRet/GUI/mrPrint.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index a0909a9e9..938c776be 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -68,12 +68,14 @@ 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 @@ -735,7 +737,7 @@ %color bar title (label) set(get(H,'Label'),'String',params.colorbarTitle{iImage}); - set(get(H,'Label'),'Interpreter','none'); + 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 @@ -792,7 +794,7 @@ set(H,'Position',[size(img{iImage},2)*1.03 size(img{iImage},1)/2 0]); set(H,'rotation',270) end - set(H,'Interpreter','none'); + set(H,'Interpreter',params.interpreter); set(H,'Color',foregroundColor); set(H,'FontSize',params.fontSize); end From 6c11a35e5f5a91765e7c5995b3c8b0ceefab2da7 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 18 May 2021 11:03:39 +0300 Subject: [PATCH 178/254] getSubplotPosition.m: corrected inaccuracies in computing the axes positions --- mrUtilities/Plot/getSubplotPosition.m | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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; From de65c28c4d601aecd821058da4de3cb9687be29a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 30 Jul 2021 15:18:21 +0300 Subject: [PATCH 179/254] mlrGuiSet.m: for recent versions of Matlab (>~9.7), no need to set the top string to 1 when updating overlay string names --- mrLoadRet/GUI/mlrGuiSet.m | 6 +++--- mrLoadRet/mrLoadRetVersion.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/GUI/mlrGuiSet.m b/mrLoadRet/GUI/mlrGuiSet.m index 768e35e7f..065db6234 100644 --- a/mrLoadRet/GUI/mlrGuiSet.m +++ b/mrLoadRet/GUI/mlrGuiSet.m @@ -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/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index e51bbecef..4e84d2392 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 9.0 9.1 9.4 9.5]; +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]; % expected toolbox if verLessThan('matlab','8.5') From 26e998926dae63c698d397501eb48128071c3c51 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Fri, 10 Sep 2021 11:46:07 +0300 Subject: [PATCH 180/254] mlrImportFreeSurfer.m: do not display warning that mriConvert cannot be found for Windows machines --- .../File/FreeSurfer/mlrImportFreeSurfer.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m index 6e1d4b87f..588958909 100755 --- 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 From a1d42a4800e12caa65289d04220c54a1c7d6d010 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Fri, 10 Sep 2021 11:47:43 +0300 Subject: [PATCH 181/254] mlrImportFreeSurfer.m: for newer versions of freesurfer (from 7), pial surface files are linux symbolic links and cannot be read on Windows. In this case, read ?h.pial.T1 --- mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m index 588958909..5d08533d8 100755 --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -177,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 From 5ef324f344cb3d4ee388274210453481cf4178c7 Mon Sep 17 00:00:00 2001 From: jb85aub Date: Thu, 16 Dec 2021 13:35:47 +0200 Subject: [PATCH 182/254] transformROIs.m: put parseArguments function back in (reverted 6091b96) because it does things slightly differently from mlrParseAdditionalArguments.m --- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index 93044456d..31fbcecaa 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -146,8 +146,7 @@ end %parse other additional inputs -additionalArgs = mlrParseAdditionalArguments(params.additionalArgs,','); -%construct function call +additionalArgs = parseArguments(params.additionalArgs,','); %construct function call functionString=''; for iArg = 1:length(additionalArgs) functionString = [functionString ',' additionalArgs{iArg}]; @@ -182,6 +181,25 @@ refreshMLRDisplay(viewGet(thisView,'viewNum')); 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 +% - strings with double quotes for non-numerical values +% so that it can be used with eval +% Julien Besle, 08/07/2010 +nArgs = 0; +arguments = cell(0); +remain = argumentString; +while ~isempty(remain) + nArgs = nArgs+1; + [token,remain] = strtok(remain, separator); + if ~isempty(str2num(token)) + arguments{nArgs} = token; + else + arguments{nArgs} = ['''' token '''']; + end +end function printHelp(params) From 291c9b179e1d4918e4426b74dad923693365cb95 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 21 Mar 2022 09:35:31 +0200 Subject: [PATCH 183/254] combineTransformOverlays.m: define params.scanList when GUI is used and there is only one scan --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index d5c8f928c..dc1e6934b 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -149,6 +149,8 @@ else scanList = params.scanList; end + else + params.scanList = 1; end if ~strcmp(params.roiMask,'None') params.roiList = selectInList(thisView,'rois','',roiList); From f6241cf75c8790a78dc4a2abd56a83fefd518547 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 21 Mar 2022 09:42:17 +0200 Subject: [PATCH 184/254] mrPrint.m: corrected bug when colorbarTitle parameter is empty --- mrLoadRet/GUI/mrPrint.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 938c776be..beeb7bd18 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -182,7 +182,7 @@ mrWarnDlg('(mrPrint) Printing colorbar for multiple overlays is not implemented'); end -if ischar(params.colorbarTitle) +if isempty(params.colorbarTitle) || ischar(params.colorbarTitle) params.colorbarTitle = {params.colorbarTitle}; end if params.mosaic From c62d79dbfc8cf210a5b2308cd015bcf0a4266dc0 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 14 Apr 2022 12:26:27 +0300 Subject: [PATCH 185/254] mrInit.m: replaced 'Raw' by defaultGroupName in error message --- mrLoadRet/Init/mrInit.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/Init/mrInit.m b/mrLoadRet/Init/mrInit.m index 025679e05..36c9ec4c9 100644 --- a/mrLoadRet/Init/mrInit.m +++ b/mrLoadRet/Init/mrInit.m @@ -141,7 +141,7 @@ % 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 From 40287818df7feb8d209f7fbf0c8355e1616b3c52 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:04:54 +0300 Subject: [PATCH 186/254] makeEmptyMLRDir.m: added option to skip prompt that the target directory already exists --- mrLoadRet/File/importSurfaceOFF.m | 2 +- mrLoadRet/File/makeEmptyMLRDir.m | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index f70e0547e..c2c15db9f 100644 --- a/mrLoadRet/File/importSurfaceOFF.m +++ b/mrLoadRet/File/importSurfaceOFF.m @@ -63,7 +63,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 diff --git a/mrLoadRet/File/makeEmptyMLRDir.m b/mrLoadRet/File/makeEmptyMLRDir.m index f21190447..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 From 22afbf90ef9b8575a00670d643ca2ea69cfad774 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:07:13 +0300 Subject: [PATCH 187/254] getptsNoDoubleClick.m: updated after changes to original getpts.m in the image processing toolbox (gets rid of warnings about the eraseMode property) --- .../MatlabUtilities/getptsNoDoubleClick.m | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) 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); From 715ee565fe8977f4b3d146a45581bae2b3738836 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:13:08 +0300 Subject: [PATCH 188/254] transformROIs.m: added option to skip prompt if overwriting ROIs --- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index 31fbcecaa..d4d1abbe4 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -3,20 +3,22 @@ % % transforms ROI(s) using pre-defined or custom functions % -% jb 11/01/2011 -% -% To just get a default parameter structure: -% +% 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 $ +% To run: +% v = transformROIs(v,params) +% v = transformROIs(v,params,'noPrompt') % to overwrite the ROI(s) without asking +% +% jb 11/01/2011 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') ')']; @@ -173,7 +175,7 @@ if ~fieldIsNotDefined(params,'roiNameSuffix') roi(iRoi).name = [roi(iRoi).name params.roiNameSuffix]; end - thisView = viewSet(thisView,'newROI',roi(iRoi)); + thisView = viewSet(thisView,'newROI',roi(iRoi),noPrompt); needToRefresh = 1; end end From 609507ac635c995de244472a1ec28c54266c251b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:14:52 +0300 Subject: [PATCH 189/254] transformROIs.m: added option to use ROI coordinates in current base from script (but this is still the base anatomy for flats and surface) --- mrLoadRet/Plugin/GLM_v2/transformROIs.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index d4d1abbe4..d007337a3 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -100,8 +100,8 @@ % if just getting params then return if justGetParams,return,end -switch(params.roiSpace) - case currentBaseString +switch(lower(params.roiSpace)) + case {lower(currentBaseString),'current base'} baseNum = viewGet(thisView,'currentbase'); newXform = viewGet(thisView,'baseXform',baseNum); newSformCode = viewGet(thisView,'baseSformCode',baseNum); @@ -110,7 +110,7 @@ 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'); From d203948f406034974381ecd517daa1d4a5bc0f3e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:19:43 +0300 Subject: [PATCH 190/254] drawROI.m: made scriptable by specifying current mrLoadRet GUI figure when getting mouse input (including replacing ginput by getrect(fig)) --- mrLoadRet/ROI/drawROI.m | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index a60012f2a..2901b2b73 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -72,7 +72,9 @@ 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); + 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=[]; @@ -97,7 +99,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 +119,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 +242,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,13 +278,13 @@ % 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(fig); else From 0dc4df09782dbd699a88aab7711f7b0f6f128f40 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Apr 2022 09:23:17 +0300 Subject: [PATCH 191/254] drawROI.m: force update of curslice.basecoords by runing refreshMLRdisplay.m --- mrLoadRet/ROI/drawROI.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index 2901b2b73..c3840089f 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -38,11 +38,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 From 73ae4f8d9c89bbfc92abf9fca652a3efc8f1a2a2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 26 May 2022 13:39:16 +0300 Subject: [PATCH 192/254] mrInit.m: if qform is missing in scan, make one based on voxel dimensions --- mrLoadRet/Init/mrInit.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mrLoadRet/Init/mrInit.m b/mrLoadRet/Init/mrInit.m index 36c9ec4c9..63f216582 100644 --- a/mrLoadRet/Init/mrInit.m +++ b/mrLoadRet/Init/mrInit.m @@ -222,6 +222,14 @@ 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}; From fe7d2f4df7eb968ebe31bef540189b980f4c05a4 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 26 May 2022 13:43:38 +0300 Subject: [PATCH 193/254] importSurfaceOFF.m: corrected bugs when importing both hemispheres --- mrLoadRet/File/importSurfaceOFF.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index c2c15db9f..8b8a838db 100644 --- a/mrLoadRet/File/importSurfaceOFF.m +++ b/mrLoadRet/File/importSurfaceOFF.m @@ -84,16 +84,16 @@ % was a left hemisphere passed? leftFlag = strfind(lower(params1.innerSurface), 'left'); if leftFlag - prefix = params1.innerSurface(1:leftFlag-1); - postfix = params1.innerSurface(leftFlag+4:end); + prefix = params1.outerCoords(1:leftFlag-1); + postfix = params1.outerCoords(leftFlag+4:end); rightFilename = sprintf('%sright%s', prefix, postfix); params2 = mrSurfViewer(rightFilename); end % 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); + prefix = params1.outerCoords(1:rightFlag-1); + postfix = params1.outerCoords(rightFlag+5:end); leftFilename = sprintf('%sleft%s', prefix, postfix); params2 = mrSurfViewer(leftFilename); end @@ -122,7 +122,7 @@ else outer1 = loadSurfOFF(params1.outerCoords); outer2 = loadSurfOFF(params2.outerCoords); - outer = combineSurfaces(inner1,inner2); + outer = combineSurfaces(outer1,outer2); end else From 9d977fc6d4e3cd606633fea0984cd3ceee5c054c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 30 May 2022 14:26:58 +0300 Subject: [PATCH 194/254] glmAnalysis.m, glmAnalysisGUI.m, getGlmStatistics.m: smoothingGaussian's FWHM now specified in mm instead of voxels to handle anisometric voxels --- .../GLM_v2/newGlmAnalysis/getGlmStatistics.m | 60 +++++++++---------- .../GLM_v2/newGlmAnalysis/glmAnalysis.m | 42 ++++++++----- .../GLM_v2/newGlmAnalysis/glmAnalysisGUI.m | 2 +- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m index 7ac327215..4ac347a62 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)); @@ -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/glmAnalysis.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m index 74accfeaa..b58f57405 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') @@ -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 diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m index 972992c87..a93739df1 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m @@ -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'},... From de6786d58cef362b5a95785c533a8888acc52958 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 30 May 2022 15:24:24 +0300 Subject: [PATCH 195/254] importSurfaceOFF.m: when loading both hemispheres, shift inflated surfaces laterally so that they do not overlap --- mrLoadRet/File/importSurfaceOFF.m | 47 +++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index 8b8a838db..941c3b0f8 100644 --- a/mrLoadRet/File/importSurfaceOFF.m +++ b/mrLoadRet/File/importSurfaceOFF.m @@ -17,7 +17,7 @@ return end base = []; -if ieNotDefined('bothHemiFlag'), +if ieNotDefined('bothHemiFlag') bothHemiFlag = 0; disp(sprintf('Only loading surfaces for one hemisphere')) else @@ -35,7 +35,7 @@ % Aborted if ieNotDefined('pathStr'),return,end -if isstr(pathStr); +if isstr(pathStr) % get surface name using mrSurfViewer [filepath filename] = fileparts(pathStr); thispwd = pwd; @@ -104,26 +104,54 @@ % 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(outer1,outer2); 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 + % 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 +204,7 @@ cd(thispwd); + function val = getBaseField(matFilename,fieldname) if ~exist(matFilename,'file') % if the anatomy doesn't have these fields set val = []; @@ -185,7 +214,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; From 759fb13bdb5cd0e42e55a6a998a2dcd3d03e5dfb Mon Sep 17 00:00:00 2001 From: jb85aub Date: Thu, 9 Jun 2022 15:33:48 +0300 Subject: [PATCH 196/254] combineTransformOverlays.m: checks for incompatibility between ROI masking and base space before converting the overlay data to base space --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index d5c8f928c..52ebd74ee 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -190,6 +190,12 @@ 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 %get the overlay data overlayData = viewGet(thisView,'overlays'); @@ -281,12 +287,7 @@ end end roiCoordsLinear = sub2ind(size(mask),roiCoords(:,1),roiCoords(:,2),roiCoords(:,3)); - if 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 - elseif isempty(roiCoordsLinear) + if isempty(roiCoordsLinear) mrWarnDlg('(combineTransformOverlays) ROI mask is empty'); set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; return From c624ee461fa25a3cc64b7afd699ea7463dfeea3c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 8 Jun 2022 14:42:47 +0300 Subject: [PATCH 197/254] importGroupScans.m: made scriptable (not tested) --- mrLoadRet/File/importGroupScans.m | 123 +++++++++++++++++++----------- 1 file changed, 79 insertions(+), 44 deletions(-) diff --git a/mrLoadRet/File/importGroupScans.m b/mrLoadRet/File/importGroupScans.m index c644e4607..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,12 +172,12 @@ % 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); @@ -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) @@ -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 From e4fc8ad1c431826c9c21f4ff2ccf44c2914669f6 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 8 Jun 2022 15:56:34 +0300 Subject: [PATCH 198/254] viewGet.m (case baseName): check that there is at least one base before attempting to get a name --- mrLoadRet/View/viewGet.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index 5506c7239..e36a6ae9f 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -1414,7 +1414,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)); From 1978627360ce1178b44169123216529a2bc13335 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 10 Jun 2022 17:50:45 +0300 Subject: [PATCH 199/254] combineTransformOverlays.m: added option to pass overlays from different scans to combine/transform function by concatenating scans on the 4th dimension --- .../Plugin/GLM_v2/combineTransformOverlays.m | 102 +++++++++++++----- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index c07ff99c9..52e3f23e1 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -55,7 +55,7 @@ combineFunctionsMenu = putOnTopOfList(combineFunctionsMenu{2},combineFunctionsMenu); end params.customCombineFunction = '';%(''@(x)max(0,-norminv(x))'; - inputOutputTypeMenu = {'3D Array','4D Array','Scalar','Structure'}; + 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 = ''; @@ -79,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 '},... @@ -175,6 +175,9 @@ if ~ieNotDefined('roiList') params.roiList = roiList; end + if ~ieNotDefined('passMultipleScans') + params.passMultipleScans = false; + end end if strcmp(params.combineFunction,'User Defined') @@ -199,6 +202,28 @@ 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{1}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scan 1 and %d have different dimensions.',iScan)); + dimensionsDiffer = true; + end + if ~isequal(base2scan{iScan},base2scan{1}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scan 1 and %d have different sforms.',iScan)); + dimensionsDiffer = true; + end + end + end + if dimensionsDiffer + return + end +end + %get the overlay data overlayData = viewGet(thisView,'overlays'); overlayData = overlayData(params.overlayList); @@ -307,6 +332,7 @@ end %reformat input data +tempScanList = params.scanList; switch(params.inputOutputType) case 'Structure' overlayData = num2cell(overlayData); @@ -318,14 +344,23 @@ 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 = mlrParseAdditionalArguments(params.additionalArrayArgs,','); if ~isempty(additionalArrayArgs) for iArg = 1:length(additionalArrayArgs) - for iScan = params.scanList - if all(cellfun(@isnumeric,additionalArrayArgs)) && ismember(params.inputOutputType,{'3D Array','4D Array'}) %if all arguments are numeric and the input type is Array + 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 @@ -342,7 +377,7 @@ %convert overlays to cell arrays if scalar function if strcmp(params.inputOutputType,'Scalar') - for iScan = params.scanList + for iScan = tempScanList for iOverlay = 1:length(params.overlayList) overlayData{iScan,iOverlay} = num2cell(overlayData{iScan,iOverlay}); %convert overlays to cell arrays end @@ -429,7 +464,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; @@ -461,8 +496,8 @@ 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})) @@ -473,6 +508,19 @@ end end +if strcmp(params.inputOutputType,'4D Array (multiple scans)') + newOutputData = cell(max(params.scanList), 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 @@ -495,22 +543,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 @@ -572,7 +626,7 @@ scanFileName = [baseName mrGetPref('niftiFileExtension')]; newPathStr = fullfile(tseriesDir,scanFileName); %find an non-empty scan to get the size of the third dimension of overlays - for iScan = 1:length(outputData) + for iScan = 1:size(outputData,1) if ~isempty(outputData{iScan}) firstNonEmptyScan = iScan; end @@ -620,7 +674,7 @@ defaultOverlay.data = []; 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); maxValue = -inf; From e52565bf9601540c7dbac86b5a0eef19fc3702f7 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 14 Jun 2022 15:40:02 +0300 Subject: [PATCH 200/254] combineTransformOverlays.m: corrected bug when processing subset of scans not starting at scan 1 --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 52e3f23e1..bbff598c4 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -209,12 +209,12 @@ scanDims{iScan} = viewGet(thisView,'scanDims',iScan); base2scan{iScan} = viewGet(thisView,'base2scan',iScan); if iScan > params.scanList(1) - if ~isequal(scanDims{iScan},scanDims{1}) - mrWarnDlg(sprintf('(combineTransformOverlays) Scan 1 and %d have different dimensions.',iScan)); + if ~isequal(scanDims{iScan},scanDims{params.scanList(1)}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scan %d and %d have different dimensions.',params.scanList(1),iScan)); dimensionsDiffer = true; end - if ~isequal(base2scan{iScan},base2scan{1}) - mrWarnDlg(sprintf('(combineTransformOverlays) Scan 1 and %d have different sforms.',iScan)); + if ~isequal(base2scan{iScan},base2scan{params.scanList(1)}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scan %d and %d have different sforms.',params.scanList(1),iScan)); dimensionsDiffer = true; end end From 5de936caa66ef14dae872be59899b267860f613b Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 14 Jun 2022 15:51:42 +0300 Subject: [PATCH 201/254] viewSet.m (overlayColorRange case): do not set the color range of overlays that do not exist --- mrLoadRet/View/viewSet.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 89c6c0746..92bddc986 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -1958,7 +1958,9 @@ 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 From eee6aca2c8b41659c6bcaabd12216d8b54d92818 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 16 Jun 2022 13:44:17 +0300 Subject: [PATCH 202/254] inverseBaseCoordMap.m: just added some comments --- mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m index 7b830569e..35f0b4db1 100644 --- a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m +++ b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m @@ -17,17 +17,17 @@ if ieNotDefined('xform') xform = eye(4); end -coordsMap = reshape(coordsMap,numel(coordsMap)/3,3); -coordsMap = (xform*[coordsMap';ones(1,size(coordsMap,1))])'; +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; +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; +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 a coordinate map of which volume index correspond to which surface voxels/vertices indices +% 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') From 28be39c0442c8e4956684dd34eec96fb6ece3162 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 22 Jun 2022 17:12:34 +0300 Subject: [PATCH 203/254] combineTransformOverlays.m: fixed bug when processing a subset of scans not starting at scan 1 --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index bbff598c4..90afc1099 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -509,7 +509,7 @@ end if strcmp(params.inputOutputType,'4D Array (multiple scans)') - newOutputData = cell(max(params.scanList), params.nOutputOverlays, size(overlayData,3)); + 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 @@ -585,7 +585,7 @@ baseCoordsOverlay{iScan} = inverseBaseCoordMap(baseCoordsMap{iScan},scanDims{iScan},base2scan{iScan}); end else - keyboard %not implemented + keyboard %not implemented (actually it might work for surfaces) end end end @@ -671,12 +671,12 @@ 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','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) @@ -742,4 +742,3 @@ function printHelp(params) disp(helpString); end end - From e4fe9d97c002585ec384522b813340918f8c357d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 24 Jun 2022 17:27:52 +0300 Subject: [PATCH 204/254] viewSet.m (case deleteGroup): get the correct number of scans if called from a different group than the one to delete --- mrLoadRet/View/viewSet.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 92bddc986..6cd5c4a25 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -281,7 +281,7 @@ % 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); From a1a29696dcf97ec4e4121d9272eff1822c4c7d70 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 18 Jul 2022 10:19:08 +0300 Subject: [PATCH 205/254] combineTransformOverlays.m: fixed bug when multiple scans and exporting output overlays to new base --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index 90afc1099..b32ff0e6d 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -708,13 +708,14 @@ % 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{1} = outputOverlay(iOverlay).data{iScan}; + 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]; From 5fbb56ce76dab5ed521296f843b8d417f8c0de23 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 21 Jul 2022 11:36:32 +0300 Subject: [PATCH 206/254] saveROI.m: now returns with a warning if the requested ROI is not found in the view --- mrLoadRet/File/saveROI.m | 5 +++++ 1 file changed, 5 insertions(+) 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 From 62399f40af3bba05520f4c6117e3c8a79bb6a20f Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 9 Aug 2022 16:40:35 +0300 Subject: [PATCH 207/254] added some comments --- .../GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m | 2 +- mrLoadRet/ROI/drawROI.m | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index 2503c1dab..03b6ad0af 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -30,7 +30,7 @@ end kernelDims = 2*w+1; kernelCenter = ceil(kernelDims/2); -[X,Y,Z] = meshgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); +[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 diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index c3840089f..0bd2b4973 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. From b74837ed53c97666de5864b8c8778f51552b64ee Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 9 Aug 2022 16:41:34 +0300 Subject: [PATCH 208/254] mrLoadRetVersion.m: added Matlab versions 9.8 --- mrLoadRet/mrLoadRetVersion.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index 4e84d2392..46df07d72 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 8.4 8.5 9.0 9.1 9.2 9.4 9.5 9.7]; +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]; % expected toolbox if verLessThan('matlab','8.5') From cb513aaeb9a6075a8df02a382a09e7d668682d5d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 12 Aug 2022 14:41:17 +0300 Subject: [PATCH 209/254] mrSurfViewer.m: corrected bug when surfRelax files contain a 'G' or an 'M' (replace strtok by strfind) --- mrLoadRet/GUI/mrSurfViewer.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mrLoadRet/GUI/mrSurfViewer.m b/mrLoadRet/GUI/mrSurfViewer.m index b75567a3f..e3586177b 100644 --- a/mrLoadRet/GUI/mrSurfViewer.m +++ b/mrLoadRet/GUI/mrSurfViewer.m @@ -31,21 +31,25 @@ if (nargin == 1) && isstr(outerSurface) && ~mlrIsFile(outerSurface) event = outerSurface; elseif (nargin == 1) && isstr(outerSurface) + outerSurface = stripext(outerSurface); if ~isempty(strfind(outerSurface,'WM')) - [token,remain] = strtok(stripext(outerSurface),'WM'); - remain = remain(3:end); + stringIndex = strfind((outerSurface),'WM'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+2:end); clear outerSurface; - innerSurface{1} = sprintf('%sGM%s.off',token,remain); + innerSurface{1} = sprintf('%sWM%s.off',token,remain); outerSurface{1} = sprintf('%sGM%s.off',token,remain); elseif ~isempty(strfind(outerSurface,'GM')) - [token,remain] = strtok(stripext(outerSurface),'GM'); - remain = remain(3:end); + stringIndex = strfind((outerSurface),'GM'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+2:end); clear outerSurface; innerSurface{1} = sprintf('%sWM%s.off',token,remain); outerSurface{1} = sprintf('%sGM%s.off',token,remain); elseif ~isempty(strfind(outerSurface,'Outer')) - [token,remain] = strtok(stripext(outerSurface),'Outer'); - remain = remain(3:end); + stringIndex = strfind((outerSurface),'Outer'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+5:end); clear outerSurface; innerSurface{1} = sprintf('%sInner%s.off',token,remain); outerSurface{1} = sprintf('%sOuter%s.off',token,remain); From 5af8a9d0cba4a0e7bd0c5744876ef99b05c4513e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 16 Aug 2022 18:34:46 +0300 Subject: [PATCH 210/254] makeFlat.m: can now be called from a script with coordinates in the current (surface) base anatomy --- mrLoadRet/Analysis/makeFlat.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/Analysis/makeFlat.m b/mrLoadRet/Analysis/makeFlat.m index 9ff683cba..2262ccbde 100644 --- a/mrLoadRet/Analysis/makeFlat.m +++ b/mrLoadRet/Analysis/makeFlat.m @@ -4,7 +4,11 @@ % 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) @@ -19,6 +23,9 @@ 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 From 538b73c49a80ceee4bf0ab0589341b4c58a7dbdc Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 16 Aug 2022 18:35:18 +0300 Subject: [PATCH 211/254] makeFlat.m: now returns the modified view --- mrLoadRet/Analysis/makeFlat.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Analysis/makeFlat.m b/mrLoadRet/Analysis/makeFlat.m index 2262ccbde..4b786ead6 100644 --- a/mrLoadRet/Analysis/makeFlat.m +++ b/mrLoadRet/Analysis/makeFlat.m @@ -10,7 +10,7 @@ % 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 @@ -130,7 +130,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 From 159f54e0574936287877dc4fdfec21d8346b4bd9 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 16 Aug 2022 18:35:49 +0300 Subject: [PATCH 212/254] makeFlat.m: changed fault radius from 75 to 60 mm --- mrLoadRet/Analysis/makeFlat.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/Analysis/makeFlat.m b/mrLoadRet/Analysis/makeFlat.m index 4b786ead6..587c63fa0 100644 --- a/mrLoadRet/Analysis/makeFlat.m +++ b/mrLoadRet/Analysis/makeFlat.m @@ -29,7 +29,7 @@ baseType = viewGet(view,'baseType'); % some other variables -defaultRadius = 75; +defaultRadius = 60; viewNum = viewGet(view, 'viewNum'); % parse the parameters From 7940578cebe8d142921671f87d9449a3e6669e3d Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 16 Aug 2022 18:38:45 +0300 Subject: [PATCH 213/254] editOverlayGUImrParams.m: corrected path of default Matlab color map folder so it also works on Linux --- mrLoadRet/Edit/editOverlayGUImrParams.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index b0ba5a9c1..db6b48ea3 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -56,8 +56,8 @@ function editOverlayGUImrParams(viewNum) % colormaps % first try to get all the predefined matlab color maps - fid = fopen(fullfile(matlabroot,'\toolbox\matlab\graph3d\contents.m')); - if fid + fid = fopen(fullfile(matlabroot,'toolbox','matlab','graph3d','Contents.m')); + if fid>0 colormaps = textscan(fid,'%% %s %s %*[^\n]'); fclose(fid); if ~isempty(colormaps) From 3d7ef941cd795541bc7493f5c9d1b610044c9d61 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 24 Aug 2022 13:11:15 +0300 Subject: [PATCH 214/254] viewSet.m (case alpha): added alternate case name 'overlayalpha' (as in viewGet.m) --- mrLoadRet/View/viewSet.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 6cd5c4a25..e126735a2 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -1964,8 +1964,9 @@ 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}; From b99d17865b0337b2ddd0a54bf4bc18de83a12359 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 24 Aug 2022 13:16:57 +0300 Subject: [PATCH 215/254] saveAnat.m: export multiple slices for flat patches --- mrLoadRet/File/saveAnat.m | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) 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; From 02096bfe6482feb540fd4b42a43717238beea01a Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Tue, 28 Feb 2023 11:14:39 +0000 Subject: [PATCH 216/254] update pRF somato plugin to include a 2D Gaussian model that also produces parameters for the std of x and y. This should not break anything else. --- mrLoadRet/Plugin/pRF_somato/pRF_somato.m | 129 ++++++++++++++------ mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 87 +++++++++++-- mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m | 2 +- 3 files changed, 175 insertions(+), 43 deletions(-) diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somato.m b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m index b3b4cf039..8b58eb4d5 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somato.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m @@ -98,29 +98,50 @@ prefDigit = r2; prefDigit.name = 'prefDigit'; -prefDigit.range = [1 5]; -prefDigit.clip = [1 5]; +prefDigit.range = [1 4]; +prefDigit.clip = [1 4]; 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); +prefDigit.colormap = digits(4); prefPD = r2; prefPD.name = 'prefPD'; -prefPD.range = [1 5]; -prefPD.clip = [1 5]; +prefPD.range = [1 4]; +prefPD.clip = [1 4]; prefPD.colormapType = 'normal'; %prefPD.colormap = rainbow_colors(4); -prefPD.colormap = digits(5); +prefPD.colormap = digits(256); % create the paramteres for the rfHalfWidth overlay % deal with the sigma. -rfHalfWidth = r2; -rfHalfWidth.name = 'rfHalfWidth'; -rfHalfWidth.range = [0 5]; -rfHalfWidth.clip = [0 5]; -rfHalfWidth.colormapType = 'normal'; -rfHalfWidth.colormap = pink(256); + +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? @@ -166,8 +187,12 @@ r2.data{scanNum} = nan(scanDims); prefDigit.data{scanNum} = nan(scanDims); prefPD.data{scanNum} = nan(scanDims); - rfHalfWidth.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) % % @@ -219,7 +244,14 @@ r = nan(n,fit.concatInfo.n); thisr2 = nan(1,n); - thisRfHalfWidth = 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); @@ -353,7 +385,7 @@ else - parfor ii = blockStart:blockEnd + for 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,... @@ -364,31 +396,38 @@ 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; + % 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; - thisRfHalfWidth(ii) = fit.rfHalfWidth; - + 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)]; - thisResid(:,ii) = fit.residual; + 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 @@ -428,7 +467,12 @@ 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); - rfHalfWidth.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidth(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); @@ -449,7 +493,9 @@ 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; - pRFAnal.d{scanNum}.myresid = thisResid; + if ~strcmpi(algorithm,'nelder-mead') + pRFAnal.d{scanNum}.myresid = thisResid; + end pRFAnal.d{scanNum}.mytSeries = thistSeries; pRFAnal.d{scanNum}.mymodelResp = thismodelResponse; @@ -463,7 +509,15 @@ r2.params{scanNum} = thisParams; prefDigit.params{scanNum} = thisParams; prefPD.params{scanNum} = thisParams; - rfHalfWidth.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))); @@ -478,7 +532,12 @@ pRFAnal.mergeFunction = 'pRFMergeParams'; pRFAnal.guiFunction = 'pRF_somatoGUI'; pRFAnal.params = params; -pRFAnal.overlays = [r2 prefDigit prefPD rfHalfWidth ]; + +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})); diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 219816e60..5fa33a475 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -317,7 +317,7 @@ % compute polar coordinates % [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); switch fit.rfType - case {'gaussian-1D'} + case {'gaussian-1D'} % WITHIN DIGIT MODEL % hang on, this is stupid @@ -395,7 +395,7 @@ 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) ); + 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)); @@ -403,7 +403,7 @@ %fit.rfHalfWidth = std(rfModel(:,index)); % std of digit Gaussian fit.rfHalfWidth = thisStd; - case {'gaussian-1D-transpose'} + case {'gaussian-1D-transpose'} % BETWEEN DIGIT MODEL @@ -452,7 +452,21 @@ 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) + + fit.prefDigit = fit.y; + fit.prefPD = fit.x; % for some reason this is flipped top to bottom. + + fit.rfHalfWidthX = fit.stdx; + fit.rfHalfWidthY = fit.stdy; case {'gaussian-1D-orthotips'} @@ -483,8 +497,15 @@ % 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.rfHalfWidth )); + 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 @@ -553,6 +574,32 @@ 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'}; @@ -1287,6 +1334,26 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) 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); @@ -1714,7 +1781,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % now gernerate the rfModel switch fitParams.rfType - case {'gaussian','gaussian-hdr','gaussian-1D','gaussian-1D-transpose','gaussian-surround', 'gaussian-1D-orthotips'} + 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); @@ -1885,9 +1952,15 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) %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 @@ -2129,7 +2202,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) prefitrfHalfWidth = [1 1 1 1 ]; end - case {'gaussian','gaussian-hdr','gaussian-1D','gaussian-surround','gaussian-1D-transpose'} + 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 diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m index 4ce11ebfe..7f45a27a9 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m @@ -118,7 +118,7 @@ %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-surround','six-param','nine-param', 'gaussian-1D-orthotips', 'five-hdr', 'four-hdr'},... + {'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.'}; From a96c5a7b7292619a6c007c652d59bb2fbc954541 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 20 Feb 2023 19:07:09 +0000 Subject: [PATCH 217/254] viewGet.m (case baseCoordMapPath): if original surfRelax folder path is missing, get the folder with the largest number of character in common with the base name, rather the first found --- mrLoadRet/View/viewGet.m | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index e36a6ae9f..cc282b5b3 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -1505,7 +1505,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 = []; @@ -1522,16 +1522,17 @@ 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) + 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 From f79aefa7950348847e2072a138a1b6670d0ed309 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Mon, 20 Feb 2023 19:08:45 +0000 Subject: [PATCH 218/254] viewGet.m (case baseCoordMap): check if path to base files exists and, if not, replace with a path in the Anatomy folder --- mrLoadRet/View/viewGet.m | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index cc282b5b3..9fcb7f4ac 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -1589,21 +1589,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'} From 466be7e0eaf4fc6f79ad452d6bcf9e9ec73a46fe Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 8 Mar 2023 12:41:41 +0000 Subject: [PATCH 219/254] glmAnalysis_v2: added estimation sampling period parameter to hrfDeconvolution.m., which required modifying other hrf functions (by adding an empty argument). This change is required for cases where the acquisition supersampling is less than the design supersampling (in which case deconvolution is not very useful). This was needed to compare the estimation efficiency of different sequences, some of which require design supersampling. This change has not been tested in mrLoadRet --- .../interrogatorFunctions/roisConfidenceInterval.m | 2 +- .../Plugin/GLM_v2/newGlmAnalysis/getGlmEVParamsGUI.m | 2 +- .../GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m | 4 ++-- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m | 2 +- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m | 2 +- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m | 2 +- .../Plugin/GLM_v2/newGlmAnalysis/hrfDeconvolution.m | 11 ++++++++--- .../Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m | 2 +- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m | 2 +- 9 files changed, 17 insertions(+), 12 deletions(-) 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/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/getGlmTestParamsGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m index 3a3552c28..fa8e8d4ee 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') diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m index b58f57405..246e99ff8 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m @@ -334,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 diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m index e4a620fa9..2a83f5d64 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m @@ -6,7 +6,7 @@ % 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]) help hrfBoxcar diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m index 8f70a4c87..be7228397 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m @@ -8,7 +8,7 @@ % 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) +function [params,hrf] = hrfCustom(params, sampleDuration, sampleDelay, defaultParams, ~) if ~any(nargin == [1 2 3 4])% 5]) help hrfCustom 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 index 9e47182d8..1d68fbb2c 100755 --- 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 diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m index 8b5d11c82..0727393aa 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m @@ -6,7 +6,7 @@ % 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]) help hrfDoubleGamma From 45dc3c9574141bf11259db520b57b9cac23f7086 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Apr 2023 10:58:53 +0100 Subject: [PATCH 220/254] viewGet.m (baseCoordMapPath): use one-time warningif surface path is incorrect --- mrLoadRet/View/viewGet.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index 9fcb7f4ac..9c08bd9e9 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -1521,7 +1521,8 @@ 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)); + 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 From 2e3a68fe125c4fcb1d4dd7130eeb9d87245b11ae Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Wed, 3 May 2023 12:40:58 +0100 Subject: [PATCH 221/254] pRF_somatoFit - Fixed a bug where the prefDig for 1x5 stimulation grids doesn't get set correctly. Also fixed it for 2D Gaussian models, where the x and y is flipped for setting pref dig and pref PD. Minor fix enter the commit message for your changes. Lines starting --- mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 127 ++++++++++++-------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 5fa33a475..6ca9fad38 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -318,12 +318,12 @@ % [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; @@ -341,16 +341,22 @@ % end %keyboard %[ma] 2019 - [~,index] = max(max(rfModel)); % max digit amp of Gaussian + + 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 @@ -358,23 +364,32 @@ % % just for outputs. % % You'd have to change the stimfile to have 100 x points. thisX = linspace(1,size(rfModel,2),100); - if fit.prefDigit == 1 + + if size(fitParams.stimX,2) == 5 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; + 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; @@ -388,7 +403,7 @@ % Pone = [fit.amp4 fit.meanFour fit.stdFour 0]; % thisStd = fit.stdFour; % end - + thisZ = gauss(Pone,thisX)'; %thisZ = gauss(Pone,thisX); [~,index2] = max(thisZ); @@ -397,16 +412,16 @@ %%%%%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 - - - + + + [~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian fit.prefPD = index; thisX = linspace(1,size(rfModel,2),100); @@ -425,20 +440,20 @@ elseif fit.prefPD == 5 Pone = [fit.amp5 fit.meanFive fit.stdFive 0]; thisStd = fit.stdFive; - - + + 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 fit.prefPD = fit.prefPD; @@ -446,35 +461,41 @@ 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) - - fit.prefDigit = fit.y; - fit.prefPD = fit.x; % for some reason this is flipped top to bottom. - + % 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; @@ -490,7 +511,7 @@ 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 From b03f186b94c944f79e788c9cb1780643f18a9344 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 7 Jun 2023 16:58:18 +0100 Subject: [PATCH 222/254] hrfBocar.m, hrfCustom.m,, hrfFslFlobs.m: fixed bugs introduced in 466be7e0 --- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m | 2 +- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m | 2 +- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m index 2a83f5d64..97a8520ba 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m @@ -8,7 +8,7 @@ % 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 index be7228397..a2964a805 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m @@ -10,7 +10,7 @@ % function [params,hrf] = hrfCustom(params, sampleDuration, sampleDelay, defaultParams, ~) -if ~any(nargin == [1 2 3 4])% 5]) +if ~any(nargin == [1 2 3 4 5]) help hrfCustom return end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m index 0727393aa..3b06246d7 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m @@ -8,7 +8,7 @@ % 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 From 73390177e14f2a4dbcfb365b698ffca49f9e3489 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 16 Jun 2023 12:53:49 +0100 Subject: [PATCH 223/254] combineTransformOverlays.m: combine scans even if they're in different spaces, as long as baseSpace is true, since data will be transformed to the same space --- mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index b32ff0e6d..6b997e9ab 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -210,16 +210,17 @@ base2scan{iScan} = viewGet(thisView,'base2scan',iScan); if iScan > params.scanList(1) if ~isequal(scanDims{iScan},scanDims{params.scanList(1)}) - mrWarnDlg(sprintf('(combineTransformOverlays) Scan %d and %d have different dimensions.',params.scanList(1),iScan)); + 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) Scan %d and %d have different sforms.',params.scanList(1),iScan)); + mrWarnDlg(sprintf('(combineTransformOverlays) Scans %d and %d have different sforms.',params.scanList(1),iScan)); dimensionsDiffer = true; end end end - if dimensionsDiffer + 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 From 061efe061dcbc6da6bb8507f10e4cd311a8eadd6 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Jul 2023 11:33:20 +0100 Subject: [PATCH 224/254] viewSet.m (case roicolor): can now set color of multiple ROIs at once --- mrLoadRet/View/viewSet.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index e126735a2..e8d72da82 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -2170,7 +2170,9 @@ roiNum = curRoi; end if ~isempty(roiNum) - view.ROIs(roiNum).color = val; + for iRoi = roiNum + view.ROIs(iRoi).color = val; + end end case {'roinotes'} From 514b556d061cedecf667956d7a273889f22944ad Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 12 Jul 2023 11:36:13 +0100 Subject: [PATCH 225/254] mrPrint.m: fixed bugs in mosaic mode when number of columns is not specified or color scales are not displayed --- mrLoadRet/GUI/mrPrint.m | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index beeb7bd18..5a311df85 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -198,7 +198,9 @@ params.imageTitle = {params.imageTitle}; end if params.mosaic - if length(params.imageTitle)>1 && length(params.imageTitle)~=nOverlays + 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; @@ -444,9 +446,9 @@ imageRows = 3:3:nImageRows*3; colorbarRows = 2:3:nImageRows*3-1; case 'none' - imageCols = 2; + imageCols = 2:2:nImageCols*2; colorbarCols = []; - imageRows = 2; + imageRows = 2:2:nImageRows*2; colorbarRows = []; end for iImage = 1:nImages @@ -614,6 +616,8 @@ case {'east','west'} colorbarPosition = getSubplotPosition(colorbarCols(curImageCol),imageRows(curImageRow),colWidths,rowWidths,0,0); colorbarLoc = params.colorbarLoc; + otherwise + colorbarPosition = []; end if ~isempty(cmap{iImage}) && ~isempty(colorbarPosition) hColorbar = axes('parent',f,'Position',colorbarPosition); From ec6d55931ba14a651538bb9a3e388dc5e1bc8f95 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 13 Jul 2023 17:25:16 +0100 Subject: [PATCH 226/254] refreshMLRDisplay.m, mrPrint.m: gyrus/sulcus boundary is now drawn as a line rather than pixels on the base image --- mrLoadRet/GUI/mrPrint.m | 19 +++++++++++ mrLoadRet/GUI/refreshMLRDisplay.m | 53 ++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 5a311df85..50aa9545b 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -803,6 +803,25 @@ 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 + % --------------- Draw the ROIs if baseType ~= 2 if iImage==1 diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index f08df40b7..588818871 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -280,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) ; @@ -290,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]); @@ -384,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) @@ -550,6 +560,41 @@ axis(hAxis,'off'); 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'); if nROIs From 3b77aed63bbf6a3c69c0a41068aeb013462719e7 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 3 Aug 2023 12:35:11 +0100 Subject: [PATCH 227/254] mrLoadRetVersion.m: added matlab version 9.13 (2022b) --- mrLoadRet/mrLoadRetVersion.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index 46df07d72..bee3e8124 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 8.4 8.5 9.0 9.1 9.2 9.4 9.5 9.7 9.8]; +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]; % expected toolbox if verLessThan('matlab','8.5') From 44066df6e1c9f9c4d3c6f7793b6301ac1c5fe782 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Wed, 9 Aug 2023 14:55:29 +0100 Subject: [PATCH 228/254] add if loop to importOverlay for scripting, minor + small plugin edits --- mrLoadRet/File/importOverlay.m | 6 ++- mrLoadRet/Plugin/pRF_somato/pRF_somato.m | 12 +++--- mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 43 ++++++++++++++------- 3 files changed, 40 insertions(+), 21 deletions(-) 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/Plugin/pRF_somato/pRF_somato.m b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m index 8b58eb4d5..76dfc7bc6 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somato.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m @@ -98,16 +98,16 @@ prefDigit = r2; prefDigit.name = 'prefDigit'; -prefDigit.range = [1 4]; -prefDigit.clip = [1 4]; +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(4); +prefDigit.colormap = digits(256); prefPD = r2; prefPD.name = 'prefPD'; -prefPD.range = [1 4]; -prefPD.clip = [1 4]; +prefPD.range = [1 5]; +prefPD.clip = [1 5]; prefPD.colormapType = 'normal'; %prefPD.colormap = rainbow_colors(4); prefPD.colormap = digits(256); @@ -385,7 +385,7 @@ else - for ii = blockStart:blockEnd + 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,... diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 6ca9fad38..5463e012a 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -421,26 +421,41 @@ case {'gaussian-1D-transpose'} % BETWEEN DIGIT MODEL + if size(fitParams.stimX,2) == 5 % 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 + %[~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian fit.prefPD = index; + thisX = linspace(1,size(rfModel,2),100); - if fit.prefPD == 1 + + if size(fitParams.stimX,2) == 5 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; + 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)'; From 22eeffc7ade51216b153168baf77706fc6a1b2ab Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 28 Sep 2023 18:20:37 +0100 Subject: [PATCH 229/254] editOverlayGUImrParams.m: finds default color maps for Matlab v9.14 and above --- mrLoadRet/Edit/editOverlayGUImrParams.m | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index db6b48ea3..04697a55d 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -56,21 +56,30 @@ function editOverlayGUImrParams(viewNum) % colormaps % 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 - colormaps = textscan(fid,'%% %s %s %*[^\n]'); + contents = textscan(fid,'%% %s %s %*[^\n]'); fclose(fid); - if ~isempty(colormaps) - colormapStart = find(ismember(colormaps{1},'Color') & ismember(colormaps{2},'maps.'))+1; - colormapEnd = find(ismember(colormaps{1},'') & ismember(colormaps{2},'')); - colormapEnd = colormapEnd(find(colormapEnd>colormapStart,1,'first'))-1; - colormaps = colormaps{1}(colormapStart:colormapEnd)'; + 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 - else - colormaps = {}; 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'); From af7dcd2175be3ce439bb71fb8c9e1d57eac16877 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 29 Sep 2023 18:05:02 +0100 Subject: [PATCH 230/254] mrLoadRetVersion.m: added Matlab versions 9.14 and 9.15 (2023a and b) --- mrLoadRet/mrLoadRetVersion.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index bee3e8124..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 8.4 8.5 9.0 9.1 9.2 9.4 9.5 9.7 9.8 9.13]; +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') From 3ae029b055b8f8d6333bcab9a01c8af1f75d3968 Mon Sep 17 00:00:00 2001 From: Michael Asghar Date: Tue, 17 Oct 2023 12:02:29 +0100 Subject: [PATCH 231/254] add pRF fit for 1d Gaussian for 4 digits --- mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m index 5463e012a..569192372 100755 --- a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -183,7 +183,7 @@ % compute all the model response, using parfor loop % parfor i = 1:fitParams.prefit.n - 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); @@ -421,7 +421,7 @@ case {'gaussian-1D-transpose'} % BETWEEN DIGIT MODEL - if size(fitParams.stimX,2) == 5 % for TW Touchmap + 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 @@ -433,7 +433,7 @@ thisX = linspace(1,size(rfModel,2),100); - if size(fitParams.stimX,2) == 5 + if size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; thisStd = fit.stdOne; else @@ -470,7 +470,7 @@ % 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 + 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) ); @@ -795,7 +795,7 @@ fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; end - elseif size(fitParams.stimX,2) == 5 % pRF on patients TW + 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',... @@ -1525,7 +1525,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) p.canonical.offset2 = 0; end - elseif size(fitParams.stimX,2) == 5 + elseif size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 % p.meanOne = params(1); % p.meanTwo = params(2); % p.meanThr = params(3); @@ -1815,7 +1815,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) rfModel = []; -% now gernerate the 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); @@ -1901,7 +1901,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % direction! - elseif size(fitParams.stimX,2) == 5 + 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]; @@ -1917,7 +1917,7 @@ function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) % else % rfModel = Z; % end - X = linspace(1,5,5); + 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]; From 4ef99562c37def7ba96d8d7f1771961f257d4d6a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 7 Nov 2023 12:07:45 +0000 Subject: [PATCH 232/254] Changeset allowing non-linear normalization of individual brains to MNI space (using SPM12), and then importing the normalization information into mrLoadRet for conversion of coordinates to MNI space --- mrLoadRet/File/loadSession.m | 9 +- mrLoadRet/File/saveSession.m | 3 +- mrLoadRet/GUI/mlrGetMouseCoords.m | 19 ++- mrLoadRet/GUI/mrInterrogator.m | 21 ++- .../printClickedCoordinates.m | 32 +++++ mrLoadRet/View/viewGet.m | 28 +++- mrLoadRet/View/viewSet.m | 3 + mrLoadRet/mrGlobals.m | 3 +- .../File/SPM/mlrImportSPMnormalization.m | 85 ++++++++++++ .../File/SPM/mlrSegmentNormalizeSPM12.m | 131 ++++++++++++++++++ 10 files changed, 325 insertions(+), 9 deletions(-) create mode 100644 mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/printClickedCoordinates.m create mode 100644 mrUtilities/File/SPM/mlrImportSPMnormalization.m create mode 100644 mrUtilities/File/SPM/mlrSegmentNormalizeSPM12.m 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/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/GUI/mlrGetMouseCoords.m b/mrLoadRet/GUI/mlrGetMouseCoords.m index 1c9ec5f3e..08226a132 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 use 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/mrInterrogator.m b/mrLoadRet/GUI/mrInterrogator.m index 1be7659ae..7045793e2 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 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/View/viewGet.m b/mrLoadRet/View/viewGet.m index 9c08bd9e9..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'} @@ -767,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 = []; @@ -2322,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 e8d72da82..70a1a12ee 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 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/mrUtilities/File/SPM/mlrImportSPMnormalization.m b/mrUtilities/File/SPM/mlrImportSPMnormalization.m new file mode 100644 index 000000000..588c3a82e --- /dev/null +++ b/mrUtilities/File/SPM/mlrImportSPMnormalization.m @@ -0,0 +1,85 @@ +% 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 + +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 \ No newline at end of file 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) From 704c79606b9b5aa54c31a91c1e6a7c7eff583808 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 8 Nov 2023 13:49:16 +0000 Subject: [PATCH 233/254] mlrImportSPMnormalization.m: go back to current directory at the end --- mrUtilities/File/SPM/mlrImportSPMnormalization.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mrUtilities/File/SPM/mlrImportSPMnormalization.m b/mrUtilities/File/SPM/mlrImportSPMnormalization.m index 588c3a82e..d347ff9cb 100644 --- a/mrUtilities/File/SPM/mlrImportSPMnormalization.m +++ b/mrUtilities/File/SPM/mlrImportSPMnormalization.m @@ -31,6 +31,8 @@ function mlrImportSPMnormalization(studyDir,subjectNames,overwrite) subjectNames = {subjectNames}; end +cwd = pwd; + for subject = subjectNames fprintf(sprintf('(mlrImportSPMnormalization) Importing MNI normalization info for %s...\n',subject{1})); @@ -82,4 +84,6 @@ function mlrImportSPMnormalization(studyDir,subjectNames,overwrite) 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 \ No newline at end of file +end + +cd(cwd); From 42b27931ebe6dced0134516a2c6b9353594ef875 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 8 Nov 2023 13:51:52 +0000 Subject: [PATCH 234/254] added makeSphereROIatCoords.m + modified makeEmptyROI to be able to have ROI coords in current base space --- mrLoadRet/ROI/getROICoordinates.m | 2 +- mrLoadRet/ROI/makeEmptyROI.m | 33 ++++++-- mrLoadRet/ROI/makeSphereROIatCoords.m | 112 ++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 mrLoadRet/ROI/makeSphereROIatCoords.m 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/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/makeSphereROIatCoords.m b/mrLoadRet/ROI/makeSphereROIatCoords.m new file mode 100644 index 000000000..a5b74ca3a --- /dev/null +++ b/mrLoadRet/ROI/makeSphereROIatCoords.m @@ -0,0 +1,112 @@ +% +% 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; +roi.coords = unique(round(base2mag \ allMagCoords(:,whichCoords))','rows')'; + +if isempty(roi.coords) + mrWarnDlg('(makeSphereROIatCoords) The sphere has no coordinates in the current base volume. No ROI was added to the view.'); + return; +end + +% Add it to the view +thisView= viewSet(thisView,'newROI',roi); + From d509979ce897f415c98bd81b38a26fb246c41790 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 12 Nov 2023 17:56:18 +0000 Subject: [PATCH 235/254] modifyROI.m: prints how many voxels were added/subtracted from the ROI (and the corresponding volume) --- mrLoadRet/ROI/modifyROI.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/ROI/modifyROI.m b/mrLoadRet/ROI/modifyROI.m index e343d671a..4610624e3 100644 --- a/mrLoadRet/ROI/modifyROI.m +++ b/mrLoadRet/ROI/modifyROI.m @@ -43,8 +43,10 @@ coords=newCoords; elseif sgn==0 coords = removeCoords(newCoords,curCoords); + fprintf('(modifROI) Removed %d voxels (%.1f mm^3) from %s\n', 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('(modifROI) Added %d voxels (%.1f mm^3) to %s\n', size(coords,2) - size(curCoords,2), (size(coords,2) - size(curCoords,2)) * prod(roiVoxelSize), viewGet(view,'roiName')); end view = viewSet(view,'ROIcoords',coords); @@ -64,7 +66,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 From d26339d7ccef7a7a3f0dd1d84b883576faaa3249 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 15 Nov 2023 16:53:51 +0000 Subject: [PATCH 236/254] restrictROI.m: now calls modifyROI to remove voxels from ROI, so that the Undo function can be used (only for the currently selected ROI) --- mrLoadRet/ROI/restrictROI.m | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) 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 From 1ced2ca9ed47a9dae39da37ef7dbb43998c76997 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 15 Nov 2023 16:55:29 +0000 Subject: [PATCH 237/254] modifyROI.m: slightly modified the printed output to add the number of voxels and volume of the modified ROI --- mrLoadRet/ROI/modifyROI.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/ROI/modifyROI.m b/mrLoadRet/ROI/modifyROI.m index 4610624e3..648819067 100644 --- a/mrLoadRet/ROI/modifyROI.m +++ b/mrLoadRet/ROI/modifyROI.m @@ -41,13 +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\n', size(curCoords,2) - size(coords,2), (size(curCoords,2) - size(coords,2)) * prod(roiVoxelSize), viewGet(view,'roiName')); + 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('(modifROI) Added %d voxels (%.1f mm^3) to %s\n', size(coords,2) - size(curCoords,2), (size(coords,2) - size(curCoords,2)) * prod(roiVoxelSize), viewGet(view,'roiName')); + 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; From a340fc8177a9266a063cc8fef089f29034de2d3a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 15 Nov 2023 16:57:09 +0000 Subject: [PATCH 238/254] drawROI.m: for single voxels, fixed the option to right-click to end the single voxels selection --- mrLoadRet/ROI/drawROI.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index 0bd2b4973..722b0d7f7 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -77,7 +77,7 @@ switch descriptor case 'single voxels' - disp('Use mouse left button to add/remove a voxel. End selection with Alt, Command key or right-click') + 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); @@ -87,8 +87,7 @@ 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 From 55e0a52c128eaad0ce2cec2162aa5a0cf761945a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Thu, 16 Nov 2023 16:42:57 +0000 Subject: [PATCH 239/254] added makeROIsFromSurfaces.m and inpolyhedron.m --- mrLoadRet/ROI/makeROIsFromSurfaces.m | 164 +++++++ mrUtilities/surfUtils/inpolyhedron.m | 461 ++++++++++++++++++ .../surfUtils/inpolyhedron_license.txt | 24 + 3 files changed, 649 insertions(+) create mode 100644 mrLoadRet/ROI/makeROIsFromSurfaces.m create mode 100644 mrUtilities/surfUtils/inpolyhedron.m create mode 100644 mrUtilities/surfUtils/inpolyhedron_license.txt 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/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) Date: Sat, 18 Nov 2023 16:56:53 +0000 Subject: [PATCH 240/254] getGlmStatistics.m, getGlmTestParamsGUI.m: ignore case of tTestSide parameter --- mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m | 8 ++++---- .../Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m index 4ac347a62..d4516cf43 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m @@ -711,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); @@ -1062,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 diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m index fa8e8d4ee..216a11e6d 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m @@ -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 From 903ff5a8b64b6b1a3621a325e8f884f97e5fa252 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 19 Nov 2023 19:20:49 +0000 Subject: [PATCH 241/254] calcSurfaceNormals.m: now accepts standard Matlab mesh structures --- mrUtilities/surfUtils/calcSurfaceNormals.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mrUtilities/surfUtils/calcSurfaceNormals.m b/mrUtilities/surfUtils/calcSurfaceNormals.m index a47ae804d..58ee43ac4 100644 --- a/mrUtilities/surfUtils/calcSurfaceNormals.m +++ b/mrUtilities/surfUtils/calcSurfaceNormals.m @@ -15,6 +15,16 @@ 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 From 194a9653b969b29944a51be0dc932ecfdf9388ed Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 21 Nov 2023 16:59:23 +0000 Subject: [PATCH 242/254] makeSphereROIatCoords.m: find single voxel to avoid empty ROI when radius is small compared to base voxel size --- mrLoadRet/ROI/makeSphereROIatCoords.m | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/mrLoadRet/ROI/makeSphereROIatCoords.m b/mrLoadRet/ROI/makeSphereROIatCoords.m index a5b74ca3a..6060049de 100644 --- a/mrLoadRet/ROI/makeSphereROIatCoords.m +++ b/mrLoadRet/ROI/makeSphereROIatCoords.m @@ -100,12 +100,21 @@ [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; -roi.coords = unique(round(base2mag \ allMagCoords(:,whichCoords))','rows')'; - -if isempty(roi.coords) - mrWarnDlg('(makeSphereROIatCoords) The sphere has no coordinates in the current base volume. No ROI was added to the view.'); - return; +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); From d8978a9a28fc65b1410a16bf59664e59b0a7f3f3 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 6 Dec 2023 09:37:56 +0000 Subject: [PATCH 243/254] xformROIcoords.m: use unique with option 'rows' (instead of converting coords to indices) to avoid choking on ROi coordinates outside the volume) --- mrLoadRet/ROI/xformROIcoords.m | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 From 01541906ae6632e2e5a6c123b2cac01448866fe8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 19 Dec 2023 12:51:48 +0000 Subject: [PATCH 244/254] mlrExportROI.m: now returns ROI indices in export volume space --- mrLoadRet/File/mlrExportROI.m | 54 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 127a3571e..06dd853a6 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -4,16 +4,19 @@ % usage: mlrExportROI(v,saveFilename,<'baseNum',baseNum>,<'scanNum',scanNum>,<'groupNum',groupNum>,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>) % by: justin gardner % date: 07/14/09 -% purpose: Export ROI(s) to a nifti image or Freesurfer label file. Uses +% 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 @@ -28,12 +31,16 @@ function mlrExportROI(v,saveFilename,varargin) return end -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 +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 ~isempty(scanNum) && ~isempty(groupNum) @@ -127,7 +134,12 @@ function mlrExportROI(v,saveFilename,varargin) for iRoi = 1:length(roiNum) roiName = viewGet(v,'roiName',roiNum(iRoi)); % tell the user what is going on - fprintf('(mlrExportROI) Exporting ROI %s to %s with dimensions set to match %s: [%i %i %i]\n',roiName, saveFilename{iRoi},exportVolumeString,volDims(1),volDims(2),volDims(3)); + 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)); % get roi coordinates in base/scan coordinates if exportToScanSpace @@ -152,30 +164,30 @@ function mlrExportROI(v,saveFilename,varargin) if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats and surfaces, use basecoordmap to transform ROI from canonical base to multi-depth flat map - roiBaseCoordsLinear = mrSub2ind(baseCoordMap.dims',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); - roiBaseCoordsLinear = ismember(baseCoordsLinear,roiBaseCoordsLinear); + roiBaseCoordsLinear{iRoi} = mrSub2ind(baseCoordMap.dims',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + roiBaseCoordsLinear{iRoi} = ismember(baseCoordsLinear,roiBaseCoordsLinear{iRoi}); else % convert to linear coordinates - roiBaseCoordsLinear = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + roiBaseCoordsLinear{iRoi} = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); end % check roiBaseCoords - if isempty(roiBaseCoords) || ~nnz(roiBaseCoordsLinear) + if isempty(roiBaseCoords) || ~nnz(roiBaseCoordsLinear{iRoi}) mrWarnDlg(sprintf('(mlrExportROI) This ROI (%s) does not have any coordinates in the %s',roiName,exportVolumeString)); - else + elseif ~isempty(saveFilename) if exportToFreesurferLabel % in order to export to label format, select vertices that are within the ROI % reshape to vertices * depths - roiBaseCoordsLinear = reshape(roiBaseCoordsLinear, [size(baseCoordMap.coords,1)*size(baseCoordMap.coords,2) size(baseCoordMap.coords,5)]); + 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,2); - roiBaseCoordsLinear = find(any(roiBaseCoordsLinear(:,ceil((nDepths-1)/4)+1:floor(3*(nDepths-1)/4)+1),2)); + 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,:)+baseCoordMap.outerVtcs(roiBaseCoordsLinear,:))/2; + 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); @@ -194,10 +206,10 @@ function mlrExportROI(v,saveFilename,varargin) 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-1, vertexCoords, ones(size(vertexCoords,1),1)]) + mlrWriteFreesurferLabel(saveFilename{iRoi},labelHeader,[roiBaseCoordsLinear{iRoi}-1, vertexCoords, ones(size(vertexCoords,1),1)]) else % set all the roi coordinates to 1 - d(roiBaseCoordsLinear) = 1; + d(roiBaseCoordsLinear{iRoi}) = 1; % if the orientation has been changed in loadAnat, undo that here. if ~passedInHeader && ~isempty(b.originalOrient) From f87593c2c31757fcea0eacae41c0f39d3574ad1e Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 5 Apr 2024 03:51:42 +0100 Subject: [PATCH 245/254] importSurfaceOFF.m: ensures inflated inner surfaces don't intersect --- mrLoadRet/File/importSurfaceOFF.m | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index 941c3b0f8..8012efb6e 100644 --- a/mrLoadRet/File/importSurfaceOFF.m +++ b/mrLoadRet/File/importSurfaceOFF.m @@ -146,13 +146,34 @@ 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... % load the curvature From b872c743a481d21de49ad8e770fd7f92cb1879b1 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 5 Apr 2024 03:53:24 +0100 Subject: [PATCH 246/254] mlrGuiSet.m: ignore case of showROIs string --- mrLoadRet/GUI/mlrGuiSet.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrLoadRet/GUI/mlrGuiSet.m b/mrLoadRet/GUI/mlrGuiSet.m index 065db6234..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 From d2705ccdc560b9fa6c8df5de152cdca65987dc5a Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 5 Apr 2024 05:22:27 +0100 Subject: [PATCH 247/254] importSurfaceOFF.m: can now script importing both hemispheres --- mrLoadRet/File/importSurfaceOFF.m | 51 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index 8012efb6e..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) @@ -22,6 +25,10 @@ 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') @@ -42,10 +49,6 @@ 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') @@ -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.outerCoords(1:leftFlag-1); - postfix = params1.outerCoords(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.outerCoords(1:rightFlag-1); - postfix = params1.outerCoords(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); From 23b250dc7b1de75a80feb7b2a15c1d5e1047d618 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 5 Apr 2024 05:28:29 +0100 Subject: [PATCH 248/254] refreshMLRDisplay.m: ignore colorBlending preference string case --- mrLoadRet/GUI/refreshMLRDisplay.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index 588818871..63c2067b9 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -340,8 +340,8 @@ % Combine base and overlays 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; @@ -349,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; @@ -363,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 From 11423b7872ec5b3aad7d760146a4aed30ea79aa2 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 5 Apr 2024 17:47:08 +0100 Subject: [PATCH 249/254] refreshMLRDisplay.m, mrPrint.m: now draws ROI perimeters on surfaces --- mrLoadRet/GUI/mrPrint.m | 14 +++++--- mrLoadRet/GUI/refreshMLRDisplay.m | 56 +++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 50aa9545b..093f9324f 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -119,10 +119,12 @@ 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 baseType == 2 % ROI options for surfaces - paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; - else % ROI options for flatmaps and images + 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'}; @@ -505,7 +507,11 @@ setMLRViewAngle(v,hImage); % 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',hImage); + 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 diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index 63c2067b9..6409f5c53 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -803,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'); @@ -898,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 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, mlrDispPercent(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, mlrDispPercent(-inf); end; - disp(sprintf('%i/%i edges',length(newy),length(y))); + 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); @@ -930,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 @@ -1026,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 = []; From 0b6e58473f6ff80ce5f3a12919d6281b281c0c80 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sun, 7 Apr 2024 15:16:26 +0100 Subject: [PATCH 250/254] mrPrint.m: now supports colorbars for multiple overlays without mosaic --- mrLoadRet/GUI/mrPrint.m | 201 ++++++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 88 deletions(-) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index 093f9324f..ef8f0e256 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -179,11 +179,6 @@ end % ------------------- Checks on parameters - -if nOverlays>1 && ~params.mosaic - mrWarnDlg('(mrPrint) Printing colorbar for multiple overlays is not implemented'); -end - if isempty(params.colorbarTitle) || ischar(params.colorbarTitle) params.colorbarTitle = {params.colorbarTitle}; end @@ -249,20 +244,22 @@ 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, ~, altBase{iImage}] = refreshMLRDisplay(viewGet(v,'viewNum')); - % get the gui, so that we can extract colorbar + [img{iImage}, base, roi, overlays, altBase{iImage}] = refreshMLRDisplay(viewGet(v,'viewNum')); fig = viewGet(v,'figNum'); - if nOverlays>1 && ~params.mosaic - cmap{iImage} = []; - elseif ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it + % 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); - % 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')); + 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 @@ -666,83 +663,111 @@ colorbarPosition(2) = colorbarPosition(2) + colorbarPosition(4)*(1-.8)/2; colorbarPosition(4) = colorbarPosition(4) * colorbarLength; end + H = colorbar(hColorbar,colorbarLoc,'Position',colorbarPosition); - % set the colormap - colormap(hColorbar,cmap{iImage}); set(H,'axisLocation','in'); %make sure ticks and labels are pointing away from the image (i.e. towards the inside of the colorbar axes) - - % apply scaling to ticks and tick labels - if ~fieldIsNotDefined(params,'colorbarTickNumber') - xTicks{iImage} = linspace(xTicks{iImage}(1),xTicks{iImage}(end),params.colorbarTickNumber); - xTickLabels{iImage} = linspace(xTickLabels{iImage}(1),xTickLabels{iImage}(end),params.colorbarTickNumber)'; - end - if ~fieldIsNotDefined(params,'colorbarScale') - colorScale = viewGet(v,'overlayColorRange'); - xTickLabels{iImage} =(xTickLabels{iImage}-colorScale(1))/diff(colorScale)*diff(params.colorbarScale)+params.colorbarScale(1); - end - if ~fieldIsNotDefined(params,'colorbarScaleFunction') - colorbarScaleFunction = str2func(params.colorbarScaleFunction); - xTickLabels{iImage} = colorbarScaleFunction(xTickLabels{iImage}); - end - xTickLabels{iImage} = num2str( xTickLabels{iImage}, '%.3f'); - - % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) - totalColNum = size(xTickLabels{iImage},2); - for iTick = 1:size(xTickLabels{iImage},1) - colNum = totalColNum; - while xTickLabels{iImage}(iTick,colNum)=='0' - colNum=colNum-1; + % (this also places the labels away from the image) + if size(cmap{iImage},3) == 1 + % set the colormap + colormap(hColorbar,cmap{iImage}); + + % apply scaling to ticks and tick labels + if ~fieldIsNotDefined(params,'colorbarTickNumber') + xTicks{iImage} = linspace(xTicks{iImage}(1),xTicks{iImage}(end),params.colorbarTickNumber); + xTickLabels{iImage} = linspace(xTickLabels{iImage}(1),xTickLabels{iImage}(end),params.colorbarTickNumber)'; end - if xTickLabels{iImage}(iTick,colNum)=='.' - colNum=colNum-1; + if ~fieldIsNotDefined(params,'colorbarScale') + colorScale = viewGet(v,'overlayColorRange'); + xTickLabels{iImage} =(xTickLabels{iImage}-colorScale(1))/diff(colorScale)*diff(params.colorbarScale)+params.colorbarScale(1); end - xTickLabels{iImage}(iTick,:) = circshift(xTickLabels{iImage}(iTick,:),-colNum); - xTickLabels{iImage}(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); - end - - % remove spaces and align characters depending on the location of the colorbar - minLeftSpacesEnd = totalColNum; - maxRightSpacesStart = 1; - for iTick = 1:size(xTickLabels{iImage},1) - startCol = find(~isspace(xTickLabels{iImage}(iTick,:)),1,'first'); - switch(lower(colorbarLoc)) - case 'west' - leftSpacesEnd = startCol-1; - rightSpacesStart = totalColNum+1; - case {'north','south'} - leftSpacesEnd = round((startCol-1)/2); - rightSpacesStart = totalColNum-(startCol-1-leftSpacesEnd)+1; - xTickLabels{iImage}(iTick,leftSpacesEnd+1:rightSpacesStart-1) = xTickLabels{iImage}(iTick,startCol:end); - xTickLabels{iImage}(iTick,1:leftSpacesEnd) = ' '; - xTickLabels{iImage}(iTick,rightSpacesStart:end) = ' '; - case 'east' - xTickLabels{iImage}(iTick,1:totalColNum-startCol+1) = xTickLabels{iImage}(iTick,startCol:end); - xTickLabels{iImage}(iTick,totalColNum-startCol+2:end) = ' '; - leftSpacesEnd = 0; - rightSpacesStart = totalColNum-startCol+2; + if ~fieldIsNotDefined(params,'colorbarScaleFunction') + colorbarScaleFunction = str2func(params.colorbarScaleFunction); + xTickLabels{iImage} = colorbarScaleFunction(xTickLabels{iImage}); end - minLeftSpacesEnd = min(minLeftSpacesEnd,leftSpacesEnd); - maxRightSpacesStart = max(maxRightSpacesStart,rightSpacesStart); - end - % remove unnecessary spaces (altough this it not in fact necessary, as they seem to be ignored) - xTickLabels{iImage}(:,maxRightSpacesStart:end) = []; - xTickLabels{iImage}(:,1:minLeftSpacesEnd) = []; - - % set the colorbar ticks, making sure to switch - % them if we have a vertical as opposed to horizontal bar - if ismember(lower(colorbarLoc),{'east','west'}) - set(H,'XTick',yTicks{iImage}); - set(H,'Ytick',xTicks{iImage}); - set(H,'YTickLabel',xTickLabels{iImage}); + xTickLabels{iImage} = num2str( xTickLabels{iImage}, '%.3f'); + + % remove trailing zeros (can't use %g or %f to directly get both a fixed number of decimal points and no trailing zeros) + totalColNum = size(xTickLabels{iImage},2); + for iTick = 1:size(xTickLabels{iImage},1) + colNum = totalColNum; + while xTickLabels{iImage}(iTick,colNum)=='0' + colNum=colNum-1; + end + if xTickLabels{iImage}(iTick,colNum)=='.' + colNum=colNum-1; + end + xTickLabels{iImage}(iTick,:) = circshift(xTickLabels{iImage}(iTick,:),-colNum); + xTickLabels{iImage}(iTick,1:totalColNum-colNum) = repmat(' ',1,totalColNum-colNum); + end + + % remove spaces and align characters depending on the location of the colorbar + minLeftSpacesEnd = totalColNum; + maxRightSpacesStart = 1; + for iTick = 1:size(xTickLabels{iImage},1) + startCol = find(~isspace(xTickLabels{iImage}(iTick,:)),1,'first'); + switch(lower(colorbarLoc)) + case 'west' + leftSpacesEnd = startCol-1; + rightSpacesStart = totalColNum+1; + case {'north','south'} + leftSpacesEnd = round((startCol-1)/2); + rightSpacesStart = totalColNum-(startCol-1-leftSpacesEnd)+1; + xTickLabels{iImage}(iTick,leftSpacesEnd+1:rightSpacesStart-1) = xTickLabels{iImage}(iTick,startCol:end); + xTickLabels{iImage}(iTick,1:leftSpacesEnd) = ' '; + xTickLabels{iImage}(iTick,rightSpacesStart:end) = ' '; + case 'east' + xTickLabels{iImage}(iTick,1:totalColNum-startCol+1) = xTickLabels{iImage}(iTick,startCol:end); + xTickLabels{iImage}(iTick,totalColNum-startCol+2:end) = ' '; + leftSpacesEnd = 0; + rightSpacesStart = totalColNum-startCol+2; + end + minLeftSpacesEnd = min(minLeftSpacesEnd,leftSpacesEnd); + maxRightSpacesStart = max(maxRightSpacesStart,rightSpacesStart); + end + % remove unnecessary spaces (altough this it not in fact necessary, as they seem to be ignored) + xTickLabels{iImage}(:,maxRightSpacesStart:end) = []; + xTickLabels{iImage}(:,1:minLeftSpacesEnd) = []; + + % set the colorbar ticks, making sure to switch + % them if we have a vertical as opposed to horizontal bar + if ismember(lower(colorbarLoc),{'east','west'}) + set(H,'XTick',yTicks{iImage}); + set(H,'Ytick',xTicks{iImage}); + set(H,'YTickLabel',xTickLabels{iImage}); + else + set(H,'YTick',yTicks{iImage}); + set(H,'Xtick',xTicks{iImage}); + set(H,'XTickLabel',xTickLabels{iImage}); + end + set(H,'XColor',foregroundColor); + set(H,'YColor',foregroundColor); + if nImages>1 && params.mosaic + set(H,'tickDirection','out'); % for mosaic display, colorbars are smaller, so orient the ticks outwards + end + else - set(H,'YTick',yTicks{iImage}); - set(H,'Xtick',xTicks{iImage}); - set(H,'XTickLabel',xTickLabels{iImage}); - end - set(H,'XColor',foregroundColor); - set(H,'YColor',foregroundColor); - if nImages>1 && params.mosaic - set(H,'tickDirection','out'); % for mosaic display, colorbars are smaller, so orient the ticks outwards + % 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) From 2d2277dc3fcbb416bf1edd0de437891abef98940 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Sat, 20 Apr 2024 14:41:30 +0100 Subject: [PATCH 251/254] makeROIsExactlyContiguous.m: now works for more than 2 ROIs even if voxels are shared by more than 2 ROIs --- .../makeROIsExactlyContiguous.m | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m index e0384769a..ad2b4ac05 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m @@ -1,19 +1,15 @@ % makeROIsExactlyContiguous.m % -% usage: transformedRois = makeROIsExactlyContiguous(rois,margin,) +% usage: transformedRois = makeROIsExactlyContiguous(rois) % by: julien besle % date: 11/01/2011 % -% purpose: attribute voxels shared by two ROIs to the closest of these two ROIs, with -% the two resulting two ROIs then becoming exactly contiguous. -% This function will run with more than two ROIs and will apply to all pairs of ROIs -% This assumes that no voxel is shared by more than two ROIs. If this is the case, -% then results will depend on the order in which the ROIs are passed - +% purpose: make two or more ROIs mutually exclusive +% function rois = makeROIsExactlyContiguous(rois) if ~ismember(nargin,[1]) - help expandROI; + help makeROIsExactlyContiguous; return end @@ -31,34 +27,36 @@ end for iRoi = 1:length(rois) - for jRoi = iRoi+1:length(rois) - % 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; + 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 -% % 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 duplciates removed) - rois(iRoi).coords = [coords1; commonCoordinates(belongsToROI1,:)]'; - rois(jRoi).coords = [coords2; commonCoordinates(~belongsToROI1,:)]'; end end end From cef8784f863c33314355704eb546b447423381d8 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Fri, 10 May 2024 12:01:39 +0100 Subject: [PATCH 252/254] mrInterrogator.m: for restarts, try to keep the currently-set interrogator --- mrLoadRet/GUI/mlrGetMouseCoords.m | 2 +- mrLoadRet/GUI/mrInterrogator.m | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mrLoadRet/GUI/mlrGetMouseCoords.m b/mrLoadRet/GUI/mlrGetMouseCoords.m index 08226a132..39f49a09e 100644 --- a/mrLoadRet/GUI/mlrGetMouseCoords.m +++ b/mrLoadRet/GUI/mlrGetMouseCoords.m @@ -150,7 +150,7 @@ % 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 use to compute the deformation coords map + % 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 diff --git a/mrLoadRet/GUI/mrInterrogator.m b/mrLoadRet/GUI/mrInterrogator.m index 7045793e2..7bc23a623 100644 --- a/mrLoadRet/GUI/mrInterrogator.m +++ b/mrLoadRet/GUI/mrInterrogator.m @@ -449,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 isempty(MLR.interrogator{viewNum}.interrogator) + MLR.interrogator{viewNum}.interrogator = viewGet(view,'interrogator',overlayNum,analysisNum); +end set(MLR.interrogator{viewNum}.hInterrogator,'String',MLR.interrogator{viewNum}.interrogator); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% From d7c8849c26ebedfd3210d45414cf3cd1824e1228 Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Tue, 14 May 2024 08:33:13 +0100 Subject: [PATCH 253/254] mrPrint.m: added option to add light to surfaces --- mrLoadRet/GUI/mrPrint.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m index ef8f0e256..f31217e06 100755 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -93,6 +93,9 @@ 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'}; @@ -495,6 +498,7 @@ 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 @@ -513,6 +517,13 @@ end end end + + if params.camLight % add a light + material dull + lighting phong + camlight(params.camLightAz,params.camLightEl); + end + else %crop image From 85c1e4cfa08d176f557a380c2a86b93752dec21c Mon Sep 17 00:00:00 2001 From: Julien Besle Date: Wed, 15 May 2024 10:43:45 +0100 Subject: [PATCH 254/254] viewSet.m: when setting ROi properties, first check that ROI number(s) exist in the view --- mrLoadRet/View/viewSet.m | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 70a1a12ee..bdeb47560 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -2110,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); @@ -2138,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); @@ -2155,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); @@ -2172,6 +2175,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) for iRoi = roiNum view.ROIs(iRoi).color = val; @@ -2186,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 @@ -2201,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 @@ -2216,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 @@ -2229,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 @@ -2242,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 @@ -2254,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 @@ -2267,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 @@ -2280,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 @@ -2293,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 @@ -2306,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 @@ -2319,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 @@ -2332,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 @@ -2344,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