function hdr = spm_dicom_header(P, dict, options) % Read header information from a DICOM file % FORMAT hdr = spm_dicom_header(P,dict, options) % P - array of filenames % dict - DICOM dictionary loaded from file % options - an (optional) structure containing fields % abort - if this is a function handle, it will be called % with fieldname and value arguments. If this function % returns true, then reading the header will be aborted. % all_fields - binary true/false, indicating what to do with % fields thatare not included in the DICOM % dictionary. % % hdr - a header. % % Contents of headers are approximately explained in: % http://medical.nema.org/standard.html % % This code may not work for all cases of DICOM data, as DICOM is an % extremely complicated "standard". %__________________________________________________________________________ % Copyright (C) 2002-2015 Wellcome Trust Centre for Neuroimaging % John Ashburner % $Id: spm_dicom_header.m 6798 2016-05-20 11:53:33Z john $ if nargin<3, options = struct('abort',false,'all_fields',true); end hdr = []; P = deblank(P); fp = fopen(P,'r','ieee-le'); if fp==-1, warning('spm:dicom','%s: Cant open file.',P); return; end fseek(fp,128,'bof'); dcm = char(fread(fp,4,'uint8')'); if ~strcmp(dcm,'DICM') % Try truncated DICOM file fomat fseek(fp,0,'bof'); tag.group = fread(fp,1,'ushort'); tag.element = fread(fp,1,'ushort'); if isempty(tag.group) || isempty(tag.element) fclose(fp); warning('spm:dicom','%s: Truncated file.',P); return; end %t = dict.tags(tag.group+1,tag.element+1); if isempty(find(dict.group==tag.group & dict.element==tag.element,1)) && ~(tag.group==8 && tag.element==0) % entry not found in DICOM dict and not from a GE Twin+excite % that starts with with an 8/0 tag that I can't find any % documentation for. fclose(fp); warning('spm:dicom','%s: Not a DICOM file.', P); return; else fseek(fp,0,'bof'); end end try hdr = read_dicom(fp, 'il',dict, options); if isempty(hdr) fclose(fp); return; end hdr.Filename = fopen(fp); catch problem = lasterror; fprintf('%s: Trouble reading DICOM file (%s), skipping.\n', fopen(fp), problem.message); end fclose(fp); %========================================================================== % function [ret,len] = read_dicom(fp, flg, dict, options, lim) %========================================================================== function [ret,len] = read_dicom(fp, flg, dict, options, lim) if nargin<5, lim=4294967295; end % FFFFFFFF len = 0; ret = []; while len0 switch tag.name case {'GroupLength'}, % Ignore it fseek(fp,tag.length,'cof'); case {'PixelData'}, ret.StartOfPixelData = ftell(fp); ret.SizeOfPixelData = tag.length; ret.VROfPixelData = tag.vr; fseek(fp,tag.length,'cof'); case {'CSAData'}, % raw data ret.StartOfCSAData = ftell(fp); ret.SizeOfCSAData = tag.length; fseek(fp,tag.length,'cof'); case {'CSAImageHeaderInfo', 'CSASeriesHeaderInfo','CSANonImageHeaderInfoVA','CSAMiscProtocolHeaderInfoVA','CSANonImageHeaderInfoVB','CSAMiscProtocolHeaderInfoVB'}, dat = decode_csa(fp,tag.length); ret.(tag.name) = dat; case {'TransferSyntaxUID'}, dat = char(fread(fp,tag.length,'uint8')'); dat = deblank(dat); ret.(tag.name) = dat; switch dat case {'1.2.840.10008.1.2'}, % Implicit VR Little Endian flg = 'il'; case {'1.2.840.10008.1.2.1'}, % Explicit VR Little Endian flg = 'el'; case {'1.2.840.10008.1.2.1.99'}, % Deflated Explicit VR Little Endian warning('spm:dicom','%s: Cant read Deflated Explicit VR Little Endian file.', fopen(fp)); flg = 'dl'; return; case {'1.2.840.10008.1.2.2'}, % Explicit VR Big Endian %warning('spm:dicom','%s: Cant read Explicit VR Big Endian file',fopen(fp)); flg = 'eb'; % Unused case {'1.2.840.10008.1.2.4.50','1.2.840.10008.1.2.4.51','1.2.840.10008.1.2.4.70',... '1.2.840.10008.1.2.4.80','1.2.840.10008.1.2.4.90','1.2.840.10008.1.2.4.91'}, % JPEG Explicit VR flg = 'el'; %warning('spm:dicom',['Cant read JPEG Encoded file "' fopen(fp) '".']); otherwise, flg = 'el'; warning('spm:dicom','%s: Unknown Transfer Syntax UID (%s).',fopen(fp), dat); return; end otherwise switch tag.vr, case {'UN'}, % Unknown - read as char dat = fread(fp,tag.length,'uint8')'; case {'AE', 'AS', 'CS', 'DA', 'DS', 'DT', 'IS', 'LO', 'LT',... 'PN', 'SH', 'ST', 'TM', 'UI', 'UT'}, % Character strings dat = char(fread(fp,tag.length,'uint8')'); switch tag.vr, case {'UI','ST'}, dat = deblank(dat); case {'DS'}, try dat = textscan(dat,'%f','delimiter','\\')'; dat = dat{1}; catch dat = textscan(dat,'%f','delimiter','/')'; dat = dat{1}; end case {'IS'}, dat = textscan(dat,'%d','delimiter','\\')'; dat = double(dat{1}); case {'DA'}, dat = strrep(dat,'.',' '); dat = textscan(dat,'%4d%2d%2d'); [y,m,d] = deal(dat{:}); dat = datenum(double(y),double(m),double(d)); case {'TM'}, if any(dat==':'), dat = textscan(dat,'%d:%d:%f'); [h,m,s] = deal(dat{:}); h = double(h); m = double(m); else dat = textscan(dat,'%2d%2d%f'); [h,m,s] = deal(dat{:}); h = double(h); m = double(m); end if isempty(h), h = 0; end; if isempty(m), m = 0; end; if isempty(s), s = 0; end; dat = s+60*(m+60*h); case {'LO'}, dat = uscore_subst(dat); otherwise, end; case {'OB'}, % dont know if this should be signed or unsigned dat = fread(fp,tag.length,'uint8')'; case {'US', 'AT', 'OW'}, dat = fread(fp,tag.length/2,'uint16')'; case {'SS'}, dat = fread(fp,tag.length/2,'int16')'; case {'UL'}, dat = fread(fp,tag.length/4,'uint32')'; case {'SL'}, dat = fread(fp,tag.length/4,'int32')'; case {'FL','OF'}, dat = fread(fp,tag.length/4,'float')'; case {'FD','OD'}, dat = fread(fp,tag.length/8,'double')'; case {'SQ'}, [dat,len1] = read_sq(fp, flg,dict, options, tag.length); if len1==-1 && isempty(dat) ret = []; len = -1; return; end tag.length = len1; otherwise, dat = fread(fp,tag.length,'uint8')'; %dat = ''; %if tag.length % fseek(fp,tag.length,'cof'); % warning('spm:dicom','%s: Unknown VR "%s" [%X%X] (offset=%d).', fopen(fp), tag.vr, tag.vr(1)+0, tag.vr(2)+0, ftell(fp)); %end end if ~isempty(tag.name) if isa(options.abort,'function_handle') && feval(options.abort,tag.name,dat) ret = []; len = -1; return; end ret.(tag.name) = dat; end end end len = len + tag.le + tag.length; end %========================================================================== % function [ret,len] = read_sq(fp, flg, dict, options, lim) %========================================================================== function [ret,len] = read_sq(fp, flg, dict, options, lim) ret = {}; n = 0; len = 0; while len1024 || n <= 0 fseek(fp,lim-4,'cof'); t = struct('name','JUNK: Don''t know how to read this damned file format'); return; end unused = fread(fp,1,'uint32')'; % Unused "M" or 77 for some reason tot = 2*4; t(n) = struct('name','', 'vm',[], 'vr','', 'syngodt',[], 'nitems',[], 'xx',[], 'item',struct('xx',{},'val',{})); for i=1:n t(i).name = fread(fp,64,'uint8')'; msk = find(~t(i).name)-1; if ~isempty(msk) t(i).name = char(t(i).name(1:msk(1))); else t(i).name = char(t(i).name); end t(i).vm = fread(fp,1,'int32')'; t(i).vr = fread(fp,4,'uint8')'; t(i).vr = char(t(i).vr(1:3)); t(i).syngodt = fread(fp,1,'int32')'; t(i).nitems = fread(fp,1,'int32')'; t(i).xx = fread(fp,1,'int32')'; % 77 or 205 tot = tot + 64+4+4+4+4+4; if t(i).nitems > 0 t(i).item(t(i).nitems) = struct('xx',[], 'val',[]); end for j=1:t(i).nitems % This bit is just wierd t(i).item(j).xx = fread(fp,4,'int32')'; % [x x 77 x] len = t(i).item(j).xx(1)-t(1).nitems; if len<0 || len+tot+4*4>lim t(i).item(j).val = ''; t(i).item = t(i).item(1:j); tot = tot + 4*4; break; end t(i).item(j).val = char(fread(fp,len,'uint8')'); fread(fp,4-rem(len,4),'uint8'); tot = tot + 4*4+len+(4-rem(len,4)); end end %========================================================================== % function t = decode_csa2(fp,lim) %========================================================================== function t = decode_csa2(fp,lim) unused1 = fread(fp,4,'uint8'); % Unused unused2 = fread(fp,4,'uint8'); % Unused n = fread(fp,1,'uint32'); opos = ftell(fp); if isempty(n) || n>1024 || n < 0 fseek(fp,lim-4,'cof'); t = struct('name','Don''t know how to read this damned file format'); return; end unused = fread(fp,1,'uint32')'; % Unused "M" or 77 for some reason pos = 16; t(n) = struct('name','', 'vm',[], 'vr','', 'syngodt',[], 'nitems',[], 'xx',[], 'item',struct('xx',{},'val',{})); for i=1:n t(i).name = fread(fp,64,'uint8')'; pos = pos + 64; msk = find(~t(i).name)-1; if ~isempty(msk) t(i).name = char(t(i).name(1:msk(1))); else t(i).name = char(t(i).name); end t(i).vm = fread(fp,1,'int32')'; t(i).vr = fread(fp,4,'uint8')'; t(i).vr = char(t(i).vr(1:3)); t(i).syngodt = fread(fp,1,'int32')'; t(i).nitems = fread(fp,1,'int32')'; t(i).xx = fread(fp,1,'int32')'; % 77 or 205 pos = pos + 20; if t(i).nitems > 0 t(i).item(t(i).nitems) = struct('xx',[], 'val',[]); end for j=1:t(i).nitems t(i).item(j).xx = fread(fp,4,'int32')'; % [x x 77 x] pos = pos + 16; len = t(i).item(j).xx(2); if len>lim-pos len = lim-pos; t(i).item(j).val = char(fread(fp,len,'uint8')'); fread(fp,rem(4-rem(len,4),4),'uint8'); t(i).item = t(i).item(1:j); warning('spm:dicom','%s: Problem reading Siemens CSA field.', fopen(fp)); return; end t(i).item(j).val = char(fread(fp,len,'uint8')'); fread(fp,rem(4-rem(len,4),4),'uint8'); end end %========================================================================== % function str_out = uscore_subst(str_in) %========================================================================== function str_out = uscore_subst(str_in) str_out = str_in; pos = strfind(str_in,'+AF8-'); if ~isempty(pos) str_out(pos) = '_'; str_out(repmat(pos,4,1)+repmat((1:4)',1,numel(pos))) = []; end