function N = spm_parrec2nifti(parfile,opts)
% Import PAR/REC images from Philips scanners into NIfTI
% FORMAT N = spm_parrec2nifti(parfile,opts)
% parfile   - name of PAR file
% opts      - options structure
%    .ext     - NIfTI file extension {'img','nii'} [default: spm_file_ext]
%    .outdir  - output directory [default: pwd]
%
% N         - NIfTI object
%__________________________________________________________________________

% Guillaume Flandin
% Copyright (C) 2015-2022 Wellcome Centre for Human Neuroimaging


%-Display warning
%--------------------------------------------------------------------------
fprintf('*************************************************************\n');
fprintf('**         PRELIMINARY SUPPORT OF PHILIPS PAR/REC          **\n');
fprintf('**                  USE AT YOUR OWN RISK                   **\n');
fprintf('*************************************************************\n');

%-Get options
%--------------------------------------------------------------------------
if nargin < 2, opts = struct; end

if ~isfield(opts,'ext'), opts.ext = spm_file_ext; end
if opts.ext(1) ~= '.', opts.ext = ['.' opts.ext]; end

if ~isfield(opts,'outdir') || isempty(opts.outdir)
    opts.outdir = pwd;
end

%-Read PAR header file
%--------------------------------------------------------------------------
hdr = spm_par_hdr(parfile);

%-Find REC data file
%--------------------------------------------------------------------------
if strcmp(spm_file(parfile,'ext'),'PAR')
    recfile = spm_file(parfile,'ext','REC');
else
    recfile = spm_file(parfile,'ext','rec');
end
d = dir(recfile);
if isempty(d), error('Cannot find REC file "%s".',recfile); end
bytes = d.bytes;

%-Write NIfTI header
%--------------------------------------------------------------------------
dim   = [hdr.ImageInfo(1).recon_resolution hdr.MaxNumberOfSlices hdr.MaxNumberOfDynamics];
dim   = double(dim);
dtype = double(hdr.ImageInfo(1).image_pixel_size);
if prod(dim)*dtype/8 ~= bytes
    % can happen for corrupted files or non dynamic data
    dim(4) = floor(bytes*8/dtype/prod(dim(1:3)));
    fprintf('Size of REC file does not match PAR file. ');
    fprintf('Setting dim(4) to %d.\n',dim(4));
end
% Two scaling options [using DV here]:
% DV = displayed value on console
% FP = floating point value 
scale = hdr.ImageInfo(1).rescale_slope;
inter = hdr.ImageInfo(1).rescale_intercept;
mat   = spm_par_orient(hdr);
switch dtype
    case 8
        dtype = spm_type('int8'); % uint8?
    case 16
        dtype = spm_type('int16'); % uint16?
    case 32
        dtype = spm_type('float32');
    case 64
        dtype = spm_type('float64');
    otherwise
        error('Unknown data type.');
end

ofile    = spm_file(parfile,'path',opts.outdir,'ext',opts.ext);
dato     = file_array(ofile,dim,[dtype 0],0,scale,inter);

N        = nifti;
N.dat    = dato;
N.mat    = mat;
N.mat0   = mat;
N.mat_intent  = 'Scanner';
N.mat0_intent = 'Scanner';
N.descrip     = hdr.ProtocolName;
%N.timing = struct('toffset',[],'tspace',[]); % store TR
create(N);

%-Write NifTI data
%--------------------------------------------------------------------------
dati     = file_array(recfile,dim,[dtype 0],0,scale,inter);
dato     = N.dat;

% should handle interleaved data
% data should not be scaled/unscaled
for i=1:dim(4)
    slice_order = [hdr.ImageInfo([hdr.ImageInfo.dynamic_scan_number]==i).slice_number];
    dato(:,:,:,i) = dati(:,:,slice_order,i);
end


%==========================================================================
% FUNCTION hdr = spm_par_hdr(fname)
%==========================================================================
function hdr = spm_par_hdr(fname)

% could use fileread instead
fid = fopen(fname,'rt');
if fid == -1
    error('Cannot open "%s".',fname);
end

iid = [];
j = 1;

while 1
    l = fgetl(fid);
    if ~ischar(l), break, end
    l = strtrim(l);
    if isempty(l), continue, end
    switch l(1)
        case '#'
            if strncmp(l,'# CLINICAL TRYOUT',17)
                hdr.Version = fliplr(strtok(fliplr(l)));
            end
            if strncmp(l,'# Dataset name:',15)
                hdr.DatasetName = strtrim(l(16:end));
            end
            if strncmp(l,'# === IMAGE INFORMATION DEFINITION',34)
                l = fgetl(fid); % #  The rest of this file contains...
                l = fgetl(fid); % # 
                iid = spm_par_get_iid(fid);
            end
        case '.'
            [key,val] = strtok(l(2:end),':');
            e = spm_par_findindict(strtrim(key));
            if ~isempty(e)
                hdr.(e.name) = feval(e.fcn,sscanf(strtrim(val(2:end)),e.fmt));
            else
                fprintf('Unknown key "%s" - ignored.\n',strtrim(key));
            end
        otherwise
            if isempty(iid)
                fclose(fid);
                error('Cannot read image information without definition.');
            end
            C = textscan(l,[iid.typ]);
            o = 1;
            for i=1:numel(iid)
                for k=1:iid(i).n
                    hdr.ImageInfo(j).(iid(i).name)(k) = C{o};
                    o = o + 1;
                end
            end
            j = j + 1;
    end
end

fclose(fid);


%==========================================================================
% FUNCTION iid = spm_par_get_iid(fid)
%==========================================================================
function iid = spm_par_get_iid(fid)
% get Image Information Definition

n = 1;
while 1
    l = fgetl(fid);
    if ~ischar(l), break, end
    l = strtrim(l(2:end));
    if isempty(l), break, end
    i = strfind(l,'(');
    if isempty(i), continue, else i = i(end); end
    
    name = strrep(strrep(strtrim(strtok(l(1:i-1),'(<')),' ','_'),'-','_');
    typ  = strtrim(l(i:end));
    [m,typ] = strtok(typ(2:end-1),'*');
    if isempty(typ)
        typ = m; m = 1;
    else
        m = str2double(m);
        typ = typ(2:end);
    end
    
    iid(n).name = name;
    switch typ
        case 'integer'
            iid(n).typ = strtrim(repmat('%d',1,m));
        case 'float'
            iid(n).typ = strtrim(repmat('%f',1,m));
        case 'string'
            iid(n).typ = strtrim(repmat('%s',1,m));
        otherwise
            error('Unknown data type "%s".',typ);
    end
    iid(n).n = m;
    n = n + 1;
end


%==========================================================================
% FUNCTION e = spm_par_findindict(key)
%==========================================================================
function e = spm_par_findindict(key)

dict = spm_par_dict;
for i=1:numel(dict)
    if strcmp(dict(i).key,key)
        e = dict(i);
        return
    end
end
e = [];


%==========================================================================
% FUNCTION dict = spm_par_dict 
%==========================================================================
function d = spm_par_dict

persistent dict;
if ~isempty(dict)
    d = dict;
    return;
end

n = @(x) x;
dict = {...
    'Patient name',                        'PatientName', '%s', n
    'Examination name',                    'ExaminationName', '%s', n
    'Protocol name',                       'ProtocolName', '%s', n
    'Examination date/time',               'ExaminationDate', '%s', @(x) datevec(x,'yyyy.mm.dd / HH:MM:SS')
    'Series Type',                         'SeriesType', '%s', n
    'Series_data_type',                    'SeriesDataType', '%s', n
    'Acquisition nr',                      'AcquisitionNr', '%d', n
    'Reconstruction nr',                   'ReconstructionNr', '%d', n
    'Scan Duration [sec]',                 'ScanDuration', '%d', n
    'Max. number of cardiac phases',       'MaxNumberCardiacPhases', '%d', n
    'Max. number of echoes',               'MaxNumberOfEchoes', '%d', n
    'Max. number of slices/locations',     'MaxNumberOfSlices', '%d', n
    'Max. number of dynamics',             'MaxNumberOfDynamics', '%d', n
    'Max. number of mixes',                'MaxNumberOfMixes', '%d', n
    'Patient Position',                    'PatientPosition', '%s', n
    'Patient position',                    'PatientPosition', '%s', n
    'Preparation direction',               'PreparationDirection', '%s', n
    'Technique',                           'Technique', '%s', n
    'Scan resolution  (x, y)',             'ScanResolution', '%d  %d', n
    'Scan mode',                           'ScanMode', '%s', n
    'Repetition time [ms]',                'RepetitionTime', '%f', n
    'Repetition time [msec]',              'RepetitionTime', '%f', n
    'FOV (ap,fh,rl) [mm]',                 'FOV', '%f  %f  %f', n
    'Water Fat shift [pixels]',            'WaterFatShift', '%f', n
    'Angulation midslice(ap,fh,rl)[degr]', 'AngulationMidslice', '%f  %f  %f', n
    'Off Centre midslice(ap,fh,rl) [mm]',  'OffCentreMidslice', '%f  %f  %f', n
    'Flow compensation <0=no 1=yes> ?',    'FlowCompensation', '%d', @logical
    'Presaturation     <0=no 1=yes> ?',    'Presaturation', '%d', @logical
    'Phase encoding velocity [cm/sec]',    'PhaseEncodingVelocity', '%f  %f  %f', n
    'MTC               <0=no 1=yes> ?',    'MTC', '%d', @logical
    'SPIR              <0=no 1=yes> ?',    'SPIR', '%d', @logical
    'EPI factor        <0,1=no EPI>',      'EPIFactor', '%d', n
    'Dynamic scan      <0=no 1=yes> ?',    'DynamicScan', '%d', @logical
    'Diffusion         <0=no 1=yes> ?',    'Diffusion', '%d', @logical
    'Diffusion echo time [ms]',            'DiffusionEchoTime', '%f', n
    'Diffusion echo time [msec]',          'DiffusionEchoTime', '%f', n
    'Max. number of diffusion values',     'MaxNumberOfDiffusionValues', '%d', n
    'Max. number of gradient orients',     'MaxNumberOfGradientsOrients', '%d', n
    'Number of label types   <0=no ASL>',  'NumberOfLabelTypes', '%d', n
    };

dict = struct(...
    'key' , dict(:,1),...
    'name', dict(:,2),...
    'fmt' , dict(:,3),...
    'fcn' , dict(:,4));

d = dict;


%==========================================================================
% FUNCTION M = spm_par_orient(hdr)
%==========================================================================
function M = spm_par_orient(hdr)

dim = double([hdr.ImageInfo(1).recon_resolution hdr.MaxNumberOfSlices]);
to_center = [eye(3) -dim(:)/2];
to_center(4,4) = 1;

vox = diag([hdr.ImageInfo(1).pixel_spacing hdr.ImageInfo(1).slice_thickness+hdr.ImageInfo(1).slice_gap 1]);

switch hdr.ImageInfo(1).slice_orientation
    case 1   % transversal
        to_pls = [0 1 0 0; 0 0 1 0; 1 0 0 0; 0 0 0 1];
    case 2   % sagittal
        to_pls = diag([1 -1 -1 1]);
    case 3   % coronal
        to_pls = [0 0 1 0; 0 -1 0 0; 1 0 0 0; 0 0 0 1];
    otherwise
        error('Unknown slice orientation.');
end

ang = hdr.ImageInfo(1).image_angulation * pi / 180; % {radians} [AP FH RL]
rz  = [cos(ang(3)) -sin(ang(3)) 0; sin(ang(3)) cos(ang(3)) 0; 0 0 1];
ry  = [cos(ang(2)) 0 sin(ang(2)); 0 1 0; -sin(ang(2)) 0 cos(ang(2))];
rx  = [1 0 0; 0 cos(ang(1)) -sin(ang(1)); 0 sin(ang(1)) cos(ang(1))];
rot = rx * ry *rz;
rot(4,4) = 1;

pls = rot * to_pls * vox * to_center;
pls(1:3,4) = pls(1:3,4) + hdr.ImageInfo(1).image_offcentre(:);

pls_to_ras = [0 0 -1 0; -1 0 0 0; 0 1 0 0; 0 0 0 1];
          
M = pls_to_ras * pls;
