function out = spm_dicom_convert(hdr,opts,root_dir,format,out_dir,meta) % Convert DICOM images into something that SPM can use (e.g. NIfTI) % FORMAT out = spm_dicom_convert(hdr,opts,root_dir,format,out_dir) % Inputs: % hdr - a cell array of DICOM headers from spm_dicom_headers % opts - options: % 'all' - all DICOM files [default] % 'mosaic' - the mosaic images % 'standard' - standard DICOM files % 'spect' - SIEMENS Spectroscopy DICOMs (some formats only) % This will write out a 5D NIFTI containing real % and imaginary part of the spectroscopy time % points at the position of spectroscopy voxel(s) % 'raw' - convert raw FIDs (not implemented) % root_dir - 'flat' - do not produce file tree [default] % With all other options, files will be sorted into % directories according to their sequence/protocol names: % 'date_time' - Place files under ./ % 'patid' - Place files under ./ % 'patid_date' - Place files under ./ % 'patname' - Place files under ./ % 'series' - Place files in series folders, without % creating patient folders % format - output format: % 'nii' - Single file NIfTI format [default] % 'img' - Two file (hdr+img) NIfTI format % All images will contain a single 3D dataset, 4D images will % not be created. % out_dir - output directory name [default: pwd] % meta - save metadata as sidecar JSON file [default: false] % % Output: % out - a struct with a single field .files. out.files contains a % cellstring with filenames of created files. If no files are % created, a cell with an empty string {''} is returned. %__________________________________________________________________________ % Copyright (C) 2002-2017 Wellcome Trust Centre for Neuroimaging % John Ashburner % $Id: spm_dicom_convert.m 7201 2017-11-08 11:13:25Z guillaume $ %-Input parameters %-------------------------------------------------------------------------- if nargin<2, opts = 'all'; end if nargin<3, root_dir = 'flat'; end if nargin<4, format = spm_get_defaults('images.format'); end if nargin<5, out_dir = pwd; end if nargin<6, meta = false; end %-Select files %-------------------------------------------------------------------------- [images, other] = select_tomographic_images(hdr); [multiframe,images] = select_multiframe(images); [spect, guff] = select_spectroscopy_images(other); [mosaic, standard] = select_mosaic_images(images); [standard, guff] = select_last_guff(standard, guff); if ~isempty(guff) warning('spm:dicom','%d files could not be converted from DICOM.', numel(guff)); end %-Convert files %-------------------------------------------------------------------------- fmos = {}; fstd = {}; fspe = {}; fmul = {}; if (strcmp(opts,'all') || strcmp(opts,'mosaic')) && ~isempty(mosaic) fmos = convert_mosaic(mosaic,root_dir,format,out_dir,meta); end if (strcmp(opts,'all') || strcmp(opts,'standard')) && ~isempty(standard) fstd = convert_standard(standard,root_dir,format,out_dir,meta); end if (strcmp(opts,'all') || strcmp(opts,'spect')) && ~isempty(spect) fspe = convert_spectroscopy(spect,root_dir,format,out_dir,meta); end if (strcmp(opts,'all') || strcmp(opts,'multiframe')) && ~isempty(multiframe) fmul = convert_multiframes(multiframe,root_dir,format,out_dir,meta); end out.files = [fmos(:); fstd(:); fspe(:); fmul(:)]; if isempty(out.files) out.files = {''}; end %========================================================================== % function fnames = convert_mosaic(hdr,root_dir,format,out_dir,meta) %========================================================================== function fnames = convert_mosaic(hdr,root_dir,format,out_dir,meta) spm_progress_bar('Init',length(hdr),'Writing Mosaic', 'Files written'); fnames = cell(length(hdr),1); for i=1:length(hdr) % Output filename %---------------------------------------------------------------------- fnames{i} = getfilelocation(hdr{i},root_dir,'f',format,out_dir); % Image dimensions and data %---------------------------------------------------------------------- nc = hdr{i}.Columns; nr = hdr{i}.Rows; dim = [0 0 0]; dim(3) = read_NumberOfImagesInMosaic(hdr{i}); np = [nc nr]/ceil(sqrt(dim(3))); dim(1:2) = np; if ~all(np==floor(np)) warning('spm:dicom','%s: dimension problem [Num Images=%d, Num Cols=%d, Num Rows=%d].',... hdr{i}.Filename,dim(3), nc,nr); continue end % Apparently, this is not the right way of doing it. %np = read_AcquisitionMatrixText(hdr{i}); %if rem(nc, np(1)) || rem(nr, np(2)), % warning('spm:dicom','%s: %dx%d wont fit into %dx%d.',hdr{i}.Filename,... % np(1), np(2), nc,nr); % return; %end; %dim = [np read_NumberOfImagesInMosaic(hdr{i})]; mosaic = read_image_data(hdr{i}); volume = zeros(dim); snnz = zeros(dim(3), 1); for j=1:dim(3) img = mosaic((1:np(1))+np(1)*rem(j-1,nc/np(1)), (np(2):-1:1)+np(2)*floor((j-1)/(nc/np(1)))); snnz(j) = nnz(img) > 0; volume(:,:,j) = img; end d3 = find(snnz, 1, 'last'); if ~isempty(d3) dim(3) = d3; volume = volume(:,:,1:dim(3)); end dt = determine_datatype(hdr{1}); % Orientation information %---------------------------------------------------------------------- % Axial Analyze voxel co-ordinate system: % x increases right to left % y increases posterior to anterior % z increases inferior to superior % DICOM patient co-ordinate system: % x increases right to left % y increases anterior to posterior % z increases inferior to superior % T&T co-ordinate system: % x increases left to right % y increases posterior to anterior % z increases inferior to superior analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)-1) 0]'; 0 0 0 1]*[eye(4,3) [-1 -1 -1 1]']; vox = [hdr{i}.PixelSpacing(:); hdr{i}.SpacingBetweenSlices]; pos = hdr{i}.ImagePositionPatient(:); orient = reshape(hdr{i}.ImageOrientationPatient,[3 2]); orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end; % The image position vector is not correct. In dicom this vector points to % the upper left corner of the image. Perhaps it is unlucky that this is % calculated in the syngo software from the vector pointing to the center of % the slice (keep in mind: upper left slice) with the enlarged FoV. dicom_to_patient = [orient*diag(vox) pos ; 0 0 0 1]; truepos = dicom_to_patient *[(size(mosaic)-dim(1:2))/2 0 1]'; dicom_to_patient = [orient*diag(vox) truepos(1:3) ; 0 0 0 1]; patient_to_tal = diag([-1 -1 1 1]); mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; % Maybe flip the image depending on SliceNormalVector from 0029,1010 %---------------------------------------------------------------------- SliceNormalVector = read_SliceNormalVector(hdr{i}); if det([reshape(hdr{i}.ImageOrientationPatient,[3 2]) SliceNormalVector(:)])<0 volume = volume(:,:,end:-1:1); mat = mat*[eye(3) [0 0 -(dim(3)-1)]'; 0 0 0 1]; end % Possibly useful information %---------------------------------------------------------------------- if checkfields(hdr{i},'AcquisitionTime','MagneticFieldStrength',... 'MRAcquisitionType','ScanningSequence','RepetitionTime',... 'EchoTime','FlipAngle','AcquisitionDate') tim = datevec(hdr{i}.AcquisitionTime/(24*60*60)); descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg %s %d:%d:%.5g Mosaic',... hdr{i}.MagneticFieldStrength, hdr{i}.MRAcquisitionType,... deblank(hdr{i}.ScanningSequence),... hdr{i}.RepetitionTime,hdr{i}.EchoTime,hdr{i}.FlipAngle,... datestr(hdr{i}.AcquisitionDate),tim(4),tim(5),tim(6)); else descrip = hdr{1}.Modality; end % descrip = [deblank(descrip) ' ' hdr{i}.PatientsName]; if ~true % LEFT-HANDED STORAGE mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; volume = flipud(volume); end %if isfield(hdr{i},'RescaleSlope') && hdr{i}.RescaleSlope ~= 1, % volume = volume*hdr{i}.RescaleSlope; %end; %if isfield(hdr{i},'RescaleIntercept') && hdr{i}.RescaleIntercept ~= 0, % volume = volume + hdr{i}.RescaleIntercept; %end; %V = struct('fname',fname, 'dim',dim, 'dt',dt, 'mat',mat, 'descrip',descrip); %spm_write_vol(V,volume); % Note that data are no longer scaled by the maximum amount. % This may lead to rounding errors in smoothed data, but it % will get around other problems. RescaleSlope = 1; RescaleIntercept = 0; if isfield(hdr{i},'RescaleSlope') && hdr{i}.RescaleSlope ~= 1 RescaleSlope = hdr{i}.RescaleSlope; end if isfield(hdr{i},'RescaleIntercept') && hdr{i}.RescaleIntercept ~= 0 RescaleIntercept = hdr{i}.RescaleIntercept; end N = nifti; N.dat = file_array(fnames{i},dim,dt,0,RescaleSlope,RescaleIntercept); N.mat = mat; N.mat0 = mat; N.mat_intent = 'Scanner'; N.mat0_intent = 'Scanner'; N.descrip = descrip; create(N); if meta N = spm_dicom_metadata(N,hdr{i}); end % Write the data unscaled dat = N.dat; dat.scl_slope = []; dat.scl_inter = []; % write out volume at once - see spm_write_plane.m for performance comments dat(:,:,:) = volume; spm_progress_bar('Set',i); end spm_progress_bar('Clear'); %========================================================================== % function fnames = convert_standard(hdr,root_dir,format,out_dir,meta) %========================================================================== function fnames = convert_standard(hdr,root_dir,format,out_dir,meta) hdr = sort_into_volumes(hdr); fnames = cell(length(hdr),1); for i=1:length(hdr) fnames{i} = write_volume(hdr{i},root_dir,format,out_dir,meta); end %========================================================================== % function vol = sort_into_volumes(hdr) %========================================================================== function vol = sort_into_volumes(hdr) % % First of all, sort into volumes based on relevant % fields in the header. % vol{1}{1} = hdr{1}; for i=2:length(hdr) %orient = reshape(hdr{i}.ImageOrientationPatient,[3 2]); %xy1 = hdr{i}.ImagePositionPatient(:)*orient; match = 0; if isfield(hdr{i},'CSAImageHeaderInfo') && isfield(hdr{i}.CSAImageHeaderInfo,'name') ice1 = sscanf( ... strrep(get_numaris4_val(hdr{i}.CSAImageHeaderInfo,'ICE_Dims'), ... 'X', '-1'), '%i_%i_%i_%i_%i_%i_%i_%i_%i')'; dimsel = logical([1 1 1 1 1 1 0 0 1]); else ice1 = []; end for j=1:length(vol) %orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); %xy2 = vol{j}{1}.ImagePositionPatient(:)*orient; % This line is a fudge because of some problematic data that Bogdan, % Cynthia and Stefan were trying to convert. I hope it won't cause % problems for others -JA % dist2 = sum((xy1-xy2).^2); dist2 = 0; if strcmp(hdr{i}.Modality,'CT') && ... strcmp(vol{j}{1}.Modality,'CT') % Our CT seems to have shears in slice positions dist2 = 0; end if ~isempty(ice1) && isfield(vol{j}{1},'CSAImageHeaderInfo') && numel(vol{j}{1}.CSAImageHeaderInfo)>=1 && isfield(vol{j}{1}.CSAImageHeaderInfo(1),'name') % Replace 'X' in ICE_Dims by '-1' ice2 = sscanf( ... strrep(get_numaris4_val(vol{j}{1}.CSAImageHeaderInfo,'ICE_Dims'), ... 'X', '-1'), '%i_%i_%i_%i_%i_%i_%i_%i_%i')'; if ~isempty(ice2) identical_ice_dims=all(ice1(dimsel)==ice2(dimsel)); else identical_ice_dims = 0; % have ice1 but not ice2, -> % something must be different end else identical_ice_dims = 1; % No way of knowing if there is no CSAImageHeaderInfo end try match = hdr{i}.SeriesNumber == vol{j}{1}.SeriesNumber &&... hdr{i}.Rows == vol{j}{1}.Rows &&... hdr{i}.Columns == vol{j}{1}.Columns &&... sum((hdr{i}.ImageOrientationPatient - vol{j}{1}.ImageOrientationPatient).^2)<1e-4 &&... sum((hdr{i}.PixelSpacing - vol{j}{1}.PixelSpacing).^2)<1e-4 && ... identical_ice_dims && dist2<1e-3; %if (hdr{i}.AcquisitionNumber ~= hdr{i}.InstanceNumber) || ... % (vol{j}{1}.AcquisitionNumber ~= vol{j}{1}.InstanceNumber) % match = match && (hdr{i}.AcquisitionNumber == vol{j}{1}.AcquisitionNumber) %end % For raw image data, tell apart real/complex or phase/magnitude if isfield(hdr{i},'ImageType') && isfield(vol{j}{1}, 'ImageType') match = match && strcmp(hdr{i}.ImageType, vol{j}{1}.ImageType); end if isfield(hdr{i},'SequenceName') && isfield(vol{j}{1}, 'SequenceName') match = match && strcmp(hdr{i}.SequenceName,vol{j}{1}.SequenceName); end if isfield(hdr{i},'SeriesInstanceUID') && isfield(vol{j}{1}, 'SeriesInstanceUID') match = match && strcmp(hdr{i}.SeriesInstanceUID,vol{j}{1}.SeriesInstanceUID); end if isfield(hdr{i},'EchoNumbers') && isfield(vol{j}{1}, 'EchoNumbers') match = match && hdr{i}.EchoNumbers == vol{j}{1}.EchoNumbers; end if isfield(hdr{i}, 'GE_ImageType') && numel( hdr{i}.GE_ImageType)==1 && ... isfield(vol{j}{1}, 'GE_ImageType') && numel(vol{j}{1}.GE_ImageType)==1 match = match && hdr{i}.GE_ImageType == vol{j}{1}.GE_ImageType; end catch match = 0; end if match vol{j}{end+1} = hdr{i}; break; end end if ~match vol{end+1}{1} = hdr{i}; end end %dcm = vol; %save('dicom_headers.mat','dcm'); % % Secondly, sort volumes into ascending/descending % slices depending on .ImageOrientationPatient field. % vol2 = {}; for j=1:length(vol) orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); proj = null(orient'); if det([orient proj])<0, proj = -proj; end z = zeros(length(vol{j}),1); for i=1:length(vol{j}) z(i) = vol{j}{i}.ImagePositionPatient(:)'*proj; end [z,index] = sort(z); vol{j} = vol{j}(index); if length(vol{j})>1 % dist = diff(z); if any(diff(z)==0) tmp = sort_into_vols_again(vol{j}); vol{j} = tmp{1}; vol2 = {vol2{:} tmp{2:end}}; end end end vol = {vol{:} vol2{:}}; for j=1:length(vol) if length(vol{j})>1 orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); proj = null(orient'); if det([orient proj])<0, proj = -proj; end z = zeros(length(vol{j}),1); for i=1:length(vol{j}) z(i) = vol{j}{i}.ImagePositionPatient(:)'*proj; end dist = diff(sort(z)); if sum((dist-mean(dist)).^2)/length(dist)>1e-4 fprintf('***************************************************\n'); fprintf('* VARIABLE SLICE SPACING *\n'); fprintf('* This may be due to missing DICOM files. *\n'); PatientID = 'anon'; if checkfields(vol{j}{1},'PatientID'), PatientID = deblank(vol{j}{1}.PatientID); end if checkfields(vol{j}{1},'SeriesNumber','AcquisitionNumber','InstanceNumber') fprintf('* %s / %d / %d / %d \n',... PatientID, vol{j}{1}.SeriesNumber, ... vol{j}{1}.AcquisitionNumber, vol{j}{1}.InstanceNumber); fprintf('* *\n'); end fprintf('* %20.4g *\n', dist); fprintf('***************************************************\n'); end end end %========================================================================== % function vol2 = sort_into_vols_again(volj) %========================================================================== function vol2 = sort_into_vols_again(volj) if ~isfield(volj{1},'InstanceNumber'), fprintf('***************************************************\n'); fprintf('* The slices may be all mixed up and the data *\n'); fprintf('* not really usable. Talk to your physicists *\n'); fprintf('* about this. *\n'); fprintf('***************************************************\n'); vol2 = {volj}; return; end fprintf('***************************************************\n'); fprintf('* The AcquisitionNumber counter does not appear *\n'); fprintf('* to be changing from one volume to another. *\n'); fprintf('* Another possible explanation is that the same *\n'); fprintf('* DICOM slices are used multiple times. *\n'); %fprintf('* Talk to your MR sequence developers or scanner *\n'); %fprintf('* supplier to have this fixed. *\n'); fprintf('* The conversion is having to guess how slices *\n'); fprintf('* should be arranged into volumes. *\n'); PatientID = 'anon'; if checkfields(volj{1},'PatientID'), PatientID = deblank(volj{1}.PatientID); end if checkfields(volj{1},'SeriesNumber','AcquisitionNumber') fprintf('* %s / %d / %d\n',... PatientID, volj{1}.SeriesNumber, ... volj{1}.AcquisitionNumber); end fprintf('***************************************************\n'); z = zeros(length(volj),1); t = zeros(length(volj),1); d = zeros(length(volj),1); orient = reshape(volj{1}.ImageOrientationPatient,[3 2]); proj = null(orient'); if det([orient proj])<0, proj = -proj; end for i=1:length(volj), z(i) = volj{i}.ImagePositionPatient(:)'*proj; t(i) = volj{i}.InstanceNumber; end % msg = 0; [t,index] = sort(t); volj = volj(index); z = z(index); msk = find(diff(t)==0); if any(msk) % fprintf('***************************************************\n'); % fprintf('* These files have the same InstanceNumber: *\n'); % for i=1:length(msk), % [tmp,nam1,ext1] = fileparts(volj{msk(i)}.Filename); % [tmp,nam2,ext2] = fileparts(volj{msk(i)+1}.Filename); % fprintf('* %s%s = %s%s (%d)\n', nam1,ext1,nam2,ext2, volj{msk(i)}.InstanceNumber); % end; % fprintf('***************************************************\n'); index = [true ; diff(t)~=0]; t = t(index); z = z(index); d = d(index); volj = volj(index); end %if any(diff(sort(t))~=1), msg = 1; end; [z,index] = sort(z); volj = volj(index); t = t(index); vol2 = {}; while ~all(d) i = find(~d); i = i(1); i = find(z==z(i)); [t(i),si] = sort(t(i)); volj(i) = volj(i(si)); for i1=1:length(i) if length(vol2) 1 if isfield(hdr{1},'ImagePositionPatient') &&... isfield(hdr{1},'ImageOrientationPatient') &&... isfield(hdr{1},'SliceThickness') &&... isfield(hdr{1},'StartOfPixelData') &&... isfield(hdr{1},'SizeOfPixelData') orient = reshape(hdr{1}.ImageOrientationPatient,[3 2]); orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end slicevec = orient(:,3); hdr_temp = cell(1,hdr{1}.NumberOfFrames); % alternative: NumberofSlices hdr_temp{1} = hdr{1}; hdr_temp{1}.SizeOfPixelData = hdr{1}.SizeOfPixelData / hdr{1}.NumberOfFrames; for sn = 2 : hdr{1}.NumberOfFrames hdr_temp{sn} = hdr{1}; hdr_temp{sn}.ImagePositionPatient = hdr{1}.ImagePositionPatient + (sn-1) * hdr{1}.SliceThickness * slicevec; hdr_temp{sn}.SizeOfPixelData = hdr_temp{1}.SizeOfPixelData; hdr_temp{sn}.StartOfPixelData = hdr{1}.StartOfPixelData + (sn-1) * hdr_temp{1}.SizeOfPixelData; end hdr = hdr_temp; else error('spm_dicom_convert:write_volume','TAGS missing in DICOM file.'); end end dim = [nc nr length(hdr)]; dt = determine_datatype(hdr{1}); % Orientation information %-------------------------------------------------------------------------- % Axial Analyze voxel co-ordinate system: % x increases right to left % y increases posterior to anterior % z increases inferior to superior % DICOM patient co-ordinate system: % x increases right to left % y increases anterior to posterior % z increases inferior to superior % T&T co-ordinate system: % x increases left to right % y increases posterior to anterior % z increases inferior to superior analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)+1) 0]'; 0 0 0 1]; % Flip voxels in y patient_to_tal = diag([-1 -1 1 1]); % Flip mm coords in x and y directions R = [reshape(hdr{1}.ImageOrientationPatient,3,2)*diag(hdr{1}.PixelSpacing); 0 0]; x1 = [1;1;1;1]; y1 = [hdr{1}.ImagePositionPatient(:); 1]; if length(hdr)>1 x2 = [1;1;dim(3); 1]; y2 = [hdr{end}.ImagePositionPatient(:); 1]; else orient = reshape(hdr{1}.ImageOrientationPatient,[3 2]); orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end if checkfields(hdr{1},'SliceThickness') z = hdr{1}.SliceThickness; else z = 1; end x2 = [0;0;1;0]; y2 = [orient*[0;0;z];0]; end dicom_to_patient = [y1 y2 R]/[x1 x2 eye(4,2)]; mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; % Possibly useful information %-------------------------------------------------------------------------- if checkfields(hdr{1},'AcquisitionTime','MagneticFieldStrength','MRAcquisitionType',... 'ScanningSequence','RepetitionTime','EchoTime','FlipAngle',... 'AcquisitionDate') if isfield(hdr{1},'ScanOptions') ScanOptions = hdr{1}.ScanOptions; else ScanOptions = 'no'; end tim = datevec(hdr{1}.AcquisitionTime/(24*60*60)); descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg/SO=%s %s %d:%d:%.5g',... hdr{1}.MagneticFieldStrength, hdr{1}.MRAcquisitionType,... deblank(hdr{1}.ScanningSequence),... hdr{1}.RepetitionTime,hdr{1}.EchoTime,hdr{1}.FlipAngle,... ScanOptions,... datestr(hdr{1}.AcquisitionDate),tim(4),tim(5),tim(6)); else descrip = hdr{1}.Modality; end if ~true, % LEFT-HANDED STORAGE mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; end % Write the image volume %-------------------------------------------------------------------------- spm_progress_bar('Init',length(hdr),['Writing ' fname], 'Planes written'); pinfos = [ones(length(hdr),1) zeros(length(hdr),1)]; for i=1:length(hdr) if isfield(hdr{i},'RescaleSlope'), pinfos(i,1) = hdr{i}.RescaleSlope; end if isfield(hdr{i},'RescaleIntercept'), pinfos(i,2) = hdr{i}.RescaleIntercept; end end if any(any(diff(pinfos,1))) % Ensure random numbers are reproducible (see later) % when intensities are dithered to prevent aliasing effects. rand('state',0); end volume = zeros(dim); for i=1:length(hdr) plane = read_image_data(hdr{i}); if any(any(diff(pinfos,1))) % This is to prevent aliasing effects in any subsequent histograms % of the data (eg for mutual information coregistration). % It's a bit inelegant, but probably necessary for when slices are % individually rescaled. plane = double(plane) + rand(size(plane)) - 0.5; end if pinfos(i,1)~=1, plane = plane*pinfos(i,1); end if pinfos(i,2)~=0, plane = plane+pinfos(i,2); end plane = fliplr(plane); if ~true, plane = flipud(plane); end % LEFT-HANDED STORAGE volume(:,:,i) = plane; spm_progress_bar('Set',i); end if ~any(any(diff(pinfos,1))) % Same slopes and intercepts for all slices pinfo = pinfos(1,:); else % Variable slopes and intercept (maybe PET/SPECT) mx = max(volume(:)); mn = min(volume(:)); % Slope and Intercept % 32767*pinfo(1) + pinfo(2) = mx % -32768*pinfo(1) + pinfo(2) = mn % pinfo = ([32767 1; -32768 1]\[mx; mn])'; % Slope only dt = 'int16-be'; pinfo = [max(mx/32767,-mn/32768) 0]; end N = nifti; N.dat = file_array(fname,dim,dt,0,pinfo(1),pinfo(2)); N.mat = mat; N.mat0 = mat; N.mat_intent = 'Scanner'; N.mat0_intent = 'Scanner'; N.descrip = descrip; create(N); if meta N = spm_dicom_metadata(N,hdr{1}); end N.dat(:,:,:) = volume; spm_progress_bar('Clear'); %========================================================================== % function fnames = convert_spectroscopy(hdr,root_dir,format,out_dir,meta) %========================================================================== function fnames = convert_spectroscopy(hdr,root_dir,format,out_dir,meta) fnames = cell(length(hdr),1); for i=1:length(hdr) fnames{i} = write_spectroscopy_volume(hdr(i),root_dir,format,out_dir,meta); end %========================================================================== % function fname = write_spectroscopy_volume(hdr,root_dir,format,out_dir,meta) %========================================================================== function fname = write_spectroscopy_volume(hdr,root_dir,format,out_dir,meta) % Output filename %------------------------------------------------------------------- fname = getfilelocation(hdr{1}, root_dir,'S',format,out_dir); % private field to use - depends on SIEMENS software version if isfield(hdr{1}, 'CSANonImageHeaderInfoVA') privdat = hdr{1}.CSANonImageHeaderInfoVA; elseif isfield(hdr{1}, 'CSANonImageHeaderInfoVB') privdat = hdr{1}.CSANonImageHeaderInfoVB; else disp('Don''t know how to handle these spectroscopy data'); fname = ''; return; end % Image dimensions %-------------------------------------------------------------------------- nc = get_numaris4_numval(privdat,'Columns'); nr = get_numaris4_numval(privdat,'Rows'); % Guess number of timepoints in file - I don't know for sure whether this should be % 'DataPointRows'-by-'DataPointColumns', 'SpectroscopyAcquisitionDataColumns' % or sSpecPara.lVectorSize from SIEMENS ASCII header % ntp = get_numaris4_numval(privdat,'DataPointRows')*get_numaris4_numval(privdat,'DataPointColumns'); ac = read_ascconv(hdr{1}); try ntp = ac.sSpecPara.lVectorSize; catch disp('Don''t know how to handle these spectroscopy data'); fname = ''; return; end dim = [nc nr numel(hdr) 2 ntp]; dt = spm_type('float32'); % Fixed datatype % Orientation information %-------------------------------------------------------------------------- % Axial Analyze voxel co-ordinate system: % x increases right to left % y increases posterior to anterior % z increases inferior to superior % DICOM patient co-ordinate system: % x increases right to left % y increases anterior to posterior % z increases inferior to superior % T&T co-ordinate system: % x increases left to right % y increases posterior to anterior % z increases inferior to superior analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)+1) 0]'; 0 0 0 1]; % Flip voxels in y patient_to_tal = diag([-1 -1 1 1]); % Flip mm coords in x and y directions shift_vx = [eye(4,3) [.5; .5; 0; 1]]; orient = reshape(get_numaris4_numval(privdat,... 'ImageOrientationPatient'),[3 2]); ps = get_numaris4_numval(privdat,'PixelSpacing'); if nc*nr == 1 % Single Voxel Spectroscopy (based on the following information from SIEMENS) %---------------------------------------------------------------------- % NOTE: Internally the position vector of the CSI matrix shows to the % outer border of the first voxel. Therefore the position vector has to % be corrected. (Note: The convention of Siemens spectroscopy raw data % is in contrast to the DICOM standard where the position vector points % to the center of the first voxel.) %---------------------------------------------------------------------- % SIEMENS decides which definition to use based on the contents of the % 'PixelSpacing' internal header field. If it has non-zero values, % assume DICOM convention. If any value is zero, assume SIEMENS % internal convention for this direction. % Note that in SIEMENS code, there is a shift when PixelSpacing is % zero. Here, the shift seems to be necessary when PixelSpacing is % non-zero. This may indicate more fundamental problems with % orientation decoding. if ps(1) == 0 % row ps(1) = get_numaris4_numval(privdat,... 'VoiPhaseFoV'); shift_vx(1,4) = 0; end if ps(2) == 0 % col ps(2) = get_numaris4_numval(privdat,... 'VoiReadoutFoV'); shift_vx(2,4) = 0; end end pos = get_numaris4_numval(privdat,'ImagePositionPatient'); % for some reason, pixel spacing needs to be swapped R = [orient*diag(ps([2 1])); 0 0]; x1 = [1;1;1;1]; y1 = [pos; 1]; if length(hdr)>1 error('spm_dicom_convert:spectroscopy',... 'Don''t know how to handle multislice spectroscopy data.'); else orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end z = get_numaris4_numval(privdat,... 'VoiThickness'); if isempty(z) z = get_numaris4_numval(privdat,... 'SliceThickness'); end if isempty(z) warning('spm_dicom_convert:spectroscopy',... 'Can not determine voxel thickness.'); z = 1; end x2 = [0;0;1;0]; y2 = [orient*[0;0;z];0]; end dicom_to_patient = [y1 y2 R]/[x1 x2 eye(4,2)]; mat = patient_to_tal*dicom_to_patient*shift_vx*analyze_to_dicom; % Possibly useful information %-------------------------------------------------------------------------- if checkfields(hdr{1},'AcquisitionTime','MagneticFieldStrength','MRAcquisitionType',... 'ScanningSequence','RepetitionTime','EchoTime','FlipAngle',... 'AcquisitionDate') tim = datevec(hdr{1}.AcquisitionTime/(24*60*60)); descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg %s %d:%d:%.5g',... hdr{1}.MagneticFieldStrength, hdr{1}.MRAcquisitionType,... deblank(hdr{1}.ScanningSequence),... hdr{1}.RepetitionTime,hdr{1}.EchoTime,hdr{1}.FlipAngle,... datestr(hdr{1}.AcquisitionDate),tim(4),tim(5),tim(6)); else descrip = hdr{1}.Modality; end if ~true % LEFT-HANDED STORAGE mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; end % Write the image volume %-------------------------------------------------------------------------- N = nifti; pinfo = [1 0]; if isfield(hdr{1},'RescaleSlope'), pinfo(1) = hdr{1}.RescaleSlope; end; if isfield(hdr{1},'RescaleIntercept'), pinfo(2) = hdr{1}.RescaleIntercept; end; N.dat = file_array(fname,dim,dt,0,pinfo(1),pinfo(2)); N.mat = mat; N.mat0 = mat; N.mat_intent = 'Scanner'; N.mat0_intent = 'Scanner'; N.descrip = descrip; % Store LCMODEL control/raw info in N.extras N.extras = struct('MagneticFieldStrength',... get_numaris4_numval(privdat,'MagneticFieldStrength'),... 'TransmitterReferenceAmplitude',... get_numaris4_numval(privdat,'TransmitterReferenceAmplitude'),... 'ImagingFrequency',... get_numaris4_numval(privdat,'ImagingFrequency'),... 'EchoTime',... get_numaris4_numval(privdat,'EchoTime'),... 'RealDwellTime',... get_numaris4_numval(privdat,'RealDwellTime')); create(N); if meta N = spm_dicom_metadata(N,hdr{1}); end % Read data, swap dimensions data = permute(reshape(read_spect_data(hdr{1},ntp),dim([4 5 1 2 3])), ... [3 4 5 1 2]); % plane = fliplr(plane); N.dat(:,:,:,:,:) = data; %========================================================================== % function [images,guff] = select_tomographic_images(hdr) %========================================================================== function [images,guff] = select_tomographic_images(hdr) images = {}; guff = {}; for i=1:length(hdr) if ~checkfields(hdr{i},'Modality') || ... ~(strcmp(hdr{i}.Modality,'MR') || ... strcmp(hdr{i}.Modality,'PT') || ... strcmp(hdr{i}.Modality,'NM') || ... strcmp(hdr{i}.Modality,'CT')) if checkfields(hdr{i},'Modality') fprintf('File "%s" can not be converted because it is of type "%s", which is not MRI, CT, NM or PET.\n', hdr{i}.Filename, hdr{i}.Modality); else fprintf('File "%s" can not be converted because it does not encode an image.\n', hdr{i}.Filename); end guff = [guff(:)',hdr(i)]; elseif ~checkfields(hdr{i},'StartOfPixelData','SamplesPerPixel',... 'Rows','Columns','BitsAllocated','BitsStored','HighBit','PixelRepresentation') fprintf('Cant find "Image Pixel" information for "%s".\n',hdr{i}.Filename); guff = [guff(:)',hdr(i)]; elseif ~(checkfields(hdr{i},'PixelSpacing','ImagePositionPatient','ImageOrientationPatient') ... || isfield(hdr{i},'CSANonImageHeaderInfoVA') || isfield(hdr{i},'CSANonImageHeaderInfoVB')) if isfield(hdr{i},'SharedFunctionalGroupsSequence') || isfield(hdr{i},'PerFrameFunctionalGroupsSequence'), fprintf(['\n"%s" appears to be multi-frame DICOM.\n'... 'Converting these data is still experimental and has only been tested on a very small number of\n'... 'multiframe DICOM files. Feedback about problems would be appreciated - particularly if you can\n'... 'give us examples of problematic data (providing there are no subject confidentiality issues).\n\n'], hdr{i}.Filename); images = [images(:)',hdr(i)]; else fprintf('Can''t find "Image Plane" information for "%s".\n',hdr{i}.Filename); guff = [guff(:)',hdr(i)]; end elseif ~checkfields(hdr{i},'SeriesNumber','AcquisitionNumber','InstanceNumber') %disp(['Cant find suitable filename info for "' hdr{i}.Filename '".']); if ~isfield(hdr{i},'SeriesNumber') fprintf('Setting SeriesNumber to 1.\n'); hdr{i}.SeriesNumber = 1; images = [images(:)',hdr(i)]; end if ~isfield(hdr{i},'AcquisitionNumber') if isfield(hdr{i},'Manufacturer') && ~isempty(strfind(upper(hdr{1}.Manufacturer), 'PHILIPS')) % Philips oddity if isfield(hdr{i},'InstanceNumber') hdr{i}.AcquisitionNumber = hdr{i}.InstanceNumber; else fprintf('Setting AcquisitionNumber to 1.\n'); hdr{i}.AcquisitionNumber = 1; end else fprintf('Setting AcquisitionNumber to 1.\n'); hdr{i}.AcquisitionNumber = 1; end images = [images(:)',hdr(i)]; end if ~isfield(hdr{i},'InstanceNumber') fprintf('Setting InstanceNumber to 1.\n'); hdr{i}.InstanceNumber = 1; images = [images(:)',hdr(i)]; end %elseif isfield(hdr{i},'Private_2001_105f'), % % This field corresponds to: > Stack Sequence 2001,105F SQ VNAP, COPY % % http://www.medical.philips.com/main/company/connectivity/mri/index.html % % No documentation about this private field is yet available. % disp('Cant yet convert Philips Intera DICOM.'); % guff = {guff{:},hdr{i}}; else images = [images(:)',hdr(i)]; end end %========================================================================== % function [multiframe,other] = select_multiframe(hdr) %========================================================================== function [multiframe,other] = select_multiframe(hdr) multiframe = {}; other = {}; for i=1:length(hdr) if isfield(hdr{i},'SharedFunctionalGroupsSequence') || isfield(hdr{i},'PerFrameFunctionalGroupsSequence'), multiframe = [multiframe(:)',hdr(i)]; else other = [other(:)',hdr(i)]; end end %========================================================================== % function [mosaic,standard] = select_mosaic_images(hdr) %========================================================================== function [mosaic,standard] = select_mosaic_images(hdr) mosaic = {}; standard = {}; for i=1:length(hdr) if ~checkfields(hdr{i},'ImageType','CSAImageHeaderInfo') ||... isfield(hdr{i}.CSAImageHeaderInfo,'junk') ||... isempty(read_AcquisitionMatrixText(hdr{i})) ||... isempty(read_NumberOfImagesInMosaic(hdr{i})) ||... read_NumberOfImagesInMosaic(hdr{i}) == 0 % NumberOfImagesInMosaic seems to be set to zero for pseudo images % containing e.g. online-fMRI design matrices, don't treat them as % mosaics standard = [standard, hdr(i)]; else mosaic = [mosaic, hdr(i)]; end end %========================================================================== % function [spect,images] = select_spectroscopy_images(hdr) %========================================================================== function [spect,images] = select_spectroscopy_images(hdr) spectsel = false(1,numel(hdr)); for i=1:numel(hdr) if isfield(hdr{i},'SOPClassUID') spectsel(i) = strcmp(hdr{i}.SOPClassUID,'1.3.12.2.1107.5.9.1'); end end spect = hdr(spectsel); images = hdr(~spectsel); %========================================================================== % function [standard, guff] = select_last_guff(standard, guff) %========================================================================== function [standard, guff] = select_last_guff(standard, guff) % See https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=spm;5b69d495.1108 i = find(cellfun(@(x) ~isfield(x,'ImageOrientationPatient'),standard)); guff = [guff, standard(i)]; standard(i) = []; %========================================================================== % function ok = checkfields(hdr,varargin) %========================================================================== function ok = checkfields(hdr,varargin) ok = 1; for i=1:(nargin-1) if ~isfield(hdr,varargin{i}) ok = 0; break; end end %========================================================================== % function clean = strip_unwanted(dirty) %========================================================================== function clean = strip_unwanted(dirty) msk = (dirty>='a'&dirty<='z') | (dirty>='A'&dirty<='Z') |... (dirty>='0'&dirty<='9') | dirty=='_'; clean = dirty(msk); %========================================================================== % function img = read_image_data(hdr) %========================================================================== function img = read_image_data(hdr) img = []; if hdr.SamplesPerPixel ~= 1, warning('spm:dicom','%s: SamplesPerPixel = %d - cant be an MRI.', hdr.Filename, hdr.SamplesPerPixel); return; end; prec = ['ubit' num2str(hdr.BitsAllocated) '=>' 'uint32']; if isfield(hdr,'TransferSyntaxUID') && strcmp(hdr.TransferSyntaxUID,'1.2.840.10008.1.2.2') && strcmp(hdr.VROfPixelData,'OW'), fp = fopen(hdr.Filename,'r','ieee-be'); else fp = fopen(hdr.Filename,'r','ieee-le'); end; if fp==-1, warning('spm:dicom','%s: Cant open file.', hdr.Filename); return; end; if isfield(hdr,'NumberOfFrames'), NFrames = hdr.NumberOfFrames; else NFrames = 1; end if isfield(hdr,'TransferSyntaxUID') switch(hdr.TransferSyntaxUID) case {'1.2.840.10008.1.2.4.50','1.2.840.10008.1.2.4.51',... % 8 bit JPEG & 12 bit JPEG '1.2.840.10008.1.2.4.57','1.2.840.10008.1.2.4.70',... % lossless NH JPEG & lossless NH, 1st order '1.2.840.10008.1.2.4.80','1.2.840.10008.1.2.4.81',... % lossless JPEG-LS & near lossless JPEG-LS '1.2.840.10008.1.2.4.90','1.2.840.10008.1.2.4.91',... % lossless JPEG 2000 & possibly lossy JPEG 2000, Part 1 '1.2.840.10008.1.2.4.92','1.2.840.10008.1.2.4.93' ... % lossless JPEG 2000 & possibly lossy JPEG 2000, Part 2 }, % try to read PixelData as JPEG image fseek(fp,hdr.StartOfPixelData,'bof'); fread(fp,2,'uint16'); % uint16 encoding 65534/57344 (Item) offset = double(fread(fp,1,'uint32')); % followed by 4 0 0 0 fread(fp,2,'uint16'); % uint16 encoding 65534/57344 (Item) fread(fp,offset); sz = double(fread(fp,1,'*uint32')); img = fread(fp,sz,'*uint8'); % Next uint16 seem to encode 65534/57565 (SequenceDelimitationItem), followed by 0 0 % save PixelData into temp file - imread and its subroutines can only % read from file, not from memory tfile = tempname; tfp = fopen(tfile,'w+'); fwrite(tfp,img,'uint8'); fclose(tfp); % read decompressed data, transpose to match DICOM row/column order img = uint32(imread(tfile)'); delete(tfile); case {'1.2.840.10008.1.2.4.94' ,'1.2.840.10008.1.2.4.95' ,... % JPIP References & JPIP Referenced Deflate Transfer '1.2.840.10008.1.2.4.100','1.2.840.10008.1.2.4.101',... % MPEG2 MP@ML & MPEG2 MP@HL '1.2.840.10008.1.2.4.102', ... % MPEG-4 AVC/H.264 High Profile and BD-compatible } warning('spm:dicom',[hdr.Filename ': cant deal with JPIP/MPEG data (' hdr.TransferSyntaxUID ')']); otherwise fseek(fp,hdr.StartOfPixelData,'bof'); img = fread(fp,hdr.Rows*hdr.Columns*NFrames,prec); end else fseek(fp,hdr.StartOfPixelData,'bof'); img = fread(fp,hdr.Rows*hdr.Columns*NFrames,prec); end fclose(fp); if numel(img)~=hdr.Rows*hdr.Columns*NFrames, error([hdr.Filename ': cant read whole image']); end; img = bitshift(img,hdr.BitsStored-hdr.HighBit-1); if hdr.PixelRepresentation, % Signed data - done this way because bitshift only % works with signed data. Negative values are stored % as 2s complement. neg = logical(bitshift(bitand(img,uint32(2^hdr.HighBit)),-hdr.HighBit)); msk = (2^hdr.HighBit - 1); img = double(bitand(img,msk)); img(neg) = img(neg)-2^(hdr.HighBit); else % Unsigned data msk = (2^(hdr.HighBit+1) - 1); img = double(bitand(img,msk)); end; img = reshape(img,[hdr.Columns,hdr.Rows,NFrames]); %========================================================================== % function img = read_spect_data(hdr,privdat) %========================================================================== function img = read_spect_data(hdr,ntp) % Data is stored as complex float32 values, timepoint by timepoint, voxel % by voxel. Reshaping is done in write_spectroscopy_volume. if ntp*2*4 ~= hdr.SizeOfCSAData warning('spm:dicom', [hdr.Filename,': Data size mismatch.']); end fp = fopen(hdr.Filename,'r','ieee-le'); fseek(fp,hdr.StartOfCSAData,'bof'); img = fread(fp,2*ntp,'float32'); fclose(fp); %========================================================================== % function nrm = read_SliceNormalVector(hdr) %========================================================================== function nrm = read_SliceNormalVector(hdr) str = hdr.CSAImageHeaderInfo; val = get_numaris4_val(str,'SliceNormalVector'); for i=1:3, nrm(i,1) = sscanf(val(i,:),'%g'); end %========================================================================== % function n = read_NumberOfImagesInMosaic(hdr) %========================================================================== function n = read_NumberOfImagesInMosaic(hdr) str = hdr.CSAImageHeaderInfo; val = get_numaris4_val(str,'NumberOfImagesInMosaic'); n = sscanf(val','%d'); if isempty(n), n=[]; end %========================================================================== % function dim = read_AcquisitionMatrixText(hdr) %========================================================================== function dim = read_AcquisitionMatrixText(hdr) str = hdr.CSAImageHeaderInfo; val = get_numaris4_val(str,'AcquisitionMatrixText'); dim = sscanf(val','%d*%d')'; if length(dim)==1 dim = sscanf(val','%dp*%d')'; end if isempty(dim), dim=[]; end %========================================================================== % function val = get_numaris4_val(str,name) %========================================================================== function val = get_numaris4_val(str,name) name = deblank(name); val = {}; for i=1:length(str) if strcmp(deblank(str(i).name),name) for j=1:str(i).nitems if str(i).item(j).xx(1) val = [val {str(i).item(j).val}]; end end break; end end val = strvcat(val{:}); %========================================================================== % function val = get_numaris4_numval(str,name) %========================================================================== function val = get_numaris4_numval(str,name) val1 = get_numaris4_val(str,name); val = zeros(size(val1,1),1); for k = 1:size(val1,1) val(k)=str2num(val1(k,:)); end %========================================================================== % function fname = getfilelocation(hdr,root_dir,prefix,format,out_dir) %========================================================================== function fname = getfilelocation(hdr,root_dir,prefix,format,out_dir) if nargin < 3 prefix = 'f'; end if strncmp(root_dir,'ice',3) root_dir = root_dir(4:end); imtype = textscan(hdr.ImageType,'%s','delimiter','\\'); try imtype = imtype{1}{3}; catch imtype = ''; end prefix = [prefix imtype get_numaris4_val(hdr.CSAImageHeaderInfo,'ICE_Dims')]; end if isfield(hdr,'PatientID'), PatientID = deblank(hdr.PatientID); else PatientID = 'anon'; end if isfield(hdr,'EchoNumbers'), EchoNumbers = hdr.EchoNumbers; else EchoNumbers = 0; end if isfield(hdr,'SeriesNumber'), SeriesNumber = hdr.SeriesNumber; else SeriesNumber = 0; end if isfield(hdr,'AcquisitionNumber'), AcquisitionNumber = hdr.AcquisitionNumber; else AcquisitionNumber = 0; end if isfield(hdr,'InstanceNumber'), InstanceNumber = hdr.InstanceNumber; else InstanceNumber = 0; end ImTyp = ''; if isfield(hdr,'GE_ImageType') if numel(hdr.GE_ImageType)==1 switch hdr.GE_ImageType case 1 ImTyp = '-Phase'; case 2 ImTyp = '-Real'; case 3 ImTyp = '-Imag'; end end end % To use ICE Dims systematically in file names in order to avoid % overwriting uncombined coil images, which have identical file name % otherwise) try ICE_Dims = get_numaris4_val(hdr.CSAImageHeaderInfo,'ICE_Dims'); % extract ICE dims as an array of numbers (replace 'X' which is for % combined images by '-1' first): CHA = sscanf(strrep(ICE_Dims,'X','-1'), '%i_%i_%i_%i_%i_%i_%i_%i_%i')'; if CHA(1)>0 CHA = sprintf('%.3d',CHA(1)); else CHA = ''; end catch CHA = ''; end if strcmp(root_dir, 'flat') % Standard SPM file conversion %---------------------------------------------------------------------- if checkfields(hdr,'SeriesNumber','AcquisitionNumber') if checkfields(hdr,'EchoNumbers') if ~isempty(CHA) fname = sprintf('%s%s-%.4d-%.5d-%.6d-%.2d-%s%s.%s', prefix, strip_unwanted(PatientID),... SeriesNumber, AcquisitionNumber, InstanceNumber, EchoNumbers, CHA, ImTyp, format); else fname = sprintf('%s%s-%.4d-%.5d-%.6d-%.2d%s.%s', prefix, strip_unwanted(PatientID),... SeriesNumber, AcquisitionNumber, InstanceNumber, EchoNumbers, ImTyp, format); end else if ~isempty(CHA) fname = sprintf('%s%s-%.4d-%.5d-%.6d-%s%s.%s', prefix, strip_unwanted(PatientID),... SeriesNumber, AcquisitionNumber, InstanceNumber, CHA, ImTyp, format); else fname = sprintf('%s%s-%.4d-%.5d-%.6d%s.%s', prefix, strip_unwanted(PatientID),... SeriesNumber, AcquisitionNumber, InstanceNumber, ImTyp, format); end end else fname = sprintf('%s%s-%.6d%s.%s',prefix, ... strip_unwanted(PatientID), InstanceNumber, ImTyp, format); end fname = fullfile(out_dir,fname); return; end % more fancy stuff - sort images into subdirectories if isfield(hdr,'StudyTime') m = sprintf('%02d', floor(rem(hdr.StudyTime/60,60))); h = sprintf('%02d', floor(hdr.StudyTime/3600)); else m = '00'; h = '00'; end if isfield(hdr,'AcquisitionTime'), AcquisitionTime = hdr.AcquisitionTime; else AcquisitionTime = 100; end; if isfield(hdr,'StudyDate'), StudyDate = hdr.StudyDate; else StudyDate = 100; end; % Obscure Easter Egg if isfield(hdr,'PatientsName'), PatientsName = deblank(hdr.PatientsName); else PatientsName = 'anon'; end if isfield(hdr,'SeriesDescription'), SeriesDescription = deblank(hdr.SeriesDescription); else SeriesDescription = 'unknown'; end if isfield(hdr,'ProtocolName'), ProtocolName = deblank(hdr.ProtocolName); else if isfield(hdr,'SequenceName') ProtocolName = deblank(hdr.SequenceName); else ProtocolName='unknown'; end end studydate = sprintf('%s_%s-%s', datestr(StudyDate,'yyyy-mm-dd'), h,m); switch root_dir case {'date_time','series'} id = studydate; case {'patid', 'patid_date', 'patname'}, id = strip_unwanted(PatientID); end serdes = strrep(strip_unwanted(SeriesDescription), strip_unwanted(ProtocolName),''); protname = sprintf('%s%s_%.4d',strip_unwanted(ProtocolName), serdes, SeriesNumber); switch root_dir case 'date_time', dname = fullfile(out_dir, id, protname); case 'patid', dname = fullfile(out_dir, id, protname); case 'patid_date', dname = fullfile(out_dir, id, studydate, protname); case 'patname', dname = fullfile(out_dir, strip_unwanted(PatientsName), id, protname); case 'series', dname = fullfile(out_dir, protname); otherwise error('unknown file root specification'); end if ~exist(dname,'dir') mkdir_rec(dname); end % some non-product sequences on SIEMENS scanners seem to have problems % with image numbering in MOSAICs - doublettes, unreliable ordering % etc. To distinguish, always include Acquisition time in image name sa = sprintf('%02d', floor(rem(AcquisitionTime,60))); ma = sprintf('%02d', floor(rem(AcquisitionTime/60,60))); ha = sprintf('%02d', floor(AcquisitionTime/3600)); fname = sprintf('%s%s-%s%s%s-%.5d-%.5d-%d%s.%s', prefix, id, ha, ma, sa, ... AcquisitionNumber, InstanceNumber, EchoNumbers, ImTyp, format); fname = fullfile(dname, fname); %========================================================================== % function suc = mkdir_rec(str) %========================================================================== function suc = mkdir_rec(str) % works on full pathnames only if str(end) ~= filesep, str = [str filesep]; end pos = strfind(str,filesep); suc = zeros(1,length(pos)); for g=2:length(pos) if ~exist(str(1:pos(g)-1),'dir') suc(g) = mkdir(str(1:pos(g-1)-1),str(pos(g-1)+1:pos(g)-1)); end end %========================================================================== % function ret = read_ascconv(hdr) %========================================================================== function ret = read_ascconv(hdr) % In SIEMENS data, there is an ASCII text section with % additional information items. This section starts with a code % ### ASCCONV BEGIN ### % and ends with % ### ASCCONV END ### % It is read by spm_dicom_headers into an entry 'MrProtocol' in % CSASeriesHeaderInfo or into an entry 'MrPhoenixProtocol' in % CSAMiscProtocolHeaderInfoVA or CSAMiscProtocolHeaderVB. % The additional items are assignments in C syntax, here they are just % translated according to % [] -> () % " -> ' % 0xX -> hex2dec('X') % and collected in a struct. % In addition, there seems to be "__attribute__" meta information for some % items. All "field names" starting with "_" are currently silently ignored. ret=struct; % get ascconv data if isfield(hdr, 'CSAMiscProtocolHeaderInfoVA') X = get_numaris4_val(hdr.CSAMiscProtocolHeaderInfoVA,'MrProtocol'); elseif isfield(hdr, 'CSAMiscProtocolHeaderInfoVB') X = get_numaris4_val(hdr.CSAMiscProtocolHeaderInfoVB,'MrPhoenixProtocol'); elseif isfield(hdr, 'CSASeriesHeaderInfo') X = get_numaris4_val(hdr.CSASeriesHeaderInfo,'MrProtocol'); else return; end X = regexprep(X,'^.*### ASCCONV BEGIN [^#]*###(.*)### ASCCONV END ###.*$','$1'); if ~isempty(X) tokens = textscan(char(X),'%s', ... 'delimiter',char(10)); tokens{1}=regexprep(tokens{1},{'\[([0-9]*)\]','"(.*)"','^([^"]*)0x([0-9a-fA-F]*)','#.*','^.*\._.*$'},{'($1+1)','''$1''','$1hex2dec(''$2'')','',''}); % If everything would evaluate correctly, we could use % eval(sprintf('ret.%s;\n',tokens{1}{:})); for k = 1:numel(tokens{1}) if ~isempty(tokens{1}{k}) try [tlhrh, un] = regexp(tokens{1}{k}, '(?:=)+', 'split', 'match'); [tlh, un] = regexp(tlhrh{1}, '(?:\.)+', 'split', 'match'); tlh = cellfun(@genvarname, tlh, 'UniformOutput',false); tlh = sprintf('.%s', tlh{:}); eval(sprintf('ret%s = %s;', tlh, tlhrh{2})); catch disp(['AscConv: Error evaluating ''ret.' tokens{1}{k} ''';']); end end end end %========================================================================== % function dt = determine_datatype(hdr) %========================================================================== function dt = determine_datatype(hdr) % Determine what datatype to use for NIfTI images be = spm_platform('bigend'); if hdr.BitsStored>16 if hdr.PixelRepresentation dt = [spm_type( 'int32') be]; else dt = [spm_type('uint32') be]; end else if hdr.PixelRepresentation dt = [spm_type( 'int16') be]; else dt = [spm_type('uint16') be]; end end %========================================================================== % function fspe = convert_multiframes(hdr,root_dir,format,out_dir,meta) %========================================================================== function fspe = convert_multiframes(hdr,root_dir,format,out_dir,meta) fspe = {}; dict = load('spm_dicom_dict.mat'); for i=1:numel(hdr) out = convert_multiframe(hdr{i}, dict, root_dir, format,out_dir,meta); fspe = [fspe(:); out(:)]; end %========================================================================== % function out = convert_multiframe(H, dict, root_dir, format,out_dir,meta) %========================================================================== function out = convert_multiframe(H, dict, root_dir, format,out_dir,meta) out = {}; diminfo = read_DimOrg(H,dict); dat = read_FGS(H,diminfo); N = numel(dat); fname0 = getfilelocation(H, root_dir,'MF',format,out_dir); [pth,nam,ext0] = fileparts(fname0); fname0 = fullfile(pth,nam); if ~isfield(dat,'ImageOrientationPatient') || ~isfield(dat,'PixelSpacing') || ~isfield(dat,'ImagePositionPatient'), fprintf('"%s" does not seem to have positional information.\n', H.Filename); return; end %if isfield(dat,'DimensionIndexValues') % disp(cat(1,dat.DimensionIndexValues)) %end % Read the atual image data volume = read_image_data(H); % Sort into unique files according to image orientations etc stuff = [cat(2,dat.ImageOrientationPatient); cat(2,dat.PixelSpacing)]'; if isfield(dat,'StackID') stuff = [stuff double(cat(1,dat.StackID))]; end ds = find(any(diff(stuff,1,1)~=0,2)); ord = sparse([],[],[],N,numel(ds)+1); start = 1; for i=1:numel(ds), ord(start:ds(i),i) = 1; start = ds(i)+1; end ord(start:size(stuff,1),numel(ds)+1) = 1; for n=1:size(ord,2), % Identify all slices that should go into the same output file %ind = find(all(bsxfun(@eq,stuff,u(n,:)),2)); ind = find(ord(:,n)); this = dat(ind); if size(ord,2)>1, fname = sprintf('%s_%-.3d',fname0,n); else fname = fname0; end % Orientation information %-------------------------------------------------------------------------- % Axial Analyze voxel co-ordinate system: % x increases right to left % y increases posterior to anterior % z increases inferior to superior % DICOM patient co-ordinate system: % x increases right to left % y increases anterior to posterior % z increases inferior to superior % T&T co-ordinate system: % x increases left to right % y increases posterior to anterior % z increases inferior to superior if any(sum(diff(cat(2,this.ImageOrientationPatient),1,2).^2,1)>0.001), fprintf('"%s" contains irregularly oriented slices.\n', H.Filename); % break % Option to skip writing out the NIfTI image end ImageOrientationPatient = this(1).ImageOrientationPatient(:); if any(sum(diff(cat(2,this.PixelSpacing),1,2).^2,1)>0.001), fprintf('"%s" contains slices with irregularly spaced pixels.\n', H.Filename); % break % Option to skip writing out the NIfTI image end PixelSpacing = this(1).PixelSpacing(:); R = [reshape(ImageOrientationPatient,3,2)*diag(PixelSpacing); 0 0]; % Determine the order of the slices by sorting their positions according to where they project % on a vector orthogonal to the image plane orient = reshape(this(1).ImageOrientationPatient,[3 2]); orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end Positions = cat(2,this.ImagePositionPatient)'; [proj,slice_order,inv_slice_order] = unique(round(Positions*orient(:,3)*100)/100); if any(abs(diff(diff(proj),1,1))>0.025), problem1 = true; else problem1 = false; end inv_time_order = ones(numel(this),1); for i=1:numel(slice_order), ind = find(inv_slice_order == i); if numel(ind)>1, if isfield(this,'TemporalPositionIndex'), sort_on = cat(2,this(ind).TemporalPositionIndex)'; else sort_on = ind; end [unused,tsort] = sort(sort_on); inv_time_order(ind) = tsort'; end end % Image dimensions %-------------------------------------------------------------------------- nc = H.Columns; nr = H.Rows; dim = [nc nr 1 1]; dim(3) = max(inv_slice_order); dim(4) = max(inv_time_order); problem2 = false; for i=1:max(inv_time_order) if sum(inv_time_order==i)~=dim(3) problem2 = true; end end if problem1 || problem2 fname = [fname '-problem']; end if problem1 fprintf('"%s" contains irregularly spaced slices with the following spacings:\n', H.Filename); spaces = unique(round(diff(proj)*100)/100); fprintf(' %g', spaces); fprintf('\nSee %s%s\n\n',fname,ext0); % break % Option to skip writing out the NIfTI image end if problem2 fprintf('"%s" contains missing slices.\nSee %s%s\n\n', H.Filename,fname,ext0); % break % Option to skip writing out the NIfTI image end if dim(3)>1 y1 = [this(slice_order( 1)).ImagePositionPatient(:); 1]; y2 = [this(slice_order(end)).ImagePositionPatient(:); 1]; x2 = [1;1;dim(3); 1]; else orient = reshape(ImageOrientationPatient,[3 2]); orient(:,3) = null(orient'); if det(orient)<0, orient(:,3) = -orient(:,3); end if isfield(H,'SliceThickness'), z = H.SliceThickness; else z = 1; end y1 = [this(1).ImagePositionPatient(:); 1]; y2 = [orient*[0;0;z];0]; x2 = [0;0;1;0]; end x1 = [1;1;1;1]; dicom_to_patient = [y1 y2 R]/[x1 x2 eye(4,2)]; analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)+1) 0]'; 0 0 0 1]; % Flip voxels in y patient_to_tal = diag([-1 -1 1 1]); % Flip mm coords in x and y directions mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; flip_lr = det(mat(1:3,1:3))>0; if flip_lr, mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; end % Possibly useful information for descrip field %-------------------------------------------------------------------------- if isfield(H,'AcquisitionTime') tim = datevec(H.AcquisitionTime/(24*60*60)); elseif isfield(H,'StudyTime') tim = datevec(H.StudyTime/(24*60*60)); elseif isfield(H,'ContentTime') tim = datevec(H.ContentTime/(24*60*60)); else tim = ''; end if ~isempty(tim), tim = sprintf(' %d:%d:%.5g', tim(4),tim(5),tim(6)); end if isfield(H,'AcquisitionDate') day = datestr(H.AcquisitionDate); elseif isfield(H,'StudyDate') day = datestr(H.StudyDate); elseif isfield(H,'ContentDate') day = datestr(H.ContentDate); else day = ''; end when = [day tim]; if checkfields(H,'MagneticFieldStrength','MRAcquisitionType',... 'ScanningSequence','RepetitionTime','EchoTime','FlipAngle') if isfield(H,'ScanOptions') ScanOptions = H.ScanOptions; else ScanOptions = 'no'; end modality = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg/SO=%s',... H.MagneticFieldStrength, H.MRAcquisitionType,... deblank(H.ScanningSequence),... H.RepetitionTime,H.EchoTime,H.FlipAngle,... ScanOptions); else modality = [H.Modality ' ' H.ImageType]; end descrip = [modality ' ' when]; % Sort out datatype, as well as any scalefactors or intercepts %-------------------------------------------------------------------------- pinfos = [ones(length(this),1) zeros(length(this),1)]; for i=1:length(this) if isfield(this(i),'RescaleSlope'), pinfos(i,1) = this(i).RescaleSlope; end if isfield(this(i),'RescaleIntercept'), pinfos(i,2) = this(i).RescaleIntercept; end end if ~any(any(diff(pinfos,1))) % Same slopes and intercepts for all slices dt = determine_datatype(H); pinfo = pinfos(1,:); else % Variable slopes and intercept (maybe PET/SPECT) mx = max(volume(:)); mn = min(volume(:)); % Slope and Intercept % 32767*pinfo(1) + pinfo(2) = mx % -32768*pinfo(1) + pinfo(2) = mn % pinfo = ([32767 1; -32768 1]\[mx; mn])'; % Slope only dt = 'int16-be'; pinfo = [max(mx/32767,-mn/32768) 0]; % Ensure random numbers are reproducible (see later) % when intensities are dithered to prevent aliasing effects. rand('state',0); end % Create the header %-------------------------------------------------------------------------- Nii = nifti; fname = sprintf('%s%s',fname,ext0); Nii.dat = file_array(fname,dim,dt,0,pinfo(1),pinfo(2)); Nii.mat = mat; Nii.mat0 = mat; Nii.mat_intent = 'Scanner'; Nii.mat0_intent = 'Scanner'; Nii.descrip = descrip; create(Nii); if meta Nii = spm_dicom_metadata(Nii,H); end Nii.dat(end,end,end,end,end) = 0; % Write the image volume %-------------------------------------------------------------------------- spm_progress_bar('Init',length(this),['Writing ' fname], 'Planes written'); for i=1:length(this) plane = volume(:,:,this(i).number); if any(any(diff(pinfos,1))) % This is to prevent aliasing effects in any subsequent histograms % of the data (eg for mutual information coregistration). % It's a bit inelegant, but probably necessary for when slices are % individually rescaled. plane = double(plane) + rand(size(plane)) - 0.5; end if pinfos(i,1)~=1, plane = plane*pinfos(i,1); end if pinfos(i,2)~=0, plane = plane+pinfos(i,2); end plane = fliplr(plane); if flip_lr, plane = flipud(plane); end z = inv_slice_order(i); t = inv_time_order(i); Nii.dat(:,:,z,t) = plane; spm_progress_bar('Set',i); end out = [out; {Nii.dat.fname}]; spm_progress_bar('Clear'); end %========================================================================== % function dim = read_DimOrg(H,dict) %========================================================================== function dim = read_DimOrg(H,dict) % This is only sometimes used. dim = struct('DimensionIndexPointer',{},'FunctionalGroupPointer',{}); if isfield(H,'DimensionIndexSequence'), for i=1:numel(H.DimensionIndexSequence), DIP = H.DimensionIndexSequence{i}.DimensionIndexPointer; ind = find(dict.group==DIP(1) & dict.element == DIP(2)); if numel(ind)==1, dim(i).DimensionIndexPointer = dict.values(ind).name; else if rem(DIP(1),2) dim(i).DimensionIndexPointer = sprintf('Private_%.4x_%.4x',DIP); else dim(i).DimensionIndexPointer = sprintf('Tag_%.4x_%.4x',DIP); end end FGP = H.DimensionIndexSequence{i}.FunctionalGroupPointer; ind = find(dict.group==FGP(1) & dict.element == FGP(2)); if numel(ind)==1, dim(i).FunctionalGroupPointer = dict.values(ind).name; else if rem(DIP(1),2) dim(i).FunctionalGroupPointer = sprintf('Private_%.4x_%.4x',DIP); else dim(i).FunctionalGroupPointer = sprintf('Tag_%.4x_%.4x',DIP); end end end end return %========================================================================== % dat = read_FGS(H,dim) %========================================================================== function dat = read_FGS(H,dim) dat = struct; if isfield(H,'PerFrameFunctionalGroupsSequence'), % For documentation, see C.7.6.16 "Multi-frame Functional Groups Module" % of DICOM Standard PS 3.3 - 2003, Page 261. % Multiframe DICOM N = numel(H.PerFrameFunctionalGroupsSequence); if isfield(H,'NumberOfFrames') && H.NumberOfFrames ~= N, fprintf('"%s" has incompatible numbers of frames.', dat.Filename); end macros = {'PixelMeasuresSequence',{'PixelSpacing','SliceThickness'} 'FrameContentSequence',{'Frame Acquisition Number','FrameReferenceDatetime',... 'FrameAcquisitionDatetime','FrameAcquisitionDuration',... 'CardiacCyclePosition','RespiratoryCyclePosition',... 'DimensionIndexValues','TemporalPositionIndex','Stack ID',... 'InStackPositionNumber','FrameComments'} 'PlanePositionSequence',{'ImagePositionPatient'} 'PlaneOrientationSequence',{'ImageOrientationPatient'} 'ReferencedImageSequence',{'ReferencedSOPClassUID','ReferencedSOPInstanceUID',... 'ReferencedFrameNumber','PurposeOfReferenceCode'} 'PixelValueTransformationSequence',{'RescaleIntercept','RescaleSlope','RescaleType'}}; if isfield(H,'SharedFunctionalGroupsSequence') for d=1:numel(dim), if isfield(H.SharedFunctionalGroupsSequence{1},dim(d).FunctionalGroupPointer) && ... isfield(H.SharedFunctionalGroupsSequence{1}.(dim(d).FunctionalGroupPointer){1},dim(d).DimensionIndexPointer), for n=N:-1:1 dat(n).(dim(d).DimensionIndexPointer) = H.SharedFunctionalGroupsSequence{1}.(dim(d).FunctionalGroupPointer){1}.(dim(d).DimensionIndexPointer); end end end for k1=1:size(macros,1) if isfield(H.SharedFunctionalGroupsSequence{1},macros{k1,1}) for k2=1:numel(macros{k1,2}) if (numel(H.SharedFunctionalGroupsSequence{1}.(macros{k1,1}))>0) &&... isfield(H.SharedFunctionalGroupsSequence{1}.(macros{k1,1}){1},macros{k1,2}{k2}) for n=N:-1:1 dat(n).(macros{k1,2}{k2}) = H.SharedFunctionalGroupsSequence{1}.(macros{k1,1}){1}.(macros{k1,2}{k2}); end end end end end end for n=1:N, dat(n).number = n; F = H.PerFrameFunctionalGroupsSequence{n}; for d=1:numel(dim), if isfield(F,dim(d).FunctionalGroupPointer) && ... isfield(F.(dim(d).FunctionalGroupPointer){1},dim(d).DimensionIndexPointer) dat(n).(dim(d).DimensionIndexPointer) = F.(dim(d).FunctionalGroupPointer){1}.(dim(d).DimensionIndexPointer); end end for k1=1:size(macros,1) if isfield(F,macros{k1,1}) && (numel(F.(macros{k1,1}))>0) for k2=1:numel(macros{k1,2}) if isfield(F.(macros{k1,1}){1},macros{k1,2}{k2}) dat(n).(macros{k1,2}{k2}) = F.(macros{k1,1}){1}.(macros{k1,2}{k2}); end end end end end else error('"%s" is not multiframe.', H.FileName); end