function [native,dataout,params] = DICOMReaderGeneral(fn)
% Parses native data streams from a dicom file. 
% Dependencies: dicompit.exe
% Inputs 
% fn: filepath to dicom
% Outputs 
% native: native stream data after parsing (mostly for debugging)
% dataout: individual streams, loop through these to see data extracted
% from DICOM
% params: static parameter block in structure format, some of these data
% are replicated in dataout for plotting purposes. Individual applications
% might be interested in the rest of params.
% Author: Jonathan Sutton, Philips 2018;
% Uses DICOMReaderNative by Rob Schneider, dicompit from Scott Settlemier
% For extracting native data from 3d probes in frustum form, see
% DICOMReaderNative by RobSchneider.
% 2018SEP: Supports bmode, color, spectral, also in xplane. Tested on a few
% limited EPIQ cases. Please push any updates to jonathan.sutton@philips.com.
% TO DO (as of nov18):
% - Currently, only the static parameter block is parsed using tool (dicompit) 
% which doesn't parse dynamic parameter block. Dynamic seek angle acquisitions thus cannot be resolved cylindrically. 
% - time vectors, they should be in dynamic block, so i'll wait to get that
% before trying another route. For now, constructed from params.VDB_SYS_PARAMETER_SET_TYPE.VDB_SYS_FRAME_RATE_HZ
% - Spectrogram and envelope work has not been completed either, you will find information in
% the HighQ data stream, which requires work on the parser.
% - physio only pulls amplitude right now. could use DICOMPhysioReader
% separately, or parse rest of physio information below

%if helpers are in folder, adds them, otherwise need to hard add.
if exist('helpers'); addpath('helpers'); end

fprintf('Decrypting VDB...');
[~, pit] = system(sprintf('dicompit %s',fn));
[params] = parseVDBxml(pit);
fprintf('done\n');
fid = fopen(fn,'r');
dicom_info = ScanDICOMTags(fn,0);

%parse relevant info based on tags
native.datatype = {};
ind0 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('300D')]); 
for i0 = 1:length(ind0)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind0(i0));
    native.datatype{i0} = char(fread(fid,[1 dicom_info(ind0(i0),5)]));
end

native.compresseddatastream = {};
ind1 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3CF3')]);  
for i0 = 1:length(ind1)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind1(i0));
    native.compresseddatastream{i0} = fread(fid,[1 dicom_info(ind1(i0),5)]);
end

native.compressedheaderstream = {};
ind2 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3CFB')]);  
for i0 = 1:length(ind2)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind2(i0));
    native.compressedheaderstream{i0} = fread(fid,[1 dicom_info(ind2(i0),5)]);
end
native.compressiontype = {};
ind2 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3CFA')]);  
for i0 = 1:length(ind2)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind2(i0));
    native.compressiontype{i0} = fread(fid,[1 dicom_info(ind2(i0),5)]);
end

native.instancenumber = [];
ind3 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3021')]);  
for i0 = 1:length(ind3)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind3(i0));
    native.instancenumber(i0) = str2double(char(fread(fid,dicom_info(ind3(i0),5))));
end

native.totalnumsamples = [];
ind4 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3010')]);  
for i0 = 1:length(ind4)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind4(i0));
    native.totalnumsamples(i0) = str2double(char(fread(fid,[1 dicom_info(ind4(i0),5)])));
end

native.uncompressedsamplesize = [];
ind5 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3011')]);  
for i0 = 1:length(ind5)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind5(i0));
    native.uncompressedsamplesize(i0) = str2double(char(fread(fid,[1 dicom_info(ind5(i0),5)])));
end

native.compressiontype = {};
ind6 = FindMatchingTag(dicom_info,[hex2dec('200D') hex2dec('3CFA')]);  
for i0 = 1:length(ind6)      
    fid = FileSeek2DCMTag( fid,dicom_info,ind6(i0));
    native.compressiontype{i0} = char(fread(fid,[1 dicom_info(ind6(i0),5)]));
end

% Find active datatypes
% makes index vector to look up data streams based on datatype
inumsamples = find(native.totalnumsamples);
% instancenumber = native.instancenumber;
% instancenumber(inumsamples) = 1;
% for i5 = 1:length(instancenumber)
%     if i5 > length(instancenumber)
%         break;
%     else
%         if instancenumber(i5) == 1
%             if length(instancenumber)>(i5+1) && instancenumber(i5+1) == 1
%                  instancenumber(i5+1) = 2; instancenumber(i5) = [];
%             end
%         end
%     end
% end

itwos = find((native.instancenumber + (native.totalnumsamples>0)) >1);
isactivedatatype = native.totalnumsamples>0;
isactivedatatype(itwos) = [];
instancenumber = native.instancenumber;
instancenumber(itwos-1) = 1;
instancenumber(itwos) = [];
instancenumber = instancenumber + (isactivedatatype>0);

native.isactivedatatype = isactivedatatype;
%native.isactivedatatype = zeros(1,length(native.datatype));
%native.isactivedatatype(find(instancenumber)) = 1;
%native.isactivedatatype(find(native.totalnumsamples)) = 1;

native.indexdatatype = {};
for i6=1:length(instancenumber)
    native.indexdatatype{i6} = 1:instancenumber(i6);
end
 i7=0;
for i6 = 1:length(native.indexdatatype)
    if sum(native.indexdatatype{i6})
        native.indexdatatype{i6} = native.indexdatatype{i6} + i7;
        i7 = native.indexdatatype{i6}(end);
    else
        i7=i7+1;
    end
end

%gets metadata embedded in dicomtags
[meta] = getmeta(fid,dicom_info);
%gets static and dynamic parameter block from vdb
% [meta2, params] = getVDBparam(pit);
%  meta.Xdims = meta2.Xdims;
%  meta.Ydims = meta2.Ydims;

%Parse the streams
% for all the active streams
%idatatype = find(native.totalnumsamples);
idatatype = find(native.isactivedatatype);
dataout= {};
for i8 = 1:length(idatatype)
    iplanes = native.indexdatatype{idatatype(i8)};
    fprintf('Parsing %s...',native.datatype{idatatype(i8)});
    for i9 = 1:length(iplanes)
        [hdr,stream] = parsedatastream(native,iplanes(i9),i8);
        dataout{i8}.hdr{i9} = hdr;
        
        dataout{i8}.datatype{i9} = native.datatype{idatatype(i8)};
        dataout{i8}.meta{i9} = meta;
        % Assign stream-specific metadata
        switch dataout{i8}.datatype{i9}
            case 'UDM_USD_DATATYPE_DIN_2D_ECHO'
                dataout{i8}.meta{i9}.dims = [params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_NUM_GC_COLUMNS_LIVE.Value params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_NUM_GC_ROWS_LIVE.Value];
                dataout{i8}.meta{i9}.res = [params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_SCALE_FACTOR_GC_MM_LIVE.Value params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_SCALE_FACTOR_GC_MM_LIVE.Value];
                dataout{i8}.meta{i9}.Yrange = [params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_START_DEPTH_ACTUAL params.VDB_2D_ECHO_PARAMETER_SET_TYPE.VDB_2D_ECHO_STOP_DEPTH_ACTUAL];
            case 'UDM_USD_DATATYPE_DIN_DOPPLER_PW '
                dataout{i8}.meta{i9}.Xres2 = params.VDB_DOP_PARAMETER_SET_TYPE.VDB_DOP_SCALE_FACTOR_PIXEL_X_MS.Value;
                dataout{i8}.meta{i9}.Xres = params.VDB_DOP_PARAMETER_SET_TYPE.VDB_DOP_SWEEP_RATE.Value;
                dataout{i8}.meta{i9}.Vrange = [-params.VDB_DOP_PARAMETER_SET_TYPE.VDB_DOP_VELOCITY_RANGE_CMS.Value/2 params.VDB_DOP_PARAMETER_SET_TYPE.VDB_DOP_VELOCITY_RANGE_CMS.Value/2];
            case {'UDM_USD_DATATYPE_DIN_2D_COLOR_VELOCITY','UDM_USD_DATATYPE_DIN_2D_COLOR_POWER ','UDM_USD_DATATYPE_DIN_2D_COLOR_VARIANCE'}
                dataout{i8}.meta{i9}.Yrange = [params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_START_DEPTH_ACTUAL.Value params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_STOP_DEPTH_ACTUAL.Value];
                dataout{i8}.meta{i9}.Xrange = [params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_START_WIDTH_ACTUAL.Value params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_STOP_WIDTH_ACTUAL.Value];
                dataout{i8}.meta{i9}.dims = [params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_NUM_NF_COLUMNS_LIVE.Value params.VDB_COLOR_2D_PARAMETER_SET_TYPE.VDB_COLOR_2D_NUM_NF_ROWS_LIVE.Value];
        end
        %reshape if needed
        if  ~strcmp(dataout{i8}.datatype{i9},{'UDM_USD_DATATYPE_DIN_XFOV_REALTIME_GRAPHICS ','UDM_USD_DATATYPE_DIN_DOPPLER_PW ','UDM_USD_DATATYPE_DIN_DOPPLER_AUDIO','UDM_USD_DATATYPE_DIN_DOPPLER_HIGHQ','UDM_USD_DATATYPE_DIN_PHYSIO '}) 
            if size(stream,1)/dataout{i8}.meta{i9}.dims(1) ~= dataout{i8}.meta{i9}.dims(2)
                dataout{i8}.meta{i9}.dims(1) = dataout{i8}.meta{i9}.dims(2); 
            end
            dataout{i8}.img{i9} = reshape(stream,[dataout{i8}.meta{i9}.dims size(stream,2)]);
        else
            dataout{i8}.img{i9} = stream;            
        end
        FR = params.VDB_SYS_PARAMETER_SET_TYPE.VDB_SYS_FRAME_RATE_HZ.Value;
        NF = size(dataout{i8}.img{i9},3);
        dataout{i8}.time{i9} = linspace(0,(NF-1)./FR,NF);
    end
    fprintf('done\n')
end



end

function [meta] = getmeta(fid,dicom_info)
meta.tags{1,1} = [hex2dec('0018') hex2dec('6012')];
meta.tags{1,2} = 'DICOM_REGION_SPATIAL_FORMAT';
    ind = FindMatchingTag(dicom_info,meta.tags{1,1});
for i1 = 1:length(ind)
meta.tags{1,1} = [hex2dec('0018') hex2dec('6012')];
meta.tags{1,2} = 'DICOM_REGION_SPATIAL_FORMAT';
    ind = FindMatchingTag(dicom_info,meta.tags{1,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_SPATIAL_FORMAT(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint16');
meta.tags{2,1} = [hex2dec('0018') hex2dec('6014')];
meta.tags{2,2} = 'DICOM_REGION_DATA_TYPE';
    ind = FindMatchingTag(dicom_info,meta.tags{2,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_DATA_TYPE(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint16');
meta.tags{3,1} = [hex2dec('0018') hex2dec('6016')];
meta.tags{3,2} = 'DICOM_REGION_FLAGS';
    ind = FindMatchingTag(dicom_info,meta.tags{3,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_FLAGS(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint32');
meta.tags{4,1} = [hex2dec('0018') hex2dec('6018')];
meta.tags{4,2} = 'DICOM_REGION_LOCATION_MIN_X0';
    ind = FindMatchingTag(dicom_info,meta.tags{4,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_LOCATION_MIN_X0(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint32');
meta.tags{5,1} = [hex2dec('0018') hex2dec('601A')];
meta.tags{5,2} = 'DICOM_REGION_LOCATION_MIN_Y0';
    ind = FindMatchingTag(dicom_info,meta.tags{5,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_LOCATION_MIN_Y0(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint32');
meta.tags{6,1} = [hex2dec('0018') hex2dec('601C')];
meta.tags{6,2} = 'DICOM_REGION_LOCATION_MAX_X1';
    ind = FindMatchingTag(dicom_info,meta.tags{6,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_LOCATION_MAX_X1(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint32');
meta.tags{7,1} = [hex2dec('0018') hex2dec('601E')];
meta.tags{7,2} = 'DICOM_REGION_LOCATION_MAX_Y1';
    ind = FindMatchingTag(dicom_info,meta.tags{7,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_REGION_LOCATION_MAX_Y1(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint32');
meta.tags{8,1} = [hex2dec('0018') hex2dec('6024')];
meta.tags{8,2} = 'DICOM_PHYSICAL_UNITS_X_DIRECTION';
    ind = FindMatchingTag(dicom_info,meta.tags{8,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_PHYSICAL_UNITS_X_DIRECTION(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint16');
meta.tags{9,1} = [hex2dec('0018') hex2dec('6026')];
meta.tags{9,2} = 'DICOM_PHYSICAL_UNITS_Y_DIRECTION';
    ind = FindMatchingTag(dicom_info,meta.tags{9,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_PHYSICAL_UNITS_Y_DIRECTION(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'uint16');
meta.tags{10,1} = [hex2dec('0018') hex2dec('602C')];
meta.tags{10,2} = 'DICOM_PHYSICAL_DELTA_X';
    ind = FindMatchingTag(dicom_info,meta.tags{10,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_PHYSICAL_DELTA_X(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'double');
meta.tags{11,1} = [hex2dec('0018') hex2dec('602E')];
meta.tags{11,2} = 'DICOM_PHYSICAL_DELTA_Y';
    ind = FindMatchingTag(dicom_info,meta.tags{11,1});
    fid = FileSeek2DCMTag( fid,dicom_info,ind(i1));
    meta.DICOM_PHYSICAL_DELTA_Y(i1) = typecast(uint8(fread(fid,dicom_info(ind(i1),5))),'double');
end
end

function [hdr,dataout] = parsedatastream(native,i1,i8)
%PARSEDATASTREAM Summary of this function goes here
%   Detailed explanation goes here


% parse header, from Rob Schneider
 %frame time     (uint64, 8 bytes)
   
for i10 = 0:length(native.compressedheaderstream{i1})/32-1
istart = i10*32;
hdr.frameTime(i10+1)  = typecast(uint8(native.compressedheaderstream{i1}(istart+(1:8))),'uint64');
    %reserved       (unsigned short int, 2 bytes)
hdr.reserved1(i10+1)  = typecast(uint8(native.compressedheaderstream{i1}(istart+(9:10))),'uint16');
    %headertag (:14) + elastosign (:2) (unsigned short int, 2 bytes)
hdr.headerTag(i10+1)  = typecast(uint8(native.compressedheaderstream{i1}(istart+(11:12))),'uint16');        
    %data type      (uchar, 1 byte)
hdr.dataType(i10+1)  = uint8(native.compressedheaderstream{i1}(istart+(13)));
    %reserved       (uchar, 1 byte)
hdr.reserved2(i10+1)  = char(uint8(native.compressedheaderstream{i1}(istart+(14))));    
%data format    (unsigned short int, 2 bytes)
hdr.dataFormat(i10+1) = typecast(uint8(native.compressedheaderstream{i1}(istart+(15:16))),'uint16');  
%data size      (unsigned long int, 4 bytes)
hdr.dataSize(i10+1)   = typecast(uint8(native.compressedheaderstream{i1}(istart+(17:20))),'uint32');
%NOTE: in frustum volume data, it is observed that dataSize = rPitch*thetaPitch*phiCount   
end


headersize = 32;
cds.datalength = typecast(uint8(native.compresseddatastream{i1}(1:4)),'uint32');
cds.numframes = typecast(uint8(native.compresseddatastream{i1}(5:8)),'uint32');
cds.frameoffsettable = typecast(uint8(native.compresseddatastream{i1}(9:(9+cds.numframes*4-1))),'uint32');
cds.frameoffsettable = cds.frameoffsettable+1;
for i2 = 1:length(cds.frameoffsettable)
    cds.frameheaders{i2} = native.compresseddatastream{i1}(cds.frameoffsettable(i2):cds.frameoffsettable(i2)+31);
end


for i2 = 1:length(cds.frameoffsettable)-1    
    cds.framedata{i2} = native.compresseddatastream{i1}(cds.frameoffsettable(i2)+headersize:cds.frameoffsettable(i2+1)-1);
end
if ~numel(i2), i2 = 0; end
   cds.framedata{i2+1} = native.compresseddatastream{i1}(cds.frameoffsettable(end)+headersize:length(native.compresseddatastream{i1}));

   bitscale  =1;type = [];
   idatatype = find(native.isactivedatatype);
   if strfind(native.datatype{idatatype(i8)},{'UDM_USD_DATATYPE_DIN_DOPPLER_PW'}), type = 'UDM_USD_DATATYPE_DIN_DOPPLER_PW'; bitscale = 2; end
   if strfind(native.datatype{idatatype(i8)},{'UDM_USD_DATATYPE_DIN_PHYSIO'}), type = 'UDM_USD_DATATYPE_DIN_PHYSIO'; bitscale = 2; end
   if strfind(native.datatype{idatatype(i8)},{'UDM_USD_DATATYPE_DIN_DOPPLER_AUDIO'}), type = 'UDM_USD_DATATYPE_DIN_DOPPLER_AUDIO'; bitscale = 2; end
   if strfind(native.datatype{idatatype(i8)},{'UDM_USD_DATATYPE_DIN_DOPPLER_HIGHQ'}), type ='UDM_USD_DATATYPE_DIN_DOPPLER_HIGHQ'; bitscale = 2; end
    
dataout = zeros(hdr.dataSize(1)/bitscale,length(cds.framedata));
for i100 = 1:size(dataout,2)
    switch native.compressiontype{i1}
        case 'ZLib'
            tmp = ZLibDecompress(cds.framedata{i100});
        case 'None'
            tmp = cds.framedata{i100};
    end
    if bitscale>1
        switch type
            case {'UDM_USD_DATATYPE_DIN_PHYSIO','UDM_USD_DATATYPE_DIN_DOPPLER_HIGHQ','UDM_USD_DATATYPE_DIN_DOPPLER_AUDIO'}
                tmp = typecast(uint8(tmp),'int16'); 
            otherwise
                tmp = typecast(uint8(tmp),'uint16'); 
        end
    end
    
    dataout(:,i100) = tmp(1:size(dataout,1));
end

end

function [OUT] = parseVDBxml(str_in)
y = tic;
% find param set type indices    
    iParamSetTypes = strfind(str_in,'<ParameterSetType Name="');
    %within set type, find params
        for i1 = 1:length(iParamSetTypes)
            %get name of paramsettype
            
            if i1<length(iParamSetTypes)
                str_currentsettype = str_in(iParamSetTypes(i1):iParamSetTypes(i1+1));
            else
                str_currentsettype = str_in(iParamSetTypes(i1):end);
            end
            iname = strfind(str_currentsettype,'"');
            ParamSetTypeName = str_currentsettype(iname(1)+1:iname(2)-1);
            %find paramsettype descriptors (if we want) here            
            % find parameter indices
            iParameters = strfind(str_currentsettype,'<Parameter Name=');
            for i2 = 1:length(iParameters)
                if i2<length(iParameters)
                    str_currentparameter = str_currentsettype(iParameters(i2):iParameters(i2+1));
                else
                    str_currentparameter = str_currentsettype(iParameters(i2):end);
                end
                iname = strfind(str_currentparameter,'"');
                ParamName = str_currentparameter(iname(1)+1:iname(2)-1);
%                 iSrcValue = strfind(str_currentparameter,'<SrcMark Value=');
%                      SrcValue = textscan(str_currentparameter(iSrcValue:end),'<SrcMark Value="%s"');
%                      SrcValue = cell2mat(SrcValue{1});
%                      SrcValue = SrcValue(1:end-3);
                iElm = strfind(str_currentparameter,'Elm>');
                try
                     if numel(iElm)
                         Elm = cell2mat(textscan(str_currentparameter(iElm(1)+4:iElm(2)-3),'%f','Delimiter',' '));                     
                         for i3 = 1:length(Elm)
                            eval(sprintf('OUT.%s.%s.Value(i3) = %f;',ParamSetTypeName,ParamName,Elm(i3)));
                         end
                     end
                catch e
                1;    
                end
            end
            %within params, parse descriptors, SrcMark, Elm
        end
        
    %fprintf('Parsed in %3.1f seconds.',toc(y));
end

