OSCR

Brain-wide mapping of layer-specific functional connectivity in the human cortex at 3T using draining-vein-suppressed fMRI.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § Materials and methods › Data preprocessing ↔ welton_toolbox_github/NIFTI_NORDIC_wt.m, lines 1–53 · score 0.74 · NIFTI_NORDIC, smoothing filter, fMRI, MATLAB, magnitude, width
  2. [2] § Materials and methods › Common protocol in MR acquisitions ↔ welton_toolbox_github/gen_mb3d_encodemtx_i2k.m, lines 1–61 · score 0.65 · acceleration rate, phase encoding, imaging protocols, dimensional, SMS, slicing
  3. [3] § Materials and methods › Common protocol in MR acquisitions ↔ welton_toolbox_github/mb3d_grappa_wt3.m, lines 1–28 · score 0.65 · acceleration rate, phase encoding, imaging protocols, dimensional, SMS, slicing
  4. [4] § Materials and methods › Data preprocessing ↔ pproc2_s2025010601_run1_dn2.m, lines 108–176 · score 0.60 · motion corrected, preprocessing, band, MELODIC, ICA, smoothing

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 1,148 lines · 36 KB · MIT · 1 match

  1. function NIFTI_NORDIC_wt(fn_magn_in,fn_phase_in,fn_out,ARG)
  2. % fMRI
  3. % fn_magn_in='name.nii.gz';
  4. % fn_phase_in='name2.nii.gz';
  5. % fn_out=['NORDIC_' fn_magn_in(1:end-7)];
  6. % ARG.temporal_phase=1;
  7. % ARG.phase_filter_width=10;
  8. % NIFTI_NORDIC(fn_magn_in,fn_phase_in,fn_out,ARG)
  9. %
  10. % dMRI
  11. % fn_magn_in='name.nii.gz';
  12. % fn_phase_in='name2.nii.gz';
  13. % fn_out=['NORDIC_' fn_magn_in(1:end-7)];
  14. % ARG.temporal_phase=3;
  15. % ARG.phase_filter_width=3;
  16. % NIFTI_NORDIC(fn_magn_in,fn_phase_in,fn_out,ARG)
  17. %
  18. %
  19. % file_input assumes 4D data
  20. %
  21. %OPTIONS
  22. % ARG.DIROUT VAL= string Default is empty
  23. % ARG.noise_volume_last VAL = num specifiec volume from the end of the series
  24. % 0 default
  25. %
  26. % ARG.factor_error val = num >1 use higher noisefloor <1 use lower noisefloor
  27. % 1 default
  28. %
  29. % ARG.full_dynamic_range val = [ 0 1] 0 keep the input scale, output maximizes range.
  30. % Default 0
  31. % ARG.temporal_phase val = [1 2 3] 1 was default, 3 now in dMRI due tophase errors in some data
  32. % ARG.NORDIC val = [0 1] 1 Default
  33. % ARG.MP val = [0 1 2] 1 NORDIC gfactor with MP estimation.
  34. % 2 MP without gfactor correction
  35. % 0 default
  36. % ARG.kernel_size_gfactor val = [val1 val2 val], defautl is [14 14 1]
  37. % ARG.kernel_size_PCA val = [val1 val2 val], default is val1=val2=val3;
  38. % ratio of 11:1 between spatial and temproal voxels
  39. % ARG.magnitude_only val =[] or 1. Using complex or magntiude only. Default is []
  40. % Function still needs two inputs but will ignore the second
  41. %
  42. % ARG.save_add_info val =[0 1]; If it is 1, then an additonal matlab file is being saved with degress removed etc.
  43. % default is 0
  44. % ARG.make_complex_nii if the field exist, then the phase is being saved in a similar format as the input phase
  45. %
  46. % ARG.phase_slice_average_for_kspace_centering val = [0 1]
  47. % if val =0, not used, if val=1 the series average pr slice is first removed
  48. % default is now 0
  49. % ARG.phase_filter_width val = [1... 10] Specifiec the width of the smoothing filter for the phase
  50. % default is now 3
  51. %
  52. % ARG.save_gfactor_map val = [1 2]. 1, saves the RELATIVE gfactor, 2 saves the
  53. % gfactor and does not complete the NORDIC processing
  54. % TODO
  55. % Scaling relative to the width of the MP spectrum, if one wants to be
  56. % conservative
  57. %
  58. % 4/15/21 swapped the uint16 and in16 for the phase
  59. %
  60. % VERSION 4/22/2021
  61. if ~exist('ARG') % initialize ARG structure
  62. ARG.DIROUT=[pwd '/'];
  63. elseif ~isfield(ARG,'DIROUT') % Specify where to save data
  64. ARG.DIROUT=[pwd '/'];
  65. else
  66. ARG.DIROUT=[ARG.DIROUT '/'];
  67. end
  68. if ~isfield(ARG,'noise_volume_last')
  69. ARG.noise_volume_last=0; % there is no noise volume {0 1 2 ...}
  70. end
  71. if ~isfield(ARG,'factor_error')
  72. ARG.factor_error=1.0; % error in gfactor estimatetion. >1 use higher noisefloor <1 use lower noisefloor
  73. end
  74. if ~isfield(ARG,'full_dynamic_range')
  75. ARG.full_dynamic_range=0; % Format o
  76. end
  77. if ~isfield(ARG,'temporal_phase')
  78. ARG.temporal_phase=1; % Correction for slice and time-specific phase
  79. end
  80. if ~isfield(ARG,'NORDIC') & ~isfield(ARG,'MP')
  81. ARG.NORDIC=1; % threshold based on Noise
  82. ARG.MP=0; % threshold based on Marchencko-Pastur
  83. elseif ~isfield(ARG,'NORDIC') % MP selected
  84. if ARG.MP==1
  85. ARG.NORDIC=0;
  86. else
  87. ARG.NORDIC=1;
  88. end
  89. elseif ~isfield(ARG,'MP') % NORDIC selected
  90. if ARG.NORDIC==1
  91. ARG.MP=0;
  92. else
  93. ARG.MP=1;
  94. end
  95. end
  96. if ~isfield(ARG,'phase_filter_width')
  97. ARG.phase_filter_width=3; % default is [14 14 90]
  98. end
  99. if ~isfield(ARG,'NORDIC_patch_overlap')
  100. ARG.NORDIC_patch_overlap=2; % default is [14 14 90]
  101. end
  102. if ~isfield(ARG,'gfactor_patch_overlap')
  103. ARG.gfactor_patch_overlap=2; % default is [14 14 90]
  104. end
  105. if ~isfield(ARG,'kernel_size_gfactor')
  106. ARG.kernel_size_gfactor=[]; % default is [14 14 90]
  107. end
  108. if ~isfield(ARG,'kernel_size_PCA')
  109. ARG.kernel_size_PCA=[]; % default is 11:1 ratio
  110. end
  111. if ~isfield(ARG,'phase_slice_average_for_kspace_centering');
  112. ARG.phase_slice_average_for_kspace_centering=0;
  113. end
  114. if ~isfield(ARG,'magnitude_only') % if legacy data
  115. ARG.magnitude_only=0; %
  116. end
  117. if isfield(ARG,'save_add_info'); end % additional information is saved in matlab file
  118. if isfield(ARG,'make_complex_nii'); end % two output NII files are saved
  119. if ~isfield(ARG,'save_gfactor_map') % save out a map of a relative gfactor
  120. ARG.save_gfactor_map=[]; %
  121. end
  122. if isfield(ARG,'use_generic_NII_read') % save out a map of a relative gfactor
  123. if ARG.use_generic_NII_read==1
  124. path(path,'/home/range6-raid1/moeller/matlab/ADD/NIFTI/');
  125. end
  126. else
  127. ARG.use_generic_NII_read=0;
  128. end
  129. if ~isfield(ARG,'data_has_zero_elements') %
  130. ARG.data_has_zero_elements=0; % % If there are pixels that are constant zero
  131. end
  132. ARG;
  133. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  134. if ARG.magnitude_only~=1
  135. try
  136. info_phase=niftiinfo(fn_phase_in);
  137. info=niftiinfo(fn_magn_in);
  138. catch; disp('The niftiinfo fails at reading the header') ;end
  139. if ARG.use_generic_NII_read~=1
  140. tmpvol=load_nii(fn_magn_in);
  141. %I_M=abs(single(niftiread(fn_magn_in)));
  142. I_M=abs(single(tmpvol.img));
  143. tmpvol=load_nii(fn_phase_in);
  144. %I_P=single(niftiread(fn_phase_in));
  145. I_P=single(tmpvol.img*pi/180);
  146. else
  147. try
  148. tmp=load_nii(fn_magn_in);
  149. I_M=abs(single(tmp.img));
  150. tmp=load_nii(fn_phase_in);
  151. I_P=single(tmp.img);
  152. catch
  153. disp('Missing nfiti tool. Serach mathworks for load_nii fileexchange 8797')
  154. end
  155. end
  156. phase_range=single(max(I_P(:)));
  157. phase_range_min=single(min(I_P(:)));
  158. if ~exist('info_phase')
  159. info_phase.Datatype=class(I_P);
  160. info.Datatype=class(I_M);
  161. end
  162. % Here, we combine magnitude and phase data into complex form
  163. fprintf('Phase should be -pi to pi...\n')
  164. % convert to single and then scale the phase
  165. I_P = single(I_P);
  166. range_norm=phase_range-phase_range_min;
  167. range_center=(phase_range+phase_range_min)/range_norm*1/2;
  168. I_P = (single(I_P)./range_norm -range_center)*2*pi;
  169. II=single(I_M) .* exp(1i*I_P);
  170. if 0
  171. if strmatch(info_phase.Datatype,'uint16')
  172. I_P = single(I_P)/phase_range*2*pi;
  173. II=single(I_M) .* exp(1i*I_P);
  174. elseif strmatch(info_phase.Datatype,'int16')
  175. I_P = (single(I_P)+1-(phase_range+1)/2)/(phase_range+1)*2*pi;
  176. II=single(I_M) .* exp(1i*I_P);
  177. elseif strmatch(info_phase.Datatype,'single')
  178. phase_range_min=min(I_P(:));
  179. range_norm=phase_range-phase_range_min;
  180. range_center=(phase_range+phase_range_min)/range_norm*1/2;
  181. I_P = (single(I_P)./range_norm -range_center)*2*pi;
  182. II=single(I_M) .* exp(1i*I_P);
  183. end
  184. end
  185. fprintf('Phase data range is %.2f to %.2f\n', min(I_P(:)), max(I_P(:)))
  186. else
  187. try
  188. info=niftiinfo(fn_magn_in);
  189. catch; disp('The niftiinfo fails at reading the header') ;end
  190. if ARG.use_generic_NII_read~=1
  191. tmpvol=load_nii(fn_magn_in);
  192. %I_M=abs(single(niftiread(fn_magn_in)));
  193. I_M=abs(single(tmpvol.img));
  194. else
  195. tmp=load_nii(fn_magn_in);
  196. I_M=abs(single(tmp.img));
  197. end
  198. if ~exist('info_phase')
  199. info.Datatype=class(I_M);
  200. end
  201. end
  202. if ~isempty(ARG.magnitude_only)
  203. if ARG.magnitude_only==1
  204. II=single(I_M);
  205. ARG.temporal_phase=0;
  206. end
  207. end
  208. TEMPVOL=abs(II(:,:,:,1));
  209. ARG.ABSOLUTE_SCALE=min(TEMPVOL(TEMPVOL~=0));
  210. II=II./ARG.ABSOLUTE_SCALE;
  211. % load test_DATA
  212. % II=II(:,:,:,1:95);
  213. if size(II,4)<6
  214. disp('Too few volumes')
  215. % return
  216. end
  217. KSP2=II;
  218. matdim=size(KSP2);
  219. tt=mean(reshape(abs(KSP2),[],size(KSP2,4)));
  220. [idx]=find(tt>0.95*max(tt));
  221. meanphase=mean(KSP2(:,:,:,idx(1)),4);
  222. if 1
  223. disp('estimating slice-dependent phases ...')
  224. meanphase=mean(KSP2(:,:,:,[1:end-ARG.noise_volume_last]),4);
  225. for nsl=1:size(meanphase,3);
  226. %meanphase2(:,:,nsl)=complex( medfilt2(squeeze(real(meanphase(:,:,nsl))),[7 7]), medfilt2(squeeze(imag(meanphase(:,:,nsl))),[7 7]) );
  227. end
  228. meanphase=meanphase*ARG.phase_slice_average_for_kspace_centering;
  229. end
  230. for slice=matdim(3):-1:1
  231. for n=1:size(KSP2,4); % include the noise
  232. KKSP2(:,:,slice,n)=KSP2(:,:,slice,n).*exp(-i*angle(meanphase(:,:,slice)));
  233. end
  234. end
  235. DD_phase=0*KSP2;
  236. if ARG.temporal_phase>0; % Standarad low-pass filtered map
  237. for slice=matdim(3):-1:1
  238. for n=1:size(KSP2,4);
  239. tmp=KSP2(:,:,slice,n);
  240. for ndim=[1:2]; tmp=ifftshift(ifft(ifftshift( tmp ,ndim),[],ndim),ndim+0); end
  241. [nx, ny, nc, nb] = size(tmp(:,:,:,:,1,1));
  242. tmp = bsxfun(@times,tmp,reshape(tukeywin(ny,1).^ARG.phase_filter_width,[1 ny]));
  243. tmp = bsxfun(@times,tmp,reshape(tukeywin(nx,1).^ARG.phase_filter_width,[nx 1]));
  244. for ndim=[1:2]; tmp=fftshift(fft(fftshift( tmp ,ndim),[],ndim),ndim+0); end
  245. DD_phase(:,:,slice,n)=tmp;
  246. end
  247. end
  248. end
  249. if ARG.temporal_phase==2; % Secondary step for filtered phase with residual spikes
  250. for slice=matdim(3):-1:1
  251. for n=1:size(KSP2,4);
  252. phase_diff=angle(KSP2(:,:,slice,n)./DD_phase(:,:,slice,n));
  253. mask=abs(phase_diff)>1;
  254. DD_phase2=DD_phase(:,:,slice,n);
  255. tmp=(KSP2(:,:,slice,n));
  256. DD_phase2(mask)= tmp(mask);
  257. DD_phase(:,:,slice,n)=DD_phase2;
  258. end
  259. end
  260. end
  261. for slice=matdim(3):-1:1
  262. for n=1:size(KSP2,4);
  263. KSP2(:,:,slice,n)= KSP2(:,:,slice,n).*exp(-i*angle( DD_phase(:,:,slice,n) ));
  264. end
  265. end
  266. disp('Completed estimating slice-dependent phases ...')
  267. if isfield(ARG,'use_magn_for_gfactor')
  268. if isempty(ARG.kernel_size_gfactor) | size(ARG.kernel_size_gfactor,2)<3
  269. KSP2=abs(KSP2(:,:,1:end,1:min(90,end),1)); % should be at least 30 volumes
  270. else
  271. KSP2=abs(KSP2(:,:,1:end,1:min(ARG.kernel_size_gfactor(3),end),1));
  272. end
  273. else
  274. if (isempty(ARG.kernel_size_gfactor) | size(ARG.kernel_size_gfactor,2)<3)
  275. KSP2=(KSP2(:,:,1:end,1:min(90,end),1)); % should be at least 30 volumes
  276. else
  277. % KSP2=(KSP2(:,:,1:end,1:min(ARG.kernel_size_gfactor(3),end),1));
  278. KSP2=(KSP2(:,:,1:end,1:min(ARG.kernel_size_gfactor(4),end),1));
  279. end
  280. end
  281. KSP2(isnan(KSP2))=0;
  282. KSP2(isinf(KSP2))=0;
  283. master_fast=1;
  284. KSP_recon=0*KSP2;
  285. if isempty(ARG.kernel_size_gfactor)
  286. ARG.kernel_size=[14 14 1];
  287. else
  288. ARG.kernel_size=[ARG.kernel_size_gfactor(1) ARG.kernel_size_gfactor(2) 1];
  289. ARG.kernel_size=[ARG.kernel_size_gfactor(1) ARG.kernel_size_gfactor(2) ARG.kernel_size_gfactor(3)];
  290. end
  291. QQ.KSP_processed=zeros(1,size(KSP2,1)-ARG.kernel_size(1));
  292. ARG.patch_average=0;
  293. ARG.patch_average_sub= ARG.gfactor_patch_overlap;
  294. ARG.LLR_scale=0;
  295. ARG.NVR_threshold=1;
  296. ARG.soft_thrs=10; % MPPCa (When Noise varies)
  297. %ARG.soft_thrs=[]; % NORDIC (When noise is flat)
  298. KSP_weight=KSP2(:,:,:,1)*0;
  299. NOISE=KSP_weight;
  300. Component_threshold=KSP_weight;
  301. energy_removed=KSP_weight;
  302. SNR_weight=KSP_weight;
  303. QQ.KSP_processed=zeros(1,size(KSP2,1)-ARG.kernel_size(1));
  304. if ARG.patch_average==0
  305. KSP_processed=QQ.KSP_processed*0;
  306. for nw1=2:max(1,floor(ARG.kernel_size(1)/ARG.patch_average_sub));
  307. KSP_processed(1,nw1 : max(1,floor(ARG.kernel_size(1)/ARG.patch_average_sub)):end)=2;
  308. end
  309. KSP_processed(end)=0; % disp
  310. QQ.KSP_processed=KSP_processed;
  311. end
  312. KSP_processed;
  313. disp('estimating g-factor ...')
  314. QQ.ARG=ARG;
  315. for n1=1:size(QQ.KSP_processed,2)
  316. % fprintf( [num2str(n1) ' '])
  317. [KSP_recon,~,KSP_weight,NOISE,Component_threshold,energy_removed,SNR_weight]=sub_LLR_Processing(KSP_recon,KSP2,ARG,n1,QQ,master_fast,KSP_weight,NOISE,Component_threshold,energy_removed,SNR_weight) ; % save all files
  318. % fprintf( [num2str(n1) ' '])
  319. % if mod(n1,20)==0; disp(' ');end
  320. end
  321. KSP_recon=KSP_recon./repmat((KSP_weight),[1 1 1 size(KSP2,4)]);
  322. ARG.NOISE= sqrt(NOISE./KSP_weight);
  323. ARG.Component_threshold = Component_threshold./KSP_weight;
  324. ARG.energy_removed = energy_removed./KSP_weight;
  325. ARG.SNR_weight = SNR_weight./KSP_weight;
  326. IMG2=KSP_recon;
  327. ARG2=ARG;
  328. disp('completed estimating g-factor')
  329. gfactor=ARG.NOISE;
  330. if size(KSP2,4)<6; % gfactor stimation most likely failed, replace with median estimated
  331. gfactor(isnan(gfactor))=0;
  332. gfactor(gfactor==0)=median(gfactor(gfactor~=0));
  333. end
  334. if sum(gfactor(:)==0)>0; % gfactor stimation most likely failed since it is zero
  335. gfactor(isnan(gfactor))=0;
  336. gfactor(gfactor<1)=median(gfactor(gfactor~=0));
  337. ARG.data_has_zero_elements=1;
  338. end
  339. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  340. if ARG.MP==2;
  341. gfactor=ones(size(gfactor));
  342. end
  343. % gfactor=ones(size(gfactor));
  344. if ( ARG.save_gfactor_map==2 ) | ( ARG.save_gfactor_map==1 )
  345. g_IMG=abs(gfactor(:,:,:,1:end)); % remove g-factor and noise for DUAL 1
  346. g_IMG(isnan(g_IMG))=0;
  347. tmp=sort(abs(g_IMG(:))); sn_scale=2*tmp(round(0.99*end));%sn_scale=max();
  348. gain_level=floor(log2(32000/sn_scale));
  349. if ARG.full_dynamic_range==0; gain_level=0;end
  350. if strmatch(info.Datatype,'uint16')
  351. g_IMG= uint16(abs(g_IMG)*2^gain_level);
  352. elseif strmatch(info.Datatype,'int16')
  353. g_IMG= int16(abs(g_IMG)*2^gain_level);
  354. else
  355. g_IMG= single(abs(g_IMG)*2^gain_level);
  356. end
  357. %niftiwrite((g_IMG),[ARG.DIROUT 'gfactor_' fn_out(1:end) '.nii'])
  358. nii=make_nii(g_IMG);
  359. save_nii(nii,[ARG.DIROUT 'gfactor_' fn_out(1:end) '.nii']);
  360. if ARG.save_gfactor_map==2
  361. return
  362. end
  363. end
  364. KSP2=II;
  365. matdim=size(KSP2);
  366. for slice=matdim(3):-1:1
  367. for n=1:size(KSP2,4); % include the noise
  368. KSP2(:,:,slice,n)=KSP2(:,:,slice,n).*exp(-i*angle(meanphase(:,:,slice)));
  369. end
  370. end
  371. for n=1:size(KSP2,4);
  372. KSP2(:,:,:,n)= KSP2(:,:,:,n)./ gfactor;
  373. end
  374. if ARG.noise_volume_last>0
  375. KSP2_NOISE =KSP2(:,:,:,end+1-ARG.noise_volume_last);
  376. end
  377. if ARG.temporal_phase==3; % Secondary step for filtered phase with residual spikes
  378. for slice=matdim(3):-1:1
  379. for n=1:size(KSP2,4);
  380. phase_diff=angle(KSP2(:,:,slice,n)./DD_phase(:,:,slice,n));
  381. mask = abs(phase_diff)>1;
  382. mask2 = abs(KSP2(:,:,slice,n))>sqrt(2);
  383. DD_phase2=DD_phase(:,:,slice,n);
  384. tmp=(KSP2(:,:,slice,n));
  385. DD_phase2(mask.*mask2==1)= tmp(mask.*mask2==1);
  386. DD_phase(:,:,slice,n)=DD_phase2;
  387. end
  388. end
  389. end
  390. for slice=matdim(3):-1:1
  391. for n=1:size(KSP2,4);
  392. KSP2(:,:,slice,n)= KSP2(:,:,slice,n).*exp(-i*angle( DD_phase(:,:,slice,n) ));
  393. end
  394. end
  395. KSP2(isnan(KSP2))=0;
  396. KSP2(isinf(KSP2))=0;
  397. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  398. if ARG.noise_volume_last>0
  399. %tmp_noise=KSP2(:,:,:,end+1-ARG.noise_volume_last);
  400. tmp_noise=KSP2_NOISE;
  401. tmp_noise(isnan(tmp_noise))=0;
  402. tmp_noise(isinf(tmp_noise))=0;
  403. ARG.measured_noise=std(tmp_noise(tmp_noise~=0)); % sqrt(2) for real and complex
  404. else
  405. ARG.measured_noise=1; % IF COMPLEX DATA
  406. end
  407. if ~isfield(ARG,'use_magn_for_gfactor') & (isempty(ARG.magnitude_only) | ARG.magnitude_only==0) %% WOULD THIS BE THE ISSUE & replaced by |
  408. ARG.measured_noise = ARG.measured_noise/sqrt(2)
  409. end
  410. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  411. if ARG.data_has_zero_elements==1
  412. MASK=(sum(abs(KSP2),4)==0);
  413. Num_zero_elements=sum(MASK(:));
  414. for nvol=1:size(KSP2,4)
  415. tmp=KSP2(:,:,:,nvol);
  416. tmp(MASK)=(randn(Num_zero_elements,1)+1i*randn(Num_zero_elements,1))/sqrt(2);
  417. KSP2(:,:,:,nvol)=tmp;
  418. end
  419. end
  420. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  421. for readout=1:size(KSP2,1)
  422. TT(readout)= std(reshape(KSP2(readout,:,:,:),[],1));
  423. TT1(readout)= mean(reshape(KSP2(readout,:,:,:),[],1));
  424. end
  425. % ARG.measured_noise=median(TT([2:8 end-8:end-1]))
  426. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  427. master_fast=1;
  428. KSP_recon=0*KSP2;
  429. ARG.kernel_size=repmat([ round((size(KSP2,4)*11)^(1/3)) ],1,3);
  430. if isempty(ARG.kernel_size_PCA)
  431. ARG.kernel_size=repmat([ round((size(KSP2,4)*11)^(1/3)) ],1,3);
  432. else
  433. ARG.kernel_size = ARG.kernel_size_PCA ;
  434. end
  435. if matdim(3) <= ARG.kernel_size(3) % Number of slices is less than cubic kernel
  436. ARG.kernel_size = repmat([ round((size(KSP2,4)*11/matdim(3) )^(1/2)) ],1,2);
  437. ARG.kernel_size(3)= matdim(3);
  438. end
  439. QQ.KSP_processed=zeros(1,size(KSP2,1)-ARG.kernel_size(1));
  440. ARG.patch_average=0;
  441. ARG.patch_average_sub= ARG.NORDIC_patch_overlap ;
  442. % ARG.kernel_size=[7 7 7]; ARG.patch_average_sub=7; MPPCA
  443. % ARG.soft_thrs=10; % MPPCa (When Noise varies)
  444. ARG.LLR_scale=1;
  445. ARG.NVR_threshold=0;
  446. ARG.soft_thrs=[]; % NORDIC (When noise is flat)
  447. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  448. ARG.NVR_threshold=0;
  449. for ntmp=1:10
  450. [~,S,~]=svd(randn(prod(ARG.kernel_size),size(KSP2,4)));
  451. ARG.NVR_threshold=ARG.NVR_threshold+S(1,1);
  452. end
  453. if ARG.magnitude_only~=1 % 4/29/2021
  454. ARG.NVR_threshold= ARG.NVR_threshold/10*sqrt(2)* ARG.measured_noise*ARG.factor_error; % sqrt(2) due to complex 1.20 due to understimate of g-factor
  455. else
  456. ARG.NVR_threshold= ARG.NVR_threshold/10*ARG.measured_noise*ARG.factor_error; % sqrt(2) due to complex 1.20 due to understimate of g-factor
  457. end
  458. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  459. if ARG.MP>0
  460. ARG.soft_thrs=10;
  461. end
  462. if isfield(ARG,'soft_thrs_in')
  463. ARG.soft_thrs=ARG.soft_thrs_in;
  464. end
  465. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  466. KSP_weight=KSP2(:,:,:,1)*0;
  467. NOISE=KSP_weight;
  468. Component_threshold=KSP_weight;
  469. energy_removed=KSP_weight;
  470. SNR_weight=KSP_weight;
  471. QQ.KSP_processed=zeros(1,size(KSP2,1)-ARG.kernel_size(1));
  472. if ARG.patch_average==0
  473. KSP_processed=QQ.KSP_processed*0;
  474. for nw1=2:max(1,floor(ARG.kernel_size(1)/ARG.patch_average_sub));
  475. KSP_processed(1,nw1 : max(1,floor(ARG.kernel_size(1)/ARG.patch_average_sub)):end)=2;
  476. end
  477. KSP_processed(end)=0; % disp
  478. QQ.KSP_processed=KSP_processed;
  479. end
  480. disp('starting NORDIC ...')
  481. KSP_processed;
  482. QQ.ARG=ARG;
  483. for n1=1:size(QQ.KSP_processed,2)
  484. [KSP_recon,~,KSP_weight,NOISE,Component_threshold,energy_removed,SNR_weight]=sub_LLR_Processing(KSP_recon,KSP2,ARG,n1,QQ,master_fast,KSP_weight,NOISE,Component_threshold,energy_removed,SNR_weight) ; % save all files
  485. end
  486. KSP_recon=KSP_recon./repmat((KSP_weight),[1 1 1 size(KSP2,4)]); % Assumes that the combination is with N instead of sqrt(N). Works for NVR not MPPCA
  487. ARG.NOISE= sqrt(NOISE./KSP_weight);
  488. ARG.Component_threshold = Component_threshold./KSP_weight;
  489. ARG.energy_removed = energy_removed./KSP_weight;
  490. ARG.SNR_weight = SNR_weight./KSP_weight;
  491. IMG2=KSP_recon;
  492. disp('completing NORDIC ...')
  493. if isfield(ARG,'save_residual_matlab')
  494. if ARG.save_residual_matlab==1;
  495. Residual=KSP2-KSP_recon;
  496. save([ARG.DIROUT 'RESIDUAL' fn_out '.mat' ],'Residual','-v7.3')
  497. end
  498. end
  499. for n=1:size(IMG2,4);
  500. IMG2(:,:,:,n)= IMG2(:,:,:,n).* gfactor;
  501. end
  502. for slice=matdim(3):-1:1
  503. for n=1:size(IMG2,4); % include the noise
  504. IMG2(:,:,slice,n)=IMG2(:,:,slice,n).*exp(i*angle(meanphase(:,:,slice)));
  505. end
  506. end
  507. for slice=matdim(3):-1:1
  508. for n=1:size(IMG2,4);
  509. IMG2(:,:,slice,n)= IMG2(:,:,slice,n).*exp(i*angle( DD_phase(:,:,slice,n) ));
  510. end
  511. end
  512. IMG2=IMG2.*ARG.ABSOLUTE_SCALE;
  513. IMG2(isnan(IMG2))=0;
  514. if isfield(ARG,'make_complex_nii')
  515. IMG2_tmp=abs(IMG2(:,:,:,1:end)); % remove g-factor and noise for DUAL 1
  516. IMG2_tmp(isnan(IMG2_tmp))=0;
  517. tmp=sort(abs(IMG2_tmp(:))); sn_scale=2*tmp(round(0.99*end));%sn_scale=max();
  518. gain_level=floor(log2(32000/sn_scale));
  519. %IMG2_tmp= int16(abs(IMG2_tmp)*2^gain_level);
  520. if ARG.full_dynamic_range==0; gain_level=0;end
  521. if strmatch(info.Datatype,'uint16')
  522. IMG2_tmp= uint16(abs(IMG2_tmp)*2^gain_level);
  523. elseif strmatch(info.Datatype,'int16')
  524. IMG2_tmp= int16(abs(IMG2_tmp)*2^gain_level);
  525. else
  526. IMG2_tmp= single(abs(IMG2_tmp)*2^gain_level);
  527. end
  528. %niftiwrite((IMG2_tmp),[ARG.DIROUT fn_out 'magn.nii'],info)
  529. nii=make_nii(IMG2_tmp);
  530. save_nii(nii,[ARG.DIROUT fn_out 'magn.nii']);
  531. IMG2_tmp=angle(IMG2(:,:,:,1:end));
  532. if strmatch(info_phase.Datatype,'int16')
  533. % IMG2_tmp=IMG2_tmp+pi;
  534. end
  535. IMG2_tmp= (IMG2_tmp/(2*pi)+range_center)*range_norm;
  536. if strmatch(info_phase.Datatype,'uint16')
  537. IMG2_tmp= uint16(IMG2_tmp);
  538. elseif strmatch(info_phase.Datatype,'int16')
  539. IMG2_tmp= int16(IMG2_tmp);
  540. else
  541. IMG2_tmp= single((IMG2_tmp));
  542. end
  543. if 0
  544. if strmatch(info_phase.Datatype,'uint16')
  545. IMG2_tmp=IMG2_tmp/(2*pi)*phase_range;
  546. IMG2_tmp= uint16(abs(IMG2_tmp)*2^gain_level);
  547. elseif strmatch(info_phase.Datatype,'int16')
  548. IMG2_tmp=IMG2_tmp/(2*pi)*phase_range;
  549. IMG2_tmp= int16((IMG2_tmp)*2^gain_level);
  550. else
  551. IMG2_tmp= single(abs(IMG2_tmp)*2^gain_level);
  552. end
  553. end
  554. %niftiwrite((IMG2_tmp),[ARG.DIROUT fn_out 'phase.nii'],info_phase)
  555. nii=make_nii(IMG2_tmp);
  556. save_nii(nii,[ARG.DIROUT fn_out 'phase.nii']);
  557. else
  558. IMG2=abs(IMG2(:,:,:,1:end)); % remove g-factor and noise for DUAL 1
  559. IMG2(isnan(IMG2))=0;
  560. tmp=sort(abs(IMG2(:))); sn_scale=2*tmp(round(0.99*end));%sn_scale=max();
  561. gain_level=floor(log2(32000/sn_scale));
  562. if ARG.full_dynamic_range==0; gain_level=0;end
  563. if strmatch(info.Datatype,'uint16')
  564. IMG2= uint16(abs(IMG2)*2^gain_level);
  565. elseif strmatch(info.Datatype,'int16')
  566. IMG2= int16(abs(IMG2)*2^gain_level);
  567. else
  568. IMG2= single(abs(IMG2)*2^gain_level);
  569. end
  570. if ARG.use_generic_NII_read==0;
  571. %niftiwrite((IMG2),[ARG.DIROUT fn_out(1:end) '.nii'],info)
  572. nii=make_nii(IMG2);
  573. save_nii(nii, [ARG.DIROUT fn_out(1:end) '.nii']);
  574. else
  575. nii=make_nii(IMG2);
  576. save_nii(nii, [ARG.DIROUT fn_out(1:end) '.nii']);
  577. end
  578. end
  579. if isfield(ARG,'save_add_info')
  580. if ARG.save_add_info==1
  581. disp('saving additional info')
  582. save([ARG.DIROUT fn_out '.mat' ],'ARG2','ARG','-v7.3')
  583. end
  584. end
  585. return
  586. function [KSP_recon,KSP2,KSP2_weight,NOISE, Component_threshold,energy_removed,SNR_weight]=sub_LLR_Processing(KSP_recon,KSP2,ARG,n1,QQ,master,KSP2_weight,NOISE,Component_threshold,energy_removed,SNR_weight)
  587. if ~exist('NOISE'); NOISE=[]; end
  588. if ~exist('Component_threshold');Component_threshold=[]; end
  589. if ~exist('energy_removed'); energy_removed=[]; end
  590. if ~exist('SNR_weight'); SNR_weight=[]; end
  591. % QQ.KSP_processed 0 nothing done, 1 running, 2 saved 3 completed and averaged
  592. if master==0 && ARG.patch_average==0
  593. OPTION='NO_master_NO_PA';
  594. elseif master==0 && ARG.patch_average==1
  595. OPTION='NO_master_PA' ;
  596. elseif master==1 && ARG.patch_average==0
  597. OPTION='master_NO_PA' ;
  598. elseif master==1 && ARG.patch_average==1
  599. OPTION='master_PA' ;
  600. end
  601. switch OPTION
  602. case 'NO_master_NO_PA'
  603. case 'NO_master_PA'
  604. case 'master_NO_PA'
  605. case 'master_PA'
  606. end
  607. if QQ.KSP_processed(1,n1)~=1 && QQ.KSP_processed(1,n1)~=3 % not being processed also not completed yet
  608. if QQ.KSP_processed(1,n1)==2 && master==1% processed but not added.
  609. % loading instead of processing
  610. % load file as soon as save, if more than 10 sec, just do the recon
  611. % instead.
  612. try % try to load otherwise go to next slice
  613. load([ARG.filename 'slice' num2str(n1) '.mat'],'DATA_full2')
  614. catch;
  615. QQ.KSP_processed(1,n1)=0; % identified as bad file and being identified for reprocessing
  616. return ;end
  617. end
  618. if QQ.KSP_processed(1,n1)~=2
  619. QQ.KSP_processed(1,n1)=1; % block for other processes
  620. if ~exist('DATA_full2')
  621. ARG2=QQ.ARG;
  622. if master==0
  623. QQ.KSP_processed(1,n1)=1 ; % STARTING
  624. KSP2a=QQ.KSP2([1:ARG.kernel_size(1)]+(n1-1),:,:,:); lambda=ARG2.LLR_scale*ARG.NVR_threshold;
  625. else
  626. QQ.KSP_processed(1,n1)=1 ; % STARTING
  627. KSP2a=KSP2([1:ARG.kernel_size(1)]+(n1-1),:,:,:); lambda=ARG2.LLR_scale*ARG.NVR_threshold;
  628. end
  629. if ARG.patch_average==1
  630. % [DATA_full2, ~,NOISE, Component_threshold] =subfunction_loop_for_NVR_avg(KSP2a,ARG.kernel_size(3),ARG.kernel_size(2),ARG.kernel_size(1),lambda,1,ARG.soft_thrs);
  631. [DATA_full2, KSP2_weight] =subfunction_loop_for_NVR_avg(KSP2a,ARG.kernel_size(3),ARG.kernel_size(2),ARG.kernel_size(1),lambda,1,ARG.soft_thrs,KSP2_weight);
  632. else
  633. KSP2_weight_tmp =KSP2_weight([1:ARG.kernel_size(1)]+(n1-1),:,:,:);
  634. NOISE_tmp =NOISE([1:ARG.kernel_size(1)]+(n1-1),:,:,:);
  635. Component_threshold_tmp =Component_threshold([1:ARG.kernel_size(1)]+(n1-1),:,:,:);
  636. energy_removed_tmp =energy_removed([1:ARG.kernel_size(1)]+(n1-1),:,:,:);
  637. SNR_weight_tmp =SNR_weight([1:ARG.kernel_size(1)]+(n1-1),:,:,:);
  638. [DATA_full2,KSP2_weight_tmp,NOISE_tmp, Component_threshold_tmp,energy_removed_tmp,SNR_weight_tmp] =...
  639. subfunction_loop_for_NVR_avg_update(KSP2a,ARG.kernel_size(3),ARG.kernel_size(2),ARG.kernel_size(1),lambda,1,ARG.soft_thrs,KSP2_weight_tmp,ARG,NOISE_tmp,Component_threshold_tmp,energy_removed_tmp,SNR_weight_tmp);
  640. KSP2_weight([1:ARG.kernel_size(1)]+(n1-1),:,:,:)=KSP2_weight_tmp;
  641. try; NOISE([1:ARG.kernel_size(1)]+(n1-1),:,:,:) =NOISE_tmp; catch;end
  642. Component_threshold([1:ARG.kernel_size(1)]+(n1-1),:,:,:) = Component_threshold_tmp;
  643. energy_removed([1:ARG.kernel_size(1)]+(n1-1),:,:,:) = energy_removed_tmp;
  644. SNR_weight([1:ARG.kernel_size(1)]+(n1-1),:,:,:) = SNR_weight_tmp;
  645. %DATA_full=subfunction_loop_for_NVR(KSP2a,ARG.kernel_size(3),ARG.kernel_size(2),ARG.kernel_size(1),lambda);
  646. %DATA_full2(1, round(w2/2)+[1:size(DATA_full,1)],:,: )=DATA_full; % center plane only
  647. end
  648. end
  649. end
  650. if master==0
  651. if QQ.KSP_processed(1,n1)~=2
  652. save([ARG.filename 'slice' num2str(n1) '.mat'],'DATA_full2', '-v7.3' )
  653. QQ.KSP_processed(1,n1)=2 ; % COMPLETED
  654. end
  655. else
  656. if ARG.patch_average==1
  657. tmp=KSP_recon([1:ARG.kernel_size(1)]+(n1-1),:,:,:) ;
  658. KSP_recon([1:ARG.kernel_size(1)]+(n1-1),:,:,:)= tmp + DATA_full2;
  659. else
  660. KSP_recon([1:ARG.kernel_size(1)]+(n1-1) ,1:size(DATA_full2,2),:,:)= KSP_recon([1:ARG.kernel_size(1)]+(n1-1) ,1:size(DATA_full2,2),:,:) + DATA_full2;
  661. end
  662. QQ.KSP_processed(1,n1)=3 ;
  663. end
  664. end
  665. return
  666. function [KSP2_tmp_update, KSP2_weight]=subfunction_loop_for_NVR_avg(KSP2a,w3,w2,w1,lambda2,patch_avg, soft_thrs,KSP2_weight,ARG)
  667. if ~exist('patch_avg'); patch_avg=1;end
  668. if ~exist('soft_thrs'); soft_thrs=[]; end
  669. if ~exist('KSP2_weight')
  670. KSP2_weight=zeros(size(KSP2a(:,:,:,1)));
  671. elseif isempty(KSP2_weight)
  672. KSP2_weight=zeros(size(KSP2a(:,:,:,1)));
  673. end
  674. if ~exist('KSP2_tmp_update')
  675. KSP2_tmp_update=zeros(size(KSP2a(:,:,:,:)));
  676. elseif isempty(KSP2_tmp_update)
  677. KSP2_tmp_update=zeros(size(KSP2a(:,:,:,:)));
  678. end
  679. % KSP2_weight=zeros(size(KSP2a(:,:,:,1)));
  680. % KSP2_tmp_update=zeros(size(KSP2a));
  681. % for n2=1:size(KSP2a,2)-w2+1;
  682. % for n3=1:size(KSP2a,3)-w3+1;
  683. for n2=[1: max(1,floor(w2/ARG.patch_average_sub)):size(KSP2a,2)*1-w2+1 size(KSP2a,2)-w2+1];
  684. for n3=[1: max(1,floor(w3/ARG.patch_average_sub)):size(KSP2a,3)*1-w3+1 size(KSP2a,3)-w3+1 ];
  685. KSP2_tmp=KSP2a(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:);
  686. tmp1=reshape(KSP2_tmp,[],size(KSP2_tmp,4));
  687. [U,S,V]=svd([(tmp1) ],'econ');
  688. S=diag(S);
  689. [idx]=sum(S<lambda2);
  690. if isempty(soft_thrs)
  691. S(S<lambda2)=0;
  692. elseif soft_thrs==10 % USING MPPCA
  693. % disp('test for zero entries')
  694. Test_mat=sum(tmp1,2);
  695. sum(Test_mat==0)
  696. centering=0;
  697. MM=size(tmp1,1);
  698. NNN=size(tmp1,2);
  699. R = min(MM, NNN);
  700. scaling = (max(MM, NNN) - (0:R-centering-1)) / NNN;
  701. scaling = scaling(:);
  702. vals=S;
  703. vals = (vals).^2 / NNN;
  704. % First estimation of Sigma^2; Eq 1 from ISMRM presentation
  705. csum = cumsum(vals(R-centering:-1:1)); cmean = csum(R-centering:-1:1)./(R-centering:-1:1)'; sigmasq_1 = cmean./scaling;
  706. % Second estimation of Sigma^2; Eq 2 from ISMRM presentation
  707. gamma = (MM - (0:R-centering-1)) / NNN;
  708. rangeMP = 4*sqrt(gamma(:));
  709. rangeData = vals(1:R-centering) - vals(R-centering);
  710. sigmasq_2 = rangeData./rangeMP;
  711. t = find(sigmasq_2 < sigmasq_1, 1);
  712. S(t:end)=0;
  713. else
  714. S(max(1,end-floor(idx*soft_thrs)):end)=0;
  715. end
  716. tmp1=U*diag(S)*V';
  717. tmp1=reshape(tmp1,size(KSP2_tmp));
  718. if patch_avg==1
  719. KSP2_tmp_update(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  720. KSP2_tmp_update(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) +tmp1;
  721. KSP2_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  722. KSP2_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) +1;
  723. else
  724. KSP2_tmp_update(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  725. KSP2_tmp_update(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +tmp1(1,round(end/2),round(end/2),:);
  726. KSP2_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  727. KSP2_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +1;
  728. end
  729. end
  730. end
  731. return
  732. function [KSP2_tmp_update, KSP2_weight,NOISE,KSP2_tmp_update_threshold,energy_removed,SNR_weight]=subfunction_loop_for_NVR_avg_update(KSP2a,w3,w2,w1,lambda2,patch_avg, soft_thrs,KSP2_weight,ARG,NOISE,KSP2_tmp_update_threshold,energy_removed,SNR_weight)
  733. if ~isfield(ARG,'patch_scale'); patch_scale=1; else; patch_scale=ARG.patch_scale;end
  734. if ~exist('patch_avg'); patch_avg=1;end % patch_avg=0; means zero only
  735. if ~exist('soft_thrs'); soft_thrs=[]; end
  736. if ~exist('KSP2_weight')
  737. KSP2_weight=zeros(size(KSP2a(:,:,:,1)));
  738. elseif isempty(KSP2_weight)
  739. KSP2_weight=zeros(size(KSP2a(:,:,:,1)));
  740. end
  741. if ~exist('NOISE_tmp')'% ~exist('NOISE_tmpKSP2_tmp_update')
  742. NOISE_tmp=zeros(size(KSP2a(:,:,:,1)));
  743. elseif isempty(KSP2_tmp_update)
  744. NOISE_tmp=zeros(size(KSP2a(:,:,:,1)));
  745. end
  746. if ~exist('KSP2_tmp_update_threshold')
  747. KSP2_tmp_update_threshold=zeros(size(KSP2a(:,:,:,1)));
  748. elseif isempty(KSP2_tmp_update_threshold)
  749. KSP2_tmp_update_threshold=zeros(size(KSP2a(:,:,:,1)));
  750. end
  751. if ~exist('energy_removed')
  752. energy_removed=zeros(size(KSP2a(:,:,:,1)));
  753. elseif isempty(energy_removed)
  754. energy_removed=zeros(size(KSP2a(:,:,:,1)));
  755. end
  756. if ~exist('SNR_weight')
  757. SNR_weight=zeros(size(KSP2a(:,:,:,1)));
  758. elseif isempty(SNR_weight)
  759. SNR_weight=zeros(size(KSP2a(:,:,:,1)));
  760. end
  761. KSP2_tmp_update=0*KSP2a;
  762. %NOISE=[];
  763. for n2=[1: max(1,floor(w2/ARG.patch_average_sub)):size(KSP2a,2)*1-w2+1 size(KSP2a,2)-w2+1];
  764. for n3=[1: max(1,floor(w3/ARG.patch_average_sub)):size(KSP2a,3)*1-w3+1 size(KSP2a,3)-w3+1 ];
  765. KSP2_tmp=KSP2a(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:);
  766. tmp1=reshape(KSP2_tmp,[],size(KSP2_tmp,4));
  767. [U,S,V]=svd([(tmp1) ],'econ');
  768. S=diag(S);
  769. [idx]=sum(S<lambda2);
  770. if isempty(soft_thrs)
  771. energy_scrub=sqrt(sum(S.^1)).\sqrt(sum(S(S<lambda2).^1));
  772. S(S<lambda2)=0;
  773. t=idx;
  774. elseif soft_thrs~=10;
  775. S=S-lambda2*soft_thrs;
  776. S(S<0)=0;
  777. energy_scrub=0;
  778. t=1;
  779. elseif soft_thrs==10 % USING MPPCA
  780. % disp('test for zero entries')
  781. Test_mat=sum(tmp1,2);
  782. MM0=sum(Test_mat==0);
  783. if MM0>1 & MM0<100
  784. % 2
  785. end
  786. centering=0;
  787. MM=size(tmp1,1)-MM0; % Correction for some zero entries
  788. if MM>0
  789. NNN=size(tmp1,2);
  790. R = min(MM, NNN);
  791. scaling = (max(MM, NNN) - (0:R-centering-1)) / NNN;
  792. scaling = scaling(:);
  793. vals=S;
  794. vals = (vals).^2 / NNN;
  795. % First estimation of Sigma^2; Eq 1 from ISMRM presentation
  796. csum = cumsum(vals(R-centering:-1:1)); cmean = csum(R-centering:-1:1)./(R-centering:-1:1)'; sigmasq_1 = cmean./scaling;
  797. % Second estimation of Sigma^2; Eq 2 from ISMRM presentation
  798. gamma = (MM - (0:R-centering-1)) / NNN;
  799. rangeMP = 4*sqrt(gamma(:));
  800. rangeData = vals(1:R-centering) - vals(R-centering);
  801. sigmasq_2 = rangeData./rangeMP;
  802. t = find(sigmasq_2 < sigmasq_1, 1);
  803. % NOISE(1:size(KSP2a,1),[1:w2]+(n2-1),[1:w3]+(n3-1),1) = sigmasq_2(t);
  804. idx=size(S(t:end),1) ;
  805. energy_scrub=sqrt(sum(S.^1)).\sqrt(sum(S(t:end).^1));
  806. S(t:end)=0;
  807. else % all zero entries
  808. t=1;
  809. energy_scrub=0;
  810. sigmasq_2=0;
  811. end
  812. else
  813. S(max(1,end-floor(idx*soft_thrs)):end)=0;
  814. end
  815. tmp1=U*diag(S)*V';
  816. tmp1=reshape(tmp1,size(KSP2_tmp));
  817. if patch_scale==1; else; patch_scale=size(S,1)-idx; end
  818. if isempty(t); t=1; end % threshold removed all.
  819. if patch_avg==1
  820. KSP2_tmp_update(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  821. KSP2_tmp_update(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) +patch_scale*tmp1;
  822. KSP2_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  823. KSP2_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) + patch_scale;
  824. KSP2_tmp_update_threshold(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  825. KSP2_tmp_update_threshold(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) +idx;
  826. energy_removed(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) =...
  827. energy_removed(:,[1:w2]+(n2-1),[1:w3]+(n3-1),:) +energy_scrub;
  828. SNR_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),1) =...
  829. SNR_weight(:,[1:w2]+(n2-1),[1:w3]+(n3-1),1) + S(1)./S(max(1,t-1));
  830. try
  831. NOISE(1:size(KSP2a,1),[1:w2]+(n2-1),[1:w3]+(n3-1),1) = ...
  832. NOISE(1:size(KSP2a,1),[1:w2]+(n2-1),[1:w3]+(n3-1),1) + sigmasq_2(t);; catch; end
  833. else
  834. KSP2_tmp_update(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  835. KSP2_tmp_update(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +patch_scale*tmp1(1,round(end/2),round(end/2),:);
  836. KSP2_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  837. KSP2_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +patch_scale;
  838. KSP2_tmp_update_threshold(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  839. KSP2_tmp_update_threshold(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +idx;
  840. energy_removed(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) =...
  841. energy_removed(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) +energy_scrub;
  842. SNR_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),1) =...
  843. SNR_weight(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),1) + S(1)./S(max(1,t-1));
  844. try
  845. NOISE(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) = ...
  846. NOISE(:,round(w2/2)+(n2-1),round(w3/2)+(n3-1),:) + sigmasq_2(t);; catch; end
  847. end
  848. % if MM0>1 & MM0<196
  849. % [ sigmasq_2(t) MM0] % 2
  850. % end
  851. end
  852. end
  853. return

NIFTI_NORDIC_wt.m at commit 29af031, under MIT · at the source

Overview

Authors: Wei-Tang Chang1,2,3, Weili Lin1,2, Kelly S Giovanello1,4
ORCID iDs: Wei-Tang Chang
  1. Biomedical Research Imaging Center, University of North Carolina at Chapel Hill Chapel Hill United States
  2. Department of Radiology, University of North Carolina at Chapel Hill Chapel Hill United States
  3. Department of Biomedical Engineering, University of North Carolina at Chapel Hill Chapel Hill United States
  4. Department of Psychology and Neuroscience, University of North Carolina at Chapel Hill Chapel Hill United States
Institutions: University of North Carolina at Chapel Hill (United States)
Journal: eLife, volume 12, article RP92805
Dates: published online 21 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.92805 · PMID 42012994 · PMCID PMC13099135 · OpenAlex W4389922539
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), human (organism), systems (subfield)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Preprocessing, fMRI & imaging
Keywords: brain, layer fMRI, connectivity, Human
MeSH: Brain Mapping*, Cerebral Cortex*, Magnetic Resonance Imaging*, Adult, Female, Humans, Male (* major topic)
Journal subjects: Neuroscience
Topic: Advanced MRI Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: National Institutes of Health (R21AG060324)
Citations: not cited yet (Europe PMC); 76 references in the paper

Abstract

Layer-dependent functional magnetic resonance imaging (fMRI) is a promising yet challenging approach for investigating layer-specific functional connectivity (FC). Achieving a brain-wide mapping of layer-specific FC requires several technical advancements, including sub-millimeter spatial resolution, sufficient temporal resolution, functional sensitivity, global brain coverage, and high spatial specificity. Although gradient echo (GE)-based echo planar imaging (EPI) is commonly used for rapid fMRI acquisition, it faces significant challenges due to the draining-vein contamination. In this study, we addressed these limitations by integrating velocity-nulling (VN) gradients into a GE-BOLD fMRI sequence to suppress vascular signals from the vessels with fast-flowing velocity. The extravascular contamination from pial veins was mitigated using a GE-EPI sequence at 3T rather than 7T, combined with phase regression methods. Additionally, we incorporated advanced techniques, including simultaneous multi-slice (SMS) acceleration and NOise Reduction with DIstribution Corrected principal component analysis (NORDIC PCA) denoising, to improve temporal resolution, spatial coverage, and signal sensitivity. This resulted in a VN fMRI sequence with 0.9 mm isotropic spatial resolution, a repetition time (TR) of 4 s, and brain-wide coverage. The VN gradient strength was determined based on results from a button-pressing task. Using resting-state data, we validated layer-specific FC through seed-based analyses, identifying distinct connectivity patterns in the superficial and deep layers of the primary motor cortex (M1), with significant inter-layer differences. Further analyses with a seed in the primary sensory cortex (S1) demonstrated the reliability of the method. Brain-wide layer-dependent FC analyses yielded results consistent with prior literature, reinforcing the efficacy of VN fMRI in resolving layer-specific functional connectivity. Given the widespread availability of 3T scanners, this technical advancement has the potential for significant impact across multiple domains of neuroscience research.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

welton0411/matlab_sms_recon

License: MIT
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 29af03114c6b85202e8a109e7107663ab2a89d3d, 20 April 2026
Languages: MATLAB (34)
Size: 36 files, 34 scripts
Software Heritage: archived
Found in: “Data availability”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
36 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 34 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

All imaging data has been deposited at OpenNeuro (https://openneuro.org/datasets/ds007543). Image reconstruction code is available at GitHub (https://github.com/welton0411/matlab_sms_recon) (copy archived at welton0411, 2026). Please note that the code provided is in its raw form; it has not been optimized, cleaned, or commented. While this may affect the ease of use or adaptation, we believe it remains a valuable resource for those interested in understanding or extending our analytical methods.

The following dataset was generated:

ChangWT LinW GiovanelloK OpenNeuro2026Open data of VNfMRIds007543

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 29 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 3 authors, 4 keywords, 7 MeSH terms, 1 funder, 74 references.

Cite

This paper

Chang, W.-T., Lin, W., & Giovanello, K. S. (2026). Brain-wide mapping of layer-specific functional connectivity in the human cortex at 3T using draining-vein-suppressed fMRI. eLife, 12, RP92805. https://doi.org/10.7554/elife.92805

BibTeX

@article{chang2026brain,
author = {Chang, Wei-Tang and Lin, Weili and Giovanello, Kelly S},
title = {{Brain-wide mapping of layer-specific functional connectivity in the human cortex at 3T using draining-vein-suppressed fMRI}},
journal = {eLife},
year = {2026},
month = apr,
volume = {12},
pages = {RP92805},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.92805},
url = {https://doi.org/10.7554/elife.92805},
pmid = {42012994},
pmcid = {PMC13099135}
}

RIS

TY - JOUR
AU - Chang, Wei-Tang
AU - Lin, Weili
AU - Giovanello, Kelly S
TI - Brain-wide mapping of layer-specific functional connectivity in the human cortex at 3T using draining-vein-suppressed fMRI
T2 - eLife
J2 - Elife
PY - 2026
DA - 2026/04/21
VL - 12
SP - RP92805
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.92805
UR - https://doi.org/10.7554/elife.92805
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.92805",
"type": "article-journal",
"title": "Brain-wide mapping of layer-specific functional connectivity in the human cortex at 3T using draining-vein-suppressed fMRI",
"container-title": "eLife",
"author": [
{
"family": "Chang",
"given": "Wei-Tang"
},
{
"family": "Lin",
"given": "Weili"
},
{
"family": "Giovanello",
"given": "Kelly S"
}
],
"container-title-short": "Elife",
"volume": "12",
"page": "RP92805",
"DOI": "10.7554/elife.92805",
"PMID": "42012994",
"PMCID": "PMC13099135",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.92805",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
21
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014576 [code]
A synthetic 3D human cerebrovascular model informed by histology for simulating the cortical depth-dependent BOLD fMRI signal.
Journal: PLoS computational biology
In common: fMRI, 11 references
[2] doi:10.7554/elife.108408 [code]
Frequency and laminar profile of feature-specific visual activity revealed by interleaved EEG-fMRI.
Journal: eLife
In common: Tools for NIfTI and ANALYZE image (MATLAB), Image Processing Toolbox, Signal Processing Toolbox, 1 other tool, fMRI, systems, 7 references
[3] doi:10.1162/imag.a.1197 [code]
Blood volume-sensitive laminar fMRI with VASO in human hippocampus: Capabilities and biophysical challenges at clinical 7T scanners.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Tools for NIfTI and ANALYZE image (MATLAB), Image Processing Toolbox, fMRI, systems, 7 references
[4] doi:10.1162/imag.a.1340
Biophysical simulations of fMRI responses using realistic microvascular models: Insights into distinct hemodynamics in humans and mice.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: fMRI, 8 references
[5] doi:10.1002/hbm.70483 [code]
Untamed: Unconstrained Tensor Decomposition and Graph Node Embedding for Cortical Parcellation.
Journal: Human brain mapping
In common: Tools for NIfTI and ANALYZE image (MATLAB), Parallel Computing Toolbox, Image Processing Toolbox, 2 other tools, fMRI, 4 references
[6] doi:10.1162/imag.a.1247 [code]
Evaluating BOLD functional MRI biophysical simulation approaches: Impact of vascular geometry, magnetic field calculations, and water diffusion models.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: fMRI, 8 references
[7] doi:10.1038/s41467-026-74215-5 [code]
Multi-metric evaluations of acute psychedelic effects on fMRI brain entropy.
Journal: Nature communications
In common: Tools for NIfTI and ANALYZE image (MATLAB), Parallel Computing Toolbox, Image Processing Toolbox, 2 other tools, fMRI, 2 references
[8] doi:10.1038/s41467-026-71842-w [code]
Layer-specific attentional modulation in the human primary somatosensory cortex.
Journal: Nature communications
In common: Image Processing Toolbox, Statistics and Machine Learning Toolbox, 5 references
[9] doi:10.1002/mrm.70336 [code]
Offline Reconstruction of Diffusion MRI Acquisitions for Comparison Between Complex PCA-Based and AI-Based Denoising.
Journal: Magnetic resonance in medicine
In common: Tools for NIfTI and ANALYZE image (MATLAB), Parallel Computing Toolbox, Image Processing Toolbox, 2 other tools, 2 references
[10] doi:10.1038/s41467-026-73540-z [code]
Predictive acoustical processing in human cortical layers.
Journal: Nature communications
In common: Image Processing Toolbox, Signal Processing Toolbox, Statistics and Machine Learning Toolbox, 4 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.