File Coverage

blib/lib/App/AFNI/SiemensPhysio.pm
Criterion Covered Total %
statement 55 196 28.0
branch 7 88 7.9
condition 2 37 5.4
subroutine 13 23 56.5
pod 7 14 50.0
total 84 358 23.4


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2              
3             package App::AFNI::SiemensPhysio;
4 1     1   66724 use strict;
  1         3  
  1         23  
5 1     1   5 use warnings;
  1         1  
  1         20  
6 1     1   4 use Carp;
  1         2  
  1         54  
7 1     1   487 use List::MoreUtils qw/minmax uniq/;
  1         10490  
  1         5  
8 1     1   918 use File::Basename;
  1         2  
  1         94  
9 1     1   434 use File::Copy 'mv';
  1         3957  
  1         75  
10 1     1   8 use feature 'say';
  1         2  
  1         323  
11              
12              
13             =pod
14              
15             =head1 NAME
16            
17             App::AFNI:SiemensPhysio - Physio from Siemens into format suitable for AFNI's RetroTS retroicor routine
18              
19             =head1 SYNOPSIS
20              
21             Get slice based respiration volume per time (RVT) regressors from physio collected on Siemens scanner
22              
23             my $p = SiemensPhysio->new({VERB=>1});
24             # read MR data (get times, TR, nslices)
25             # looks at all files in this directory with "dicom_hinfo"
26             $p->readMRdir('MRRaw/10824_20111108/rest_384x384.21/');
27              
28             # read pulse
29             $p->readPhysio('10824/20111108/wpc4951_10824_20111108_110811.puls');
30             # write card: $protocol_$sessionTime.puls.dat
31             $p->writeMRPhys;
32              
33             # read card
34             $p->readPhysio('10824/20111108/wpc4951_10824_20111108_110811.resp');
35             # write resp: $protocol_$sessionTime.resp.dat
36             $p->writeMRPhys;
37              
38             #
39             $p->retroTS('matlab')
40              
41             # we could get the raw data
42             # in this case, card was resp was loaded last
43             # thats what will be returned)
44             my @pval = $p->getMRPhys();
45              
46             =head1 DESCRIPTION
47              
48              
49              
50             =head2 Pipeline
51              
52             =over
53              
54             =item read Siemens physio files
55              
56             =item read timing from MR DICOM files
57              
58             =item snip physio files relative to MR
59              
60             =item prepare/run AFNI's RetroTS
61              
62             =back
63              
64              
65             =head2 prior art
66              
67              
68             =over
69              
70             =item https://cfn.upenn.edu/aguirre/public/exvolt/
71              
72             =item https://cfn.upenn.edu/aguirre/wiki/public:pulse-oximetry_during_fmri_scanning
73              
74             =back
75              
76             =cut
77              
78              
79              
80             =head2 new
81              
82             initialize object
83              
84             =head3 OPTIONS
85              
86             =over
87              
88             =item timetype
89              
90             MDH (default) or MPCU
91              
92             =item PhRate
93              
94             Physio sample rate
95             defaults to .2
96              
97             =item pulsStart and respStart
98             numeric sequence to be remove, also identifies stream type
99              
100             defaults:
101              
102             pulsStart =>'1 2 40 280'
103             respStart =>'1 2 20 2'
104              
105              
106             =item sliceOrder
107              
108             alt+z (default)
109             other options: alt-z,seq+z,seq-z,filename # slice order
110              
111             =item VERB
112              
113             set to true to be verbose, defaults false
114              
115             =item trustIdx
116              
117             don't check sample rate and index count against end-start time
118             none=> check both
119             MR => trust MR (TR)
120             phys=> trust physio (PhRate as set by init)
121             all => trust both
122              
123             Note: just have to match reg exp, so MRphys is same as all
124              
125             =back
126              
127             =cut
128              
129             sub new {
130 0     0 1 0 my $class = shift;
131 0         0 my $self = shift;
132             # default to MDH becaues MPCPU doesn't align to MR time
133 0         0 my %defaults = (
134             #run => 'matlab', # matlab|McRetroTs|none
135             timetyp => 'MDH', # MPCU|MDH
136              
137             PhRate =>'.02',
138             # parameters of the acquisition.
139             # third is ticktime, but unlcear what the transfor is to get to freq/samplerate
140             pulsStart =>'1 2 40 280',
141             respStart =>'1 2 20 2',
142             sliceOrder => 'alt+z',
143             MRDiscardNum => 0, # how many MR volues to discard
144             VERB=>0,
145             trustIdx=>'none',
146             );
147             # use defaults when we didn't set anything
148 0         0 for my $k (keys %defaults) {
149 0 0 0     0 $self->{$k} = $defaults{$k} unless $self and $self->{$k};
150             }
151              
152 0         0 return bless $self, $class;
153             }
154              
155             =head2 readPhysio
156              
157             after intializing p, provide a file name
158              
159             $p->readPhysio('10824/20111108/wpc4951_10824_20111108_110811.puls');
160              
161             =head3 input file format
162              
163             1 2 40 280 ... [long space delimn. list of measurements, maybe 5000 for trigger?]
164             ECG Freq Per: 0 0
165             PULS Freq Per: 74 807
166             RESP Freq Per: 20 2860
167             EXT Freq Per: 0 0
168             ECG Min Max Avg StdDiff: 0 0 0 0
169             PULS Min Max Avg StdDiff: 527 1586 828 4
170             RESP Min Max Avg StdDiff: 2380 6700 3477 86
171             EXT Min Max Avg StdDiff: 0 0 0 0
172             NrTrig NrMP NrArr AcqWin: 0 0 0 0
173             LogStartMDHTime: 66439690
174             LogStopMDHTime: 71116595
175             LogStartMPCUTime: 66439512
176             LogStopMPCUTime: 71114802
177             6003
178              
179             =cut
180              
181             sub readPhysio {
182 0     0 1 0 my $self=shift;
183 0         0 my $fname=shift;
184              
185 0 0 0     0 croak "cannot open $fname"
186             unless $fname and open my $fh, '<', $fname;
187              
188 0         0 my $pulsStart = $self->{pulsStart};
189 0         0 my $respStart = $self->{respStart};
190 0         0 my $timetyp = $self->{timetyp};
191              
192             # first line is physio measures
193             # puls and resp have unique start sequences
194 0         0 my $values = <$fh>;
195              
196 0 0       0 croak "$fname does not start with expected resp or pulse prefix sequence values"
197             unless $values=~s/^((?$pulsStart)|(?$respStart))\W*//;
198              
199             # we can get the type by which regex we matched
200 1 0   1   476 $self->{ptype} = join('',map { $+{$_}?$_:"" } qw/puls resp/);
  1         364  
  1         1072  
  0         0  
  0         0  
201              
202             # break values by whitespace, remove 5000 and above
203             # 5000 is a scanner trigger, 5003 is end
204 0         0 $self->{measures} = [ grep { $_ < 5000 } split(/\W+/,$values) ];
  0         0  
205              
206             # get settings matching
207 0         0 my %settings;
208 0         0 while($_=<$fh>){
209             # remove dos chars
210 0         0 s/ //g;
211              
212             # parse settings, primarily for start and end time
213 0 0       0 $settings{$1} = $2 if m/(.*):\W+(.*)/;
214              
215             # check file integrity; assume last line is 6003
216 0 0 0     0 croak "corrupt file $fname. does not end with 6003"
217             if eof and ! m/^6003$/;
218            
219             }
220              
221              
222              
223             # get start and end from settings
224 0 0 0     0 if(!$settings{"LogStart${timetyp}Time"} or !$settings{"LogStop${timetyp}Time"}) {
225 0         0 croak "Cannot find Log(Start|Stop)*Time in $fname";
226             }
227              
228 0         0 $self->{physStart} = $settings{"LogStart${timetyp}Time"}/1000;
229 0         0 $self->{physEnd} = $settings{"LogStop${timetyp}Time"}/1000;
230              
231              
232              
233            
234             # reset rate if its only off by a very small amount
235             # and we don't trust the sample rate we provided
236 0         0 my $newrate = abs($self->{physStart}- $self->{physEnd})/$#{$self->{measures}};
  0         0  
237             $self->{PhRate} = $newrate
238             if abs($newrate-$self->{PhRate}) < .00001 and
239 0 0 0     0 $self->{trustIdx}!~/All|Phys/i;
240              
241              
242 0         0 say "file is $self->{ptype} with $#{$self->{measures}} samples, " ,
243             "$self->{physStart}s - $self->{physEnd}s (",
244             sprintf("%.2f",($self->{physEnd}-$self->{physStart})/60),
245             "min), sample rate adjusted to $self->{PhRate}"
246 0 0       0 if $self->{VERB};
247              
248             # does the time match the sample rate and number of samples
249             timeCheck($self->{physStart},
250             $self->{physEnd},
251 0         0 $#{$self->{measures}},
252 0 0       0 $self->{PhRate} ) unless $self->{trustIdx}=~/All|Phys/i;
253             }
254              
255              
256             =head2 readMRdir
257              
258             after intializing p, read in MR info from raw DICOM directory
259              
260             $p->readMRdir('MRRaw/10824_20111108/rest_384x384.21/');
261              
262             sets
263              
264             =over
265              
266             =item timing (MRstart and MRend)
267              
268             =item protcol info (protocol,TR,ET,nslices,Series)
269              
270             =back
271              
272             =head3 Example Info
273              
274             dicom header info
275              
276             dicom_hdr MRRaw/10824_20111108/rest_384x384.21/MR* |egrep 'protocol|acquisition Time|Echo Time|Repetition Time' -i
277             0008 0031 14 [620 ] // ID Series Time//164627.359000
278             0008 0032 14 [642 ] // ID Acquisition Time//164932.315000
279             0018 0080 4 [1418 ] // ACQ Repetition Time//1500
280             0018 0081 2 [1430 ] // ACQ Echo Time//29
281             0018 1030 4 [1612 ] // ACQ Protocol Name//rest
282             0019 100a 2 [1788 ] // // 29
283              
284             shortend to
285              
286             dicom_hinfo -tag 0008,0032 0008,0031 0018,0080 0018,0081 0018,1030 MR*
287              
288             =cut
289              
290             # sets MRstart MRend protocol TR ET protocol nslices Series
291             sub readMRdir {
292 0     0 1 0 my $self=shift;
293 0         0 my $dicomdir=shift;
294 0 0       0 croak "$dicomdir is not a directory!" if ! -d $dicomdir;
295 0         0 my $dcmcmd = "dicom_hinfo -tag 0008,0031 0008,0032 0018,0080 0018,0081 0018,1030 0019,100a $dicomdir/*";
296 0         0 our @returns = qw/Filename Series AcqTime TR ET protocol nslice/;
297             # N.B. nslices/"Number Of Images In Mosaic" (0019,100a) is Siemens specific
298             # which is okay, because thats the kind of physio we have
299              
300              
301             # the index at which we can find the item we want
302             sub getidx {
303 0     0 0 0 my $name=shift;
304 0         0 return (grep { $returns[$_] eq $name } (0..$#returns))[0];
  0         0  
305             }
306              
307             # @v is an element for each dcm (line of output)
308 0 0       0 my @v=`$dcmcmd` or croak "could not run $dcmcmd";
309              
310              
311             # make each line an array
312             # so we have an array of arrays
313             # v[0] is all info on first dicom
314             # v[0][0] is the first dicom's file name
315 0         0 @v= map { [split / /] } @v;
  0         0  
316            
317             # Sort by acq time (just in case file names are out of order)
318             # only needed to ensure trunction takes out the right dicoms
319 0         0 my $ati=getidx('AcqTime');
320 0         0 @v = sort {$a->[$ati] <=> $b->[$ati]} @v;
  0         0  
321              
322             # Truncate dicom list if we have volumes to discard
323             # for some multi-band protocols, the first dicom is junk
324             # for older protocols, we have to manually discard volumes
325             # -- eitherway we dont want them
326 0 0       0 @v=@v[($self->{MRDiscardNum})..$#v] if($self->{MRDiscardNum});
327              
328             # record some constant settings/values
329 0         0 for my $vals (qw/protocol TR ET Series nslice/) {
330 0         0 my $vidx=getidx($vals);
331             # make sure it's constant
332 0         0 my @allvals = uniq(map {$_->[$vidx]} @v);
  0         0  
333 0 0       0 croak "$vals is not constant: ".join(",",@allvals) if $#allvals>0;
334              
335 0         0 chomp($allvals[0]);
336 0         0 $self->{$vals} = $allvals[0];
337             }
338 0         0 $self->{nDcms} = $#v+1;
339 0         0 $self->{TR} /=1000;
340            
341              
342              
343             # Acquistion index
344 0         0 my $ATidx= getidx('AcqTime');
345              
346             # find max and min acq time from all MR*s
347 0         0 my ($starttime, $endtime) = minmax( map {$_->[$ATidx] } @v);
  0         0  
348              
349              
350             ## set start and end
351 0         0 $self->{MRstart} = getMRAcqSecs($starttime);
352 0         0 $self->{MRend} = getMRAcqSecs($endtime);
353              
354             say "MR starts $self->{MRstart} (DICOM $starttime) and ends $self->{MRend} (DICOM $endtime)"
355 0 0       0 if $self->{VERB};
356              
357             timeCheck($self->{MRstart},
358             $self->{MRend},
359             $self->{nDcms},
360 0 0       0 $self->{TR}) unless $self->{trustIdx}=~/All|MR/i;
361             }
362              
363             =head2 readBIDSJson
364             read TR MRstart and crate MRend from BIDS style json output
365             (e.g. created by dcm2niix).
366              
367             must already have nDcms (number of volumes in 4d) set
368              
369             =cut
370             sub readBIDSJson() {
371 1     1   559 use JSON qw/decode_json/;
  1         8455  
  1         6  
372 0     0 1 0 my $self=shift;
373 0         0 my $jsonfile=shift;
374 0 0       0 open my $JS, '<', $jsonfile or
375             croak "could not open BIDS JSON '$jsonfile'";
376 0 0       0 my $data = decode_json(do{local $/; <$JS>}) or
  0         0  
  0         0  
377             croak "could not parse JSON in '$jsonfile'";
378              
379 0         0 $self->{'MRstart'} = getMRAcqSecs($data->{'AcquisitionDateTime'});
380             # ($+{HH}*60*60) + ($+{MM}*60) + $+{SS} ..
381 0         0 $self->{TR} = $data->{RepetitionTime}; # in seconds. eg 1.5
382 0         0 $self->{MRend} = $self->{MRstart} + $self->{nDcms}*$self->{TR};
383             # timeCheck is meaningless -- MRend is derived the same way as the check
384            
385             # for output, set protocol and series
386 0         0 $self->{protocol} = $data->{ProtocolName};
387 0         0 $self->{Series} = $data->{SeriesDescription};
388             # Also have meaningful attributes
389             # PhaseEncodingDirection SliceTiming
390             }
391              
392             =head2 writeMRPhys
393              
394             write phys during MR to file
395             works on most recently loaded physio file
396              
397             $p->writeMRPhys
398              
399             =over
400              
401             =item use getMRphys to get values
402              
403             =item use writeDat to write values
404              
405             =back
406              
407             =cut
408              
409             sub writeMRPhys {
410 0     0 1 0 my $self=shift;
411 0         0 my $outfile = shift;
412 0 0       0 $outfile="$self->{protocol}_$self->{Series}.$self->{ptype}.dat" if ! $outfile;
413            
414             # if we provided a prefix
415 0 0       0 if($self->{prefix}){
416             # get dir
417 0         0 my $bn=dirname($self->{prefix});
418 0 0       0 croak "prefix directory ($bn) does not exist!" if ! -d $bn;
419 0         0 $outfile=$self->{prefix}.$outfile;
420             }
421              
422 0         0 $self->{dat}->{$self->{ptype}}=$outfile;
423              
424 0         0 my @pvals = $self->getMRPhys;
425              
426 0 0       0 say "saving to $outfile" if $self->{VERB};
427 0         0 writeDat($outfile,@pvals)
428             }
429              
430              
431              
432              
433             =head2 retroTS
434              
435             This is kludgy code hacked together and untested :)
436             =over
437              
438             =item use a bunch of hacks to find the matlab binary
439              
440             =item construct a matlab call using options in self object
441              
442             =item execute matlab or McRetroTS
443              
444             =item move outputs to fit local naming convention
445              
446             =back
447              
448              
449              
450             get/run command to get Resp. Vol./Time (RVT) via AFNI's retroTS a la http://www.ncbi.nlm.nih.gov/pmc/articles/PMC2715870/
451             MUST have already read MR and written card and resp dat files
452              
453             $p->retroTS('matlab')
454              
455             how this step is handled is defined by the first argument
456              
457              
458              
459             =over
460              
461             =item matlab: use RetroTS.m
462              
463             =item McRetroTs: use compiled matlab verson of RetroTS.m
464              
465             =item show: print commands for both matlab and McRetroTS
466              
467             =item none: do nothing (why'd you call me then!?)
468              
469             =back
470              
471              
472             =head3 External Commands
473              
474             see
475              
476             =over
477              
478             =item http://afni.nimh.nih.gov/afni/matlab/
479              
480             =item http://afni.nimh.nih.gov/sscc/dglen/McRetroTS
481              
482             =item http://fieldtrip.googlecode.com/svn/trunk/external/afni/RetroTS.m
483              
484             =back
485              
486             if using matlab+retroTS, the path to retroTS.m should be in your MATLABPATH
487             export MATLABPATH="$HOME/afni_matlab/matlab/:$MATLABPATH"
488              
489             =cut
490              
491              
492             sub retroTS {
493 0     0 1 0 my $self=shift;
494 0         0 my $runtype=shift;
495 0 0 0     0 return if $runtype and $runtype =~ /none/i;
496              
497             # we need to have both data types
498             croak "need writeMRPhys for both puls and resp"
499 0 0 0     0 if ! $self->{dat} || ! -e $self->{dat}->{resp} || ! -e $self->{dat}->{resp};
      0        
500              
501              
502             # default to using matlab
503 0         0 my $matlabbin="matlab";
504              
505             # find where matlab script points to
506             #my $acutalmatlab=`perl -ne 'print \$& if /^.*matlab /' \`which matlab\``;
507             #$matlabbin=$acutalmatlab if $acutalmatlab;
508              
509             # or use env MATLABBIN
510 0 0       0 $matlabbin= $ENV{MATLABBIN} if $ENV{MATLABBIN};
511              
512              
513             my %params = (
514             "Opts.Respfile" => "'".$self->{dat}->{resp}."'", # Respiration data file
515             "Opts.Cardfile" => "'".$self->{dat}->{puls}."'", # Cardiac data file
516             "Opts.PhysFS" => 1/$self->{PhRate}, # Physioliogical signal sampling frequency in Hz.
517             "Opts.Nslices" => $self->{nslice}, # Number of slices
518             "Opts.VolTR" => $self->{TR}, # Volume TR in seconds
519 0         0 "Opts.SliceOrder" => "'".$self->{sliceOrder}."'" # ['alt+z']/'alt-z'/'seq+z'/'seq-z'/'Custom'/filename.1D
520             );
521              
522             # McRetroTS Respdatafile ECGdatafile VolTR Nslices SamplingFreq(PhysFS) ShowGraphs
523 0         0 my @mcrts = qw/Opts.Respfile Opts.Cardfile Opts.VolTR Opts.Nslices Opts.PhysFS/;
524 0         0 my $mccmd = "McRetroTs @params{@mcrts}";
525 0 0       0 say $mccmd if $runtype !~ /matlab|McRetroTs/g ;;
526              
527              
528             # if have matlab and singal toolbox, can use this
529 0         0 my $cmd = join("; ", map { join("=",$_,$params{$_}) } keys %params);
  0         0  
530 0         0 $cmd .= "; Opts.ShowGraphs=0;Opts.Quiet=0;"; # turn off graphs, turn on verbose
531 0         0 $cmd .= " rts = RetroTS(Opts)";
532              
533             # we should wrap matlab up in a try+quit so we dont hang in ML command window on a failure
534 0         0 my $matlabwrap= qq/$matlabbin -nodisplay -r "try; $cmd; catch err; err, exit(1); end; rts, quit;"/;
535              
536 0 0       0 say $matlabwrap if $runtype !~ /matlab|McRetroTs/i;
537             # eg
538             # matlab -nodisplay -r "try; Opts.Cardfile='rest_164627.359000.puls.dat'; Opts.VolTR=1.5; Opts.Nslices=29; Opts.SliceOrder='alt+z'; Opts.PhysFS=50.0074711455304; Opts.Respfile='rest_164627.359000.resp.dat'; rts = RetroTS(Opts); catch; exit(666); end; quit;"
539              
540             # with either command, the original output name will be "oba.slibase.1D"
541             # change that to our basename (assume resp and puls have same basename, use one from resp)
542 0         0 my $outputname = $self->{dat}->{resp};
543 0         0 $outputname =~ s/.resp.dat$/.slibase.1D/;
544            
545             # or rename to specified input
546             #my $outputname=shift if $#_;
547            
548            
549 0         0 my $runcmd="";
550 0 0       0 if($runtype =~ /matlab/i){
    0          
551 0         0 $runcmd=$matlabwrap
552             }elsif($runtype =~ /McRetroTs/i){
553 0         0 $runcmd=$mccmd;
554             }
555              
556              
557 0 0       0 if($runcmd) {
558             # retroTS just dumpes out oba.slibase.1D whereever it is run from
559             # ..so run from where we want to save the file
560 0         0 chdir dirname($outputname);
561 0         0 system($runcmd);
562              
563             # check if we have the expected output
564 0 0       0 if(! -e "oba.slibase.1D" ){
565 0         0 croak "failed to run\n\n: $runcmd\n\n";
566             } else {
567             # move file to output name
568 0 0       0 mv "oba.slibase.1D", $outputname or
569             croak "could not move oba.slibase.1D to $outputname";
570             }
571 0         0 print "$outputname # saved as final output " , `date +%F\\ %H:%M` , "\n";
572             }
573              
574              
575             }
576              
577              
578              
579             #################
580             #### helpers ####
581             #################
582              
583              
584             # do start end and tr times make sense?
585             sub timeCheck {
586 3     3 0 7 my ($start,$end,$n,$tau) = @_;
587              
588             #my $maxDiffSec=$tau*2;
589 3         5 my $maxDiffSec=$tau;
590 3         5 my $dur=$end-$start;
591 3         4 my $ideal=$n *$tau;
592              
593             # start and end time are sane
594 3 50       8 croak "time starts ($start) before or on end time ($end)!"
595             if($end<=$start);
596              
597             # samples * sample rate == actual duration
598 3         25 my $offby = sprintf('%.3f',$ideal-$dur);
599 3         13 my $offbyN = sprintf('%.0f',$offby/$tau);
600 3 100       227 croak "off by $offby s ($offbyN samples) >$maxDiffSec diff: $n samples at $tau should be $ideal secs not $dur ($end - $start)"
601             if(abs($offby) > $maxDiffSec);
602              
603 2         9 return 1;
604             }
605              
606             # DICOM Acq Time is fmt like HHMMSS.SS (172745.487500)
607             # JSON BIDs AcqTime like 2018-06-15T17:27:45.487500
608             sub getMRAcqSecs {
609 5     5 0 79 $_=shift;
610 5 50 33     32 m/^(?\d{2})(?\d{2})(?\d{2}\.\d+)$/ or
611             m/^\d{4}-\d{2}-\d{2}T(?\d{2}):(?\d{2}):(?\d{2}\.\d+)$/ or
612             croak "$_ from MR time does not look like HHMMSS.sssss or like YYYY-MM-DDTHH:MM:SS.sssss";
613              
614 5         55 my $secs = ($+{HH}*60*60) + ($+{MM}*60) + $+{SS} ;
615 5         28 return $secs;
616             }
617              
618              
619              
620             # returns vector of phys for the timing of an MR file
621             sub getMRPhys {
622 0     0 0 0 my $self=shift;
623             my ($s,$e) = sandwichIdx(
624 0         0 [@{$self}{qw/physStart physEnd/}],
625 0         0 [@{$self}{qw/MRstart MRend/}],
626 0         0 $#{$self->{measures}},
627 0         0 $self->{PhRate} );
628 0         0 $self->{MRstartIdx} = $s;
629 0         0 $self->{MRendIdx} = $e;
630              
631             ## print out where data is coming from/how sandwiching worked
632 0 0       0 $self->sayIndex if $self->{VERB};
633              
634 0         0 my @pval = @{$self->{measures}}[$s..$e];
  0         0  
635              
636 0 0       0 croak "no pvals!? bad indexes?" if $#pval < 0;
637 0         0 return @pval;
638             }
639              
640              
641              
642             # write values to file
643             sub writeDat {
644 0     0 1 0 my ($outname,@pvals) = @_;
645 0 0       0 croak "do not have a file name to save data" if(!$outname);
646 0 0       0 croak "have 1 or fewer values to write" if($#pvals < 1);
647              
648 0 0       0 open my $OH, '>', $outname or croak "could not open $outname to save physio";
649 0         0 print $OH join("\n",@pvals);
650 0         0 close $OH;
651             }
652              
653             #
654             # get the start and end index of the meat given start,end time of meat and bread
655             # need: start,end pairs in array ref + size and sample rate
656             # sandwichIdx([bread start,end],[meat start, end], size, rate)
657             #
658             sub sandwichIdx {
659 1     1 0 3 my ($bread, $meat, $n, $r) = @_;
660              
661             # make sure the meat is inside the bread
662 1         2 my ($MRstart,$MRend) = @{$meat};
  1         3  
663 1         2 my ($physStart,$physEnd) = @{$bread};
  1         2  
664              
665 1 50       4 croak sprintf("ERROR: MR starts ($MRstart) %.2f min before physio ($physStart)",
666             ($physStart-$MRstart)/60)
667             if( $MRstart < $physStart );
668              
669 1 50       4 croak sprintf("ERROR: MR ends ($MRend) %.02f min after physio ($physEnd)",
670             ($MRend-$physEnd)/60)
671             if( $MRend > $physEnd);
672              
673             # calc start and end by adding sandwitch top (start as ref)
674 1         5 my $sIdxS = timeToSamples($bread->[0],$meat->[0],$r);
675 1         11 my $eIdxS = timeToSamples($meat->[0] ,$meat->[1],$r ) + $sIdxS;
676              
677             # calc start and end by subt sandwitch bottom (use end as ref)
678 1         3 my $eIdxE = $n - timeToSamples($bread->[1],$meat->[1],$r );
679 1         3 my $sIdxE = $eIdxE - timeToSamples($meat->[0], $meat->[1],$r );
680              
681             # are the two the same?
682 1 50 33     6 carp "Inconsistant index calculation. ".
683             "Using start time vs end time as ref grabs different sample of measurements\n".
684             "(ref=start) $sIdxS to $eIdxS VS $sIdxE to $eIdxE (ref=end) @ $r secs/sample\n"
685             if $sIdxS != $sIdxE || $eIdxS != $eIdxE;
686              
687 1         4 return ($sIdxS,$eIdxS);
688             }
689              
690             # the diff of two times over the sampling rate gives the number of samples
691             sub timeToSamples {
692 11     11 0 17 my ($start, $end, $rate) = @_;
693 11         59 return sprintf( '%.0f', abs($end - $start)/$rate ) ;
694             }
695              
696             ## print whats up
697             sub sayIndex {
698 0     0 0   my $self=shift;
699              
700             # print out start and stop index for ps and pe
701 0           my $ps = timeToSamples($self->{physStart},$self->{physStart},$self->{PhRate});
702 0           my $pe = timeToSamples($self->{physStart},$self->{physEnd},$self->{PhRate});
703 0   0       my $s = $self->{MRstartIdx} || undef;
704 0   0       my $e = $self->{MRendIdx} || undef;
705             # lets talk about what we have
706 0           say "(ps ) $self->{physStart} | MR $self->{MRstart} $self->{MRend} | $self->{physEnd} (pe) ";
707 0 0 0       say "(sidx) $ps | MR $s $e | $pe (eidx)" if $s and $e;
708              
709             }
710              
711              
712              
713             1;
714              
715