~ubuntu-branches/ubuntu/utopic/mricron/utopic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
unit part;
//Physiological Artifact Removal Tool
{$H+}
interface
uses
    define_types,dialogs,SysUtils;

function ApplyPart( lFilename: string;lImgData: singleP; lBins,lVolVox,lSlices, lImgVol : integer; lTRsec: single): string;

implementation
type
    TPhysioT =  RECORD
      Triggers,InterpolatedTriggers: integer;
      TriggerMedian,TriggerQ1,TriggerQ3: Double;
      TriggerRA: singleP;
    END;
function SaveTriggersAs3ColumnFSL(lPhysioIn: TPhysioT; lOutName: string): boolean;
var
   lF: textfile;
   lPos: integer;
begin
   result := false;
   if (lPhysioIn.Triggers < 1) then
        exit;
   assignfile(lF,lOutName+'.txt');
   Filemode := 0;
   rewrite(lF);
   for lPos := 1 to lPhysioIn.Triggers do
       Writeln(lf,realtostr(lPhysioIn.TriggerRA^[lPos],3)+' 1 1');
   closefile(lF);
   Filemode := 2;
   result := true;
end;

procedure qsort(lower, upper : integer; var Data:SingleP);
//40ms - fast but very recursive...
var
       left, right : integer;
       pivot,lswap: single;
begin
     pivot:=Data^[(lower+upper) div 2];
     left:=lower;
     right:=upper;
     while left<=right do begin
             while Data^[left]  < pivot do left:=left+1;  { Parting for left }
             while Data^[right] > pivot do right:=right-1;{ Parting for right}
             if left<=right then begin   { Validate the change }
                 lswap := Data^[left];
                 Data^[left] := Data^[right];
                 Data^[right] := lswap;
                 left:=left+1;
                 right:=right-1;
             end; //validate
     end;//while left <=right
     if right>lower then qsort(lower,right,Data); { Sort the LEFT  part }
     if upper>left  then qsort(left ,upper,data); { Sort the RIGHT part }
end;

procedure QuartileTriggerSpacing(var lPhysio: TPhysioT);
var
   lTriggerDelayRA: singleP;
   lPos: integer;
begin
          lPhysio.TriggerQ1 := 0;
          lPhysio.TriggerMedian := 0;
          lPhysio.TriggerQ3 := 0;
          if lPhysio.Triggers < 4 then
             exit;
          getmem(lTriggerDelayRA,(lPhysio.Triggers-1)*sizeof(single));
          for lPos := 1 to (lPhysio.Triggers-1) do
              lTriggerDelayRA^[lPos] := abs(lPhysio.TriggerRA^[lPos]-lPhysio.TriggerRA^[lPos+1]);
          qsort(1,lPhysio.Triggers-1,lTriggerDelayRA);//-1 : fence post vs wire 
          lPos := lPhysio.Triggers div 2;
          lPhysio.TriggerMedian := lTriggerDelayRA^[lPos];
          lPos := lPhysio.Triggers div 4;
          lPhysio.TriggerQ1 := lTriggerDelayRA^[lPos];
          lPos := round(0.75*lPhysio.Triggers );
          lPhysio.TriggerQ3 := lTriggerDelayRA^[lPos];
          freemem(lTriggerDelayRA);
end;

function PARTool (var lPhysio: TPhysioT; lImgData: singleP; lTRsec: single; lnVolVox,lnSlices, lImgVol, lBinIn : integer): string;
const
     kMinSamplesPerBin = 4;
var
   lV,lSliceTime,lMeanSignal,lOnsetTime,lBinWidth,lBinMin,lBinMax,lTimeSinceTrigger,lPrevTriggerTime: double;
   lSlice,lSlicePos,lnSliceVox,lnSlicePos,lVoxel,lBin,lSample,lnBin,lnBinDiv2,lNextTrigger,lSamplesWithVariance,lCorrectedSamples,lVolOffset: integer;
   lBinCountRA,lVolBinRA: longintp;
   lVariance : boolean;
   lBinEstimateRA: doublep;
begin
     result := '';
     if (lPhysio.Triggers < 4) or (lnVolVox < 4) or (lImgVol < 4) then begin
        showmessage('PART requires at least 4 triggers and at least 4 volumes each with at least 4 voxels');
        exit;
     end;
     if (lBinIn < 4) then begin
        showmessage('PART requires at least 4 data bins');
        exit;
     end;
     lnSliceVox := lnVolVox div lnSlices;
     if (lnVolVox mod lnSlices) <> 0 then begin
         showmessage('PART requires volvox to be evenly divisible by number of slices.');
         exit;
     end;
     lSamplesWithVariance := 0;
     lCorrectedSamples := 0;
     QuartileTriggerSpacing(lPhysio);
     //find number bin range - this is median-1.5IQR..median+1.5IQR
     lBinMin := -lPhysio.TriggerMedian/2-(abs(lPhysio.TriggerQ1-lPhysio.TriggerQ3)*0.75);
     lBinMax := +lPhysio.TriggerMedian/2+abs(lPhysio.TriggerQ1-lPhysio.TriggerQ3)*0.75;
     //next - create bins
     lnBin := lBinIn;
     //could adjust number of bins and return here wth a label
    lBinWidth := abs((lBinMax-lBinMin)/(lnBin-1));//lnBin-1: fenceposts vs wire
    lnBinDiv2 := (lnBin div 2)+1;
    getmem(lBinCountRA,lnBin*sizeof(integer));
    getmem(lBinEstimateRA,lnBin*sizeof(double));
    getmem(lVolBinRA,lImgVol*sizeof(integer));
    lVoxel := 0;
    for lSlice := 1 to lnSlices do begin
        //adjust slices so slice 1 occurs at 0, slice 2 at 1/nslices...
        lSliceTime := ((lSlice-1)/lnSlices)-1; //-1 as 1st volume starts at zero, not 1
        //do next step for each slice - different slices have different bin distributions due to different slicetime
        //next count number of samples in each bin
        for lBin := 1 to lnBin do
            lBinCountRA^[lBin] := 0;
        lPrevTriggerTime := -MaxInt;
        lNextTrigger := 1;
        for lSample := 1 to lImgVol do begin
                   //for each sample, find nearest trigger
                   lOnsetTime := lSample+lSliceTime;
                   if lOnsetTime > lPhysio.TriggerRA^[lNextTrigger] then begin
                      while (lNextTrigger <= lPhysio.Triggers ) and (lOnsetTime > lPhysio.TriggerRA^[lNextTrigger]) do begin
                            lPrevTriggerTime := lPhysio.TriggerRA^[lNextTrigger];
                            inc(lNextTrigger);
                      end; //while
                   end;//if onset >
                   lTimeSinceTrigger :=  lOnsetTime-lPrevTriggerTime;
                   if lTimeSinceTrigger > abs(lPhysio.TriggerRA^[lNextTrigger]-lOnsetTime) then
                      lTimeSinceTrigger :=  -abs(lPhysio.TriggerRA^[lNextTrigger]-lOnsetTime);//use abs in case we are past final trigger
                   //now compute bin...
                   //inc(lCorrectedSamples);
                   if (lTimeSinceTrigger > lBinMin) and (lTimeSinceTrigger < lBinMax) then begin
                      lBin := round( (lTimeSinceTrigger)/ lBinWidth)+lnBinDiv2;
                      lVolBinRA^[lSample] := lBin;
                      if (lBin < 1) or (lBin > lnBin) then
                         fx(-661,lBin,lTimeSinceTrigger)
                      else
                          inc(lBinCountRA^[lBin]);
                   end else
                       lVolBinRA^[lSample] := 0;
        end; //for each volume
        for lSlicePos := 1 to lnSliceVox do begin
            inc(lVoxel);
            //first - only correct voxels with variability - do not waste time outside brain
            lVolOffset := lVoxel;
            lVariance := false;
            lSample := 1;
            lV := lImgData^[lVolOffset];
            while (not lVariance) and (lSample <= lImgVol) do begin
                  if lV <> lImgData^[lVolOffset] then
                     lVariance := true;
                  inc(lSample);
                  lVolOffset := lVolOffset+lnVolVox;
            end; //while no variance
            if lVariance then begin //voxel intensity varies accross time - attempt to remove artifact
               lSamplesWithVariance := lSamplesWithVariance +lImgVol;
               //1st - sum effects
               for lBin := 1 to lnBin do
                   lBinEstimateRA^[lBin] := 0;
               lMeanSignal := 0;
               lVolOffset := lVoxel;
               for lSample := 1 to lImgVol do begin
                   lMeanSignal := lImgData^[lVolOffset] + lMeanSignal;
                   lBin := lVolBinRA^[lSample];
                   if (lBin > 0) and (lBinCountRA^[lBin] > kMinSamplesPerBin) then
                         lBinEstimateRA^[lBin] := lBinEstimateRA^[lBin]+ lImgData^[lVolOffset];
                   lVolOffset := lVolOffset+lnVolVox;
               end; //for each volume
               lMeanSignal := lMeanSignal /lImgVol;
               //next compute correction... average signal in bin - average voxel intensity irrelevant of bin
               for lBin := 1 to lnBin do
                   if lBinCountRA^[lBin] > kMinSamplesPerBin then
                      lBinEstimateRA^[lBin] := (lBinEstimateRA^[lBin]/lBinCountRA^[lBin])-lMeanSignal;
                      //lBinEstimateRA[lBin] := lBinEstimateRA[lBin]-lBinMeanCount;
               //next  apply correction - inner loop complete for each voxel!
               lVolOffset := lVoxel;
               for lSample := 1 to lImgVol do begin
                   //for each sample, find nearest trigger
                   lBin := lVolBinRA^[lSample];
                   if (lBin > 0) and (lBinCountRA^[lBin] > kMinSamplesPerBin) then begin
                         lImgData^[lVolOffset] :=  (lImgData^[lVolOffset]-lBinEstimateRA^[lBin]);
                         inc(lCorrectedSamples)
                   end;
                   lVolOffset := lVolOffset+lnVolVox;
               end; //for each volume
            end; //if variance
        end;//for each voxel in slice
    end; //for slice
    //**INNER LOOP end -
    //next - report results
    result :=' Time per vol (TR) [sec] '+realtostr(lTRsec,4)+kCR;
    result :=result +' fMRI Volumes '+inttostr(lImgVol)+kCR;
    result :=result +' Triggers n/First...Last [vol] '+realtostr(lPhysio.Triggers,0)+'/'+realtostr(lPhysio.TriggerRA^[1],2)+'...'+realtostr(lPhysio.TriggerRA^[lPhysio.Triggers],2)+kCR;
    if abs(lImgVol-lPhysio.TriggerRA^[lPhysio.Triggers]) > 10 then begin
       result :=result +'******* WARNING: Duration of fMRI session and duration of triggers is very different *******';
       result :=result +'******* Please ensure specified TR is correct, files are correct and onset of fMRI was synchronized with physio data *******';
    end;
    result := result + '    Q1/Median/Q2 [sec] '+realtostr(lTRsec*lPhysio.TriggerQ1,2)+'/'+realtostr(lTRsec*lPhysio.TriggerMedian,2)+'/'+realtostr(lTRsec*lPhysio.TriggerQ3,2)+kCR;
    result  :=  result + ' Bin n/Range [sec] '+inttostr(lnBin)+'/'+realtostr(lTRsec*lBinMin,2)+ '...'+realtostr(lTRsec*lBinMax,2)+kCR;
    result := result+   '  voxels without variance (outside brain) %: '+realtostr(100*( (lnVolVox-(lSamplesWithVariance/lImgVol))/lnVolVox),2)+kCR;
    if lSamplesWithVariance > 0 then
       result := result+   '  voxels with variance which were corrected %: '+realtostr(100*(lCorrectedSamples/lSamplesWithVariance),2)+kCR;
    for lBin := 1 to lnBin do
        result := result+('  Bin '+inttostr(lBin)+ ' '+realtostr(lBin*lBinWidth+lBinMin ,2) +' '+inttostr(lBinCountRA^[lBin])  )+kCR;
    freemem(lBinCountRA);
    freemem(lBinEstimateRA);
    freemem(lVolBinRA);
end;  


function StrVal (var lNumStr: string): integer;
begin
     try
        result := strtoint(lNumStr);
     except
           on EConvertError do begin
              showmessage('StrVal Error - Unable to convert the string '+lNumStr+' to a number');
              result := MaxInt;
           end;
     end;
end;

procedure AddSample(var lNumStr: string; var lnTotal,lnSample, lnTrigger: integer; var lPhysio: TPhysioT);
var
   lVal: integer;
begin
     lVal := StrVal(lNumStr);
     if lVal = 5003 then
        exit;
     lNumStr := '';
     inc(lnTotal);
     if lnTotal < 5 then exit;
          if lVal > 4096 then begin
             if lVal <> 5000 then begin
                showmessage('Potentially serious error: unknown trigger type : '+inttostr(lVal));
             end;
             inc(lnTrigger);
             if (lPhysio.Triggers <> 0) then
                lPhysio.TriggerRA^[lnTrigger] := lnSample;
          end else begin
             inc(lnSample);
          end;
end;

function AdjustStartPos (var lStr: string; var lStartPos: integer): boolean;
//Some Siemens physio files appear to have nonsense characters befor real data<bh:ef><bh:bb><bh:bf>1
var
   lLen: integer;
begin
   lLen := length(lStr);
   result := false;
   if (lLen-lStartPos)<2 then
      exit;
   result := true;
   repeat
         if  lStr[lStartPos] in [ '0'..'9'] then
             exit;
         inc(lStartPos);
   until (lStartPos = lLen);
    result := false;
end;

procedure CountValidItems(var lStr: string; var lStartPos,lnSample, lnTrigger: integer; var lPhysio: TPhysioT);
label
     123;
var
   lPos,lnTotal: integer;
   lNumStr: string;
begin
   lnTotal:= 0;
   lnSample := 0;
   lnTrigger := 0;
   lNumStr := '';
   if length(lStr)<2 then exit;
      if not AdjustStartPos ( lStr, lStartPos) then exit; //Oct 2009
   for lPos := lStartPos to length(lStr) do begin
       if (lStr[lPos] = ' ') and (lNumStr <> '') then begin
          if lNumStr = '5003' then begin
              lNumStr := '';
              goto 123; //end of recording
          end else
              AddSample(lNumStr, lnTotal,lnSample, lnTrigger, lPhysio);
       end else begin
           if  lStr[lPos] in [ '0'..'9'] then
               lNumStr := lNumStr + lStr[lPos]
           else if lStr[lPos] in [' '] then

           else begin
                //Showmessage(lStr[lPos]);
                goto 123;
           end;
       end;
   end; //for length
123:
   if (lNumStr <> '') then
      AddSample(lNumStr, lnTotal,lnSample, lnTrigger, lPhysio);
   lStartPos := lPos;
   while (lStartPos < length(lStr)) and ( lStr[lStartPos] <> ' ') do begin
                      inc(lStartPos);
   end;
end;

procedure CreatePhysio (var lPhysio: TPhysioT);
begin
     lPhysio.Triggers := 0;

end;

procedure ClosePhysio (var lPhysio: TPhysioT);
begin
     with lPhysio do begin
          if Triggers > 0 then
             freemem(TriggerRA);
          Triggers := 0;
     end;
end;

procedure InitPhysio(lnTrigger: integer; var lPhysio: TPhysioT);
begin
     ClosePhysio (lPhysio);
     with lPhysio do begin
          Triggers := lnTrigger;
          InterpolatedTriggers := 0;
          if Triggers > 0 then
             getmem(TriggerRA,Triggers*sizeof(single));
     end;
end;

function load3ColTxtPhysio (lFilename: string; var lPhysio: TPhysioT): boolean;
var
   F: TextFile;
   lnTrigger: integer;
   lFloat,lFloat2,lFloat3: single;
begin
    result := false;
     if not fileexists(lFilename) then exit;
     ClosePhysio(lPhysio);
     AssignFile(F, lFilename);
     FileMode := 0;  //Set file access to read only
     //pass 1 - count number of triggers
     lnTrigger := 0;
     Reset(F);
     while not EOF(F) do begin
	  {$I-}
          read(F,lFloat,lFloat2,lFloat3); //read triplets instead of readln: this should load UNIX files
          {$I+}
          if (ioresult = 0) and (lFloat > 0) then
             inc(lnTrigger);
     end;
     //pass 2 - load array
     InitPhysio(lnTrigger, lPhysio);
     lnTrigger := 0;
     Reset(F);
     while not EOF(F) do begin
	  {$I-}
          read(F,lFloat,lFloat2,lFloat3); //read triplets instead of readln: this should load UNIX files
          {$I+}
          if (ioresult = 0) and (lFloat > 0) then begin
             inc(lnTrigger);
             lPhysio.TriggerRA^[lnTrigger] := lFloat;
          end;
     end;
     FileMode := 2;  //Set file access to read/write
     CloseFile(F);
    result := true;
end;

procedure ReadlnX (var F: TextFile; var lResult: string);
var
   lCh: char;
begin
     lResult := '';
     while not Eof(F) do begin
           Read(F, lCh);
           if (lCh in [#10,#13]) then begin
              if lResult <> '' then begin
                 //Showmessage(lResult);
                 exit;
              end;
           end else
               lResult := lResult + lCh;
     end;
end; //ReadlnX


function loadSiemensPhysio (lFilename: string; var lPhysio: TPhysioT): boolean;
var
   F: TextFile;
   lStr: string;
   lPos,lnSample,lnTrigger: integer;
begin
    result := false;
     if not fileexists(lFilename) then exit;
     ClosePhysio(lPhysio);
     AssignFile(F, lFilename);
     FileMode := 0;  //Set file access to read only
     Reset(F);
     ReadlnX(F,lStr);//ColNames
     if length(lStr) < 1 then begin
        CloseFile(F);
        exit;
     end;
     //first pass - count items
     lPos := 1;
     CountValidItems(lStr,lPos,lnSample,lnTrigger,lPhysio);
     //second pass - load array
     if (lnSample < 1) and (lnTrigger < 1) then begin
        CloseFile(F);
        exit;
     end;
     //2nd pass...
     InitPhysio(lnTrigger, lPhysio);
     lPos := 1;
     CountValidItems(lStr,lPos,lnSample,lnTrigger,lPhysio);
     FileMode := 2;  //Set file access to read/write
     CloseFile(F);
    result := true;
end;

function InterpolateGaps (var lPhysioIn: TPhysioT): boolean;
//attempts to fill missing trigger pulses
//you must call QuartileTriggerSpacing before this function!
//   it assumes q1/median/q3 are filled
var
   lGap,l2Min,l2Max,l3Min,l3Max: double;
   lnReplace,lTrigger,lTrigger2: integer;
   lTempPhysio: TPhysioT;
begin
     result := false;
     if (lPhysioIn.Triggers < 4) then begin
        showmessage('InterpolateGaps requires at least 4 triggers.');
        exit;
     end;
     l2Min := 2*lPhysioIn.TriggerMedian-(abs(lPhysioIn.TriggerQ1-lPhysioIn.TriggerQ3)*1.5);
     l2Max := 2*lPhysioIn.TriggerMedian+(abs(lPhysioIn.TriggerQ1-lPhysioIn.TriggerQ3)*1.5);

     l3Min := 3*lPhysioIn.TriggerMedian-(abs(lPhysioIn.TriggerQ1-lPhysioIn.TriggerQ3)*1.5);
     l3Max := 3*lPhysioIn.TriggerMedian+(abs(lPhysioIn.TriggerQ1-lPhysioIn.TriggerQ3)*1.5);
     if l2Max > l3Min then
        exit; //variability too high to determine gaps
     lnReplace := 0;
     for lTrigger := 2 to lPhysioIn.Triggers do begin
        lGap := lPhysioIn.TriggerRA^[lTrigger] - lPhysioIn.TriggerRA^[lTrigger-1];
        if (lGap > l2Min) and (lGap < l2Max) then
           inc(lnReplace);
        if (lGap > l3Min) and (lGap < l3Max) then
           inc(lnReplace,2);
     end;
     if lnReplace = 0 then begin
         result := true;
         exit;
     end;
     //create temp backup
     CreatePhysio(lTempPhysio);
     InitPhysio(lPhysioIn.Triggers, lTempPhysio);
     for lTrigger := 1 to lPhysioIn.Triggers do
         lTempPhysio.TriggerRA[lTrigger] := lPhysioIn.TriggerRA[lTrigger];
     //create resized array
     InitPhysio(lTempPhysio.Triggers+lnReplace, lPhysioIn);
     //fill gaps
     lPhysioIn.TriggerRA[1] := lTempPhysio.TriggerRA[1];
     lTrigger2 := 1;
     for lTrigger := 2 to lTempPhysio.Triggers do begin
        inc(lTrigger2);
        lGap := lTempPhysio.TriggerRA^[lTrigger] - lTempPhysio.TriggerRA^[lTrigger-1];
        if ((lGap > l2Min) and (lGap < l2Max)) then begin //1 beat
           lPhysioIn.TriggerRA^[lTrigger2] := lTempPhysio.TriggerRA^[lTrigger-1]+(lgap / 2);
           inc(lTrigger2);
        end;
        if ((lGap > l3Min) and (lGap < l3Max)) then begin //2 beats
           lPhysioIn.TriggerRA^[lTrigger2] := lTempPhysio.TriggerRA^[lTrigger-1]+(lgap / 3);
           inc(lTrigger2);
           lPhysioIn.TriggerRA^[lTrigger2] := lTempPhysio.TriggerRA^[lTrigger-1]+(2*lgap / 3);
           inc(lTrigger2);
        end;
        lPhysioIn.TriggerRA^[lTrigger2] := lTempPhysio.TriggerRA^[lTrigger];
     end;
     ClosePhysio (lTempPhysio);
     lPhysioIn.InterpolatedTriggers := lnReplace;
     result := true;
end;

function ScalePhysioToTime(lPhysio: TPhysioT; lSamplesPerUnit: single): boolean;
var
   lScale: single;
   lTrigger: integer;
begin
     result := false;
     if (lPhysio.Triggers < 4) then begin
        showmessage('ScalePhysioToTR requires at least 4 triggers.');
        exit;
     end;
     if (lSamplesPerUnit <= 0) then begin
        showmessage('ScalePhysioToTime requires TR(sec) and samples/sec >0.');
        exit;
     end;
     lScale := 1/(lSamplesPerUnit); //use reciprocal: mults faster than divides
     for lTrigger := 1 to lPhysio.Triggers do
        lPhysio.TriggerRA^[lTrigger] := lPhysio.TriggerRA^[lTrigger] * lScale;
    result := true;
end;

procedure EnsureAscending(lPhysio: TPhysioT);
//check if order is correct - if not the sort...
//an alternative is to always sort, but this method is faster and less resource intensive for sorted data
var
   lPos: integer;
begin
   if lPhysio.Triggers < 2 then exit;
   for lPos := 2 to lPhysio.Triggers do begin
       if lPhysio.TriggerRA^[lPos] < lPhysio.TriggerRA^[lPos-1] then begin
          showmessage('Warning: input times are not in ascending order - data will be sorted.');
          qsort(1,lPhysio.Triggers,lPhysio.TriggerRA); //ensure trigger timings are in order...
          exit;
       end;
   end;
end;
                   
function ApplyPart( lFilename: string;lImgData: singleP; lBins,lVolVox,lSlices, lImgVol : integer; lTRsec: single): string;
var
   lPhysio: TPhysioT;
begin
     result := '';
     if not fileexists (lFilename) then exit;
     CreatePhysio(lPhysio);
     if UpCaseExt(lFilename) = '.TXT' then begin
         if not load3ColTxtPhysio(lFilename,lPhysio) then exit;
     end else
         if not loadSiemensPhysio(lFilename,lPhysio) then exit;
     EnsureAscending(lPhysio);
     QuartileTriggerSpacing(lPhysio);
     if not InterpolateGaps (lPhysio) then
         exit;
     if UpCaseExt(lFilename) <> '.TXT' then begin//export Siemens file as 3-column text
        ScalePhysioToTime(lPhysio,50); //50: siemens files use 50 Hz sampling -> convert to sec
        SaveTriggersAs3ColumnFSL(lPhysio,lFilename); //do this before TR conversion...
     end;
     ScalePhysioToTime(lPhysio,lTRsec); //Convert sec to volumes
     result := PARTool (lPhysio,lImgData,lTRsec,lVolVox,lSlices, lImgVol, lBins);
     ClosePhysio(lPhysio);
end;

end.