File Coverage

blib/lib/Lab/Data/Analysis.pm
Criterion Covered Total %
statement 345 625 55.2
branch 140 368 38.0
condition 25 99 25.2
subroutine 20 24 83.3
pod 12 12 100.0
total 542 1128 48.0


line stmt bran cond sub pod time code
1             package Lab::Data::Analysis;
2             #ABSTRACT: Analyze data from 'Trace' files
3             $Lab::Data::Analysis::VERSION = '3.881';
4 2     2   2711 use v5.20;
  2         17  
5              
6 2     2   11 use strict;
  2         4  
  2         49  
7 2     2   424 use Clone qw(clone);
  2         2501  
  2         125  
8 2     2   14 use warnings;
  2         4  
  2         47  
9 2     2   10 use Carp;
  2         4  
  2         121  
10 2     2   665 use Data::Dumper;
  2         6892  
  2         14199  
11              
12             # default config values, copied to $self->{CONFIG} initially
13              
14             our $DEFAULT_CONFIG = {
15             use_ftell => 0, # use ftell/fsetpos for positioning
16             clone_headers => 1, # clone run/file headers into events
17             combined_gpib => 0, # also combine GPIB from all streams
18             };
19              
20             our $_DefaultAnalyzer = {
21             1 => { # order in which to check for match
22             MATCH =>
23             '(?i)^Lab::Instrument::(TDS|TPS|TBS)(1\d\d\d|2\d\d\d?)(B|C)?::',
24             TYPE => 'Lab::Data::Analysis::TekTDS',
25             COMMENT =>
26             'Tektronix TDS/TPS/TBS 1000 and 2000 series, TDS200 series, oscilloscopes'
27             },
28             2 => {
29             MATCH => '(?i)^Lab::Instrument::DPO4\d\d\d(A-C)?::',
30             TYPE => 'Lab::Data::Analysis::TekDPO',
31             COMMENT => 'Tektronix DPO4000 series oscilloscopes',
32             },
33              
34             3 => {
35             MATCH => '(?i)^Lab::Instrument::WR\d+::',
36             TYPE => 'Lab::Data::Analysis::WaveRunner',
37             COMMENT => 'LeCroy WaveRunner series oscilloscopes',
38             },
39             };
40              
41             our $_LOADED = {};
42              
43              
44             sub new {
45 1     1 1 1393 my $proto = shift;
46 1   33     8 my $class = ref($proto) || $proto;
47 1         3 my $self = {};
48 1         3 bless $self, $class;
49              
50 1         5 my ( $file, $tail ) = $self->_check_args( \@_, qw(file) );
51 1         7 $self->{FILE} = undef;
52 1         3 $self->{FH} = undef;
53 1         3 $self->{INDEX} = undef;
54 1         3 $self->{CONFIG} = {};
55 1         3 $self->{FILEHEADER} = undef;
56 1         3 $self->{RUNHEADER} = undef;
57              
58             # set up the default config variables
59 1         2 foreach my $k ( keys( %{$DEFAULT_CONFIG} ) ) {
  1         6  
60 3         8 $self->{CONFIG}->{$k} = $DEFAULT_CONFIG->{$k};
61             }
62              
63             # override default config with user-provided parameters
64 1         3 foreach my $k ( keys( %{$tail} ) ) {
  1         3  
65 0 0       0 next unless exists $self->{CONFIG}->{$k};
66 0         0 $self->{CONFIG}->{$k} = $tail->{$k};
67             }
68              
69 1 50       7 $self->open($file) if defined($file);
70              
71 1         24 return $self;
72             }
73              
74             ################ internal routines ########################
75              
76             # calling argument parsing; this is an extension of the
77             # _check_args and _check_args_strict routines in Instrument.pm,
78             # allowing more flexibility in how routines are called.
79             # In particular routine(a=>1,b=>2,..) and
80             # routine({a=>1,b=>2,..}) can both be used.
81              
82             # note: if this code does not properly recognize the syntax,
83             # then you have to use the {key=>value...} form.
84              
85             # calling:
86             # ($par1,$par2,$par3,$tail) = $self->_check_args(\@_,qw(par1 par2 par3));
87             # or, for compatibility:
88             # ($par1,$par2,$par3,$tail) = $self->_check_args(\@_,[qw(par1 par2 par3)]);
89              
90             # can also call without the $self-> pointer, since not used.
91              
92             sub _check_args {
93 10     10   16 my $self = shift;
94 10         17 my $args;
95              
96 10 100       42 if ( ref($self) eq 'ARRAY' ) {
97 9         16 $args = $self;
98             }
99             else {
100 1         2 $args = shift;
101             }
102 10         22 my $params = [@_];
103 10 100       33 $params = $params->[0] if ref( $params->[0] ) eq 'ARRAY';
104 10         17 my $arguments = {};
105              
106 10 50 66     15 if ( $#{$args} == 0 && ref( $args->[0] ) eq 'HASH' ) { # case 3
  10         38  
107 0         0 %{$arguments} = ( %{ $args->[0] } );
  0         0  
  0         0  
108             }
109             else {
110 10         20 my $simple = 1;
111 10 100       14 if ( $#{$args} & 1 == 1 ) { # must have even # arguments
  10         27  
112 8         13 my $found = {};
113 8         18 for ( my $j = 0; $j <= $#{$args}; $j += 2 ) {
  14         35  
114 6 50       18 if ( ref( $args->[$j] ) ne '' ) { # a ref for a key? no
115 0         0 $simple = 1;
116 0         0 last;
117             }
118 6         11 foreach my $p ( @{$params} ) { # named param
  6         10  
119 14 100       33 $simple = 0 if $p eq $args->[$j];
120             }
121 6 50       15 if ( exists( $found->{ $args->[$j] } ) ) { # key used 2x? no
122 0         0 $simple = 1;
123 0         0 last;
124             }
125 6         19 $found->{ $args->[$j] } = 1;
126             }
127             }
128              
129 10 100       23 if ($simple) { # case 1
130 6         9 my $i = 0;
131 6         7 my $j = 0;
132 6         9 foreach my $arg ( @{$args} ) {
  6         14  
133 2 50       3 if ( defined @{$params}[$i] ) {
  2         5  
134 2         4 $arguments->{ @{$params}[$i] } = $arg;
  2         5  
135 2         5 $i++;
136             }
137             else {
138 0         0 $arguments->{"_tail$j"} = $arg;
139 0         0 $j++;
140             }
141             }
142             }
143             else { # case 2
144 4         4 %{$arguments} = ( @{$args} );
  4         10  
  4         9  
145             }
146             }
147              
148 10         19 my @return_args = ();
149              
150 10         16 foreach my $param ( @{$params} ) {
  10         19  
151 17 100       31 if ( exists $arguments->{$param} ) {
152 8         14 push( @return_args, $arguments->{$param} );
153 8         16 delete $arguments->{$param};
154             }
155             else {
156 9         14 push( @return_args, undef );
157             }
158             }
159              
160 10         20 push( @return_args, $arguments );
161              
162 10 50       18 if (wantarray) {
163 10         37 return @return_args;
164             }
165             else {
166 0         0 return $return_args[0];
167             }
168             }
169              
170             sub _check_args_strict {
171 1     1   3 my $self = shift;
172 1         2 my $args;
173              
174 1 50       4 if ( ref($self) eq 'ARRAY' ) {
175 1         3 $args = $self;
176             }
177             else {
178 0         0 $args = shift;
179             }
180 1         3 my $params = [@_];
181 1 50       4 $params = $params->[0] if ref( $params->[0] ) eq 'ARRAY';
182              
183 1         3 my @result = _check_args( $args, $params );
184              
185 1         3 my $num_params = @result - 1;
186              
187 1         19 for ( my $i = 0; $i < $num_params; ++$i ) {
188 1 50       6 if ( not defined $result[$i] ) {
189 0         0 croak("missing mandatory argument '$params->[$i]'");
190             }
191             }
192              
193 1 50       4 if (wantarray) {
194 1         5 return @result;
195             }
196             else {
197 0         0 return $result[0];
198             }
199             }
200              
201             # get file position, make use of CONFIG option
202              
203             sub _tell {
204 380     380   586 my $self = shift;
205 380 50       840 croak("no file open") unless defined $self->{FH};
206 380 50       783 if ( $self->{CONFIG}->{use_ftell} ) {
207 0         0 return $self->{FH}->getpos();
208             }
209             else {
210 380         933 return $self->{FH}->tell();
211             }
212             }
213              
214             # set file absolute position, making use of CONFIG option
215             # ignores any 'whence' parameter
216              
217             sub _seek {
218 9     9   16 my $self = shift;
219 9         13 my $p = shift;
220 9 50       22 croak("no file open") unless defined $self->{FH};
221 9 50       23 if ( $self->{CONFIG}->{use_ftell} ) {
222 0         0 return $self->{FH}->setpos($p);
223             }
224             else {
225 9         34 return $self->{FH}->seek( $p, 0 );
226             }
227             }
228              
229              
230             sub open {
231 1     1 1 2 my $self = shift;
232 1         5 my ( $file, $tail ) = _check_args_strict( \@_, 'file' );
233              
234 1 50       57 croak("input file '$file' does not exist") unless -e $file;
235 1 50       17 croak("input file '$file' not readable") unless -r $file;
236              
237 1 50       6 $self->{FH}->close() if defined $self->{FH};
238              
239 1         16 $self->{FH} = IO::File->new( $file, "r" );
240             croak("unable to open file '$file' for reading")
241 1 50       1670 unless defined $self->{FH};
242 1         3 $self->{FILE} = $file;
243 1         5 $self->{FILE_BEGIN} = $self->_tell(); # beginning position
244 1         11 $self->{INDEX} = undef;
245 1         7 $self->{FSTAT} = [ ( $self->{FH}->stat() ) ];
246              
247             }
248              
249              
250             sub rewind {
251 0     0 1 0 my $self = shift;
252 0         0 my ($tail) = _check_args( \@_ );
253              
254 0 0       0 croak("tracefile not opened") unless defined $self->{FH};
255              
256 0         0 $self->_seek( $self->{FILE_BEGIN} );
257             }
258              
259              
260             sub MakeIndex {
261 1     1 1 2412 my $self = shift;
262 1         5 my ($tail) = _check_args( \@_ );
263              
264 1         3 my $fh = $self->{FH};
265 1 50       4 croak("no tracefile opened") unless defined $fh;
266              
267 1         4 my $iloc = $self->_tell();
268 1         7 my $p = $self->{FILE_BEGIN};
269              
270 1         4 $self->_seek($p); # beginning
271 1         16 $self->{INDEX} = {};
272 1         3 $self->{INDEX}->{RUN} = {};
273 1         3 $self->{INDEX}->{STREAM} = {};
274 1         2 $self->{RUN} = undef;
275 1         3 $self->{EVENT} = undef;
276 1         2 my $run = 0;
277 1         2 my $n = 1;
278              
279 1         20 while (<$fh>) {
280 152         319 my $pnext = $self->_tell();
281 152 50       1361 if ( !/^(\d+)(.)(.*)\s*$/ ) {
282 0         0 carp("Error parsing trace file at line $n: $_");
283 0         0 $p = $pnext;
284 0         0 next;
285             }
286 152         461 my $str = $1 + 0;
287 152         249 my $cc = $2;
288 152         5510 my $rest = eval($3);
289              
290 152 100       557 if ( !exists( $self->{INDEX}->{STREAM}->{$str} ) ) {
291 2         7 $self->{INDEX}->{STREAM}->{$str} = {};
292 2         7 $self->{INDEX}->{STREAM}->{$str}->{COMMENT} = {};
293 2         6 $self->{INDEX}->{STREAM}->{$str}->{NUMBER} = $str;
294             }
295              
296 152 100 100     455 if ( $cc ne '<' && $cc ne '>' ) {
297              
298 6 100       17 if ( $cc eq '*' ) {
    50          
299 5 100       41 if ( $rest =~ /^Lab::[\w\:]+::new\s/i ) {
    100          
    100          
    50          
300 1         5 $self->{INDEX}->{STREAM}->{$str}->{CONNECT} = $rest;
301             }
302             elsif ( $rest =~ /^start\s+run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i )
303             {
304 1         4 $run = $1 + 0;
305 1         4 my $t0 = $2;
306             carp("duplicate run '$run' at line $n")
307 1 50       4 if exists $self->{INDEX}->{RUN}->{$run};
308 1 50       5 if ( !defined( $self->{RUN} ) ) {
309 1         3 $self->{RUN} = $run;
310 1         3 $self->{EVENT} = 0;
311             }
312 1         3 $self->{INDEX}->{RUN}->{$run} = {};
313 1         3 $self->{INDEX}->{RUN}->{$run}->{POSITION} = $p;
314 1         4 $self->{INDEX}->{RUN}->{$run}->{STARTTIME} = $t0;
315 1         3 $self->{INDEX}->{RUN}->{$run}->{EVENT} = {};
316             }
317             elsif ( $rest
318             =~ /^event\s*(\d+)\s*run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i ) {
319 2 50       10 carp("event found outside of its run at line $n")
320             unless $run == $2 + 0;
321 2         5 my $event = $1 + 0;
322 2         5 my $te = $3;
323             carp("duplicate event '$event', run '$run' at line $n")
324             if exists $self->{INDEX}->{RUN}->{$run}->{EVENT}
325 2 50       7 ->{$event};
326 2         9 $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event} = {};
327             $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event}
328 2         6 ->{POSITION} = $p;
329             $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event}->{TIME}
330 2         5 = $te;
331             }
332             elsif ( $rest =~ /^stop\s+run(\d+)\s.*\\?\@\s*([\d\.]+)/i ) {
333 1 50       7 carp("run # mismatch at start/stop at line $n")
334             unless $run == $1 + 0;
335 1         3 $run = $1 + 0;
336 1         4 my $t1 = $2;
337 1         3 $self->{INDEX}->{RUN}->{$run}->{STOPTIME} = $t1;
338 1         3 $run = 0;
339             }
340             else {
341 0         0 carp(
342             "ignoring unknown control sequence at line $n: $rest"
343             );
344             }
345             }
346             elsif ( $cc eq '|' ) {
347 1         4 $self->{INDEX}->{STREAM}->{$str}->{COMMENT}->{$p} = $rest;
348             }
349             else {
350 0         0 carp("unknown trace control char at line $n\n");
351             }
352             }
353 152         210 $p = $pnext;
354 152         670 $n++;
355             }
356 1         6 $self->_seek($iloc);
357             }
358              
359              
360             sub PrintIndex {
361 0     0 1 0 my $self = shift;
362 0         0 my ( $print_events, $tail ) = _check_args( \@_, qw(print_events) );
363              
364 0 0       0 if ( !defined( $self->{INDEX} ) ) {
365 0         0 carp "No index generated yet";
366 0         0 return;
367             }
368              
369 0         0 my $dirty = 0;
370 0         0 foreach my $str ( sort( keys( %{ $self->{INDEX}->{STREAM} } ) ) ) {
  0         0  
371 0 0       0 next if $str == 0;
372 0 0       0 if ( !$dirty ) {
373 0         0 print "======= DATA STREAMS ==========\n";
374 0         0 $dirty = 1;
375             }
376 0         0 my $text = $self->{INDEX}->{STREAM}->{$str}->{CONNECT};
377 0         0 $text =~ s/\\"/"/g;
378              
379 0         0 my $len = length($text);
380 0         0 my $j = 0;
381 0   0     0 while ( $j == 0 || $j < $len ) {
382 0 0       0 print "$str :" if $j == 0;
383 0 0       0 print "\t", substr( $text, $j, 64 ) if $j < $len;
384 0         0 print "\n";
385 0         0 $j += 64;
386             }
387              
388             # print "$str\t",$self->{INDEX}->{STREAM}->{$str}->{CONNECT},"\n";
389 0         0 my $d2 = 0;
390 0         0 foreach my $c (
391 0         0 sort( keys( %{ $self->{INDEX}->{STREAM}->{$str}->{COMMENT} } ) ) )
392             {
393 0         0 $d2 = 1;
394 0         0 print "\t", $self->{INDEX}->{STREAM}->{$str}->{COMMENT}->{$c},
395             "\n";
396             }
397 0 0       0 print "\n" if $d2;
398             }
399 0 0       0 print "\n" if $dirty;
400 0         0 $dirty = 0;
401              
402 0         0 foreach my $run ( sort( keys( %{ $self->{INDEX}->{RUN} } ) ) ) {
  0         0  
403 0 0       0 if ( !$dirty ) {
404 0         0 print "======= RUNS ==========\n";
405 0         0 $dirty = 1;
406             }
407 0         0 my (@e) = ( keys( %{ $self->{INDEX}->{RUN}->{$run}->{EVENT} } ) );
  0         0  
408 0         0 my $t0 = localtime( $self->{INDEX}->{RUN}->{$run}->{STARTTIME} );
409 0         0 my $t1;
410             $t1 = localtime( $self->{INDEX}->{RUN}->{$run}->{STOPTIME} )
411 0 0       0 if exists( $self->{INDEX}->{RUN}->{$run}->{STOPTIME} );
412 0 0       0 if ( !defined($t1) ) { # use file modification time
413 0         0 $t1 = localtime( $self->{FSTAT}->[9] ) . '?';
414             }
415             printf(
416 0         0 "RUN %08d :\t% 8d events\t(%s)..(%s)\n", $run, $#e + 1, $t0,
417             $t1
418             );
419              
420 0 0 0     0 if ( defined($print_events) && $print_events ) {
421 0         0 print "\t EVENT\t FILE POSITION \t TIME\n";
422 0         0 foreach my $ev ( sort(@e) ) {
423             $t0 = localtime(
424 0         0 $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$ev}->{TIME} );
425             my $pos = $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$ev}
426 0         0 ->{POSITION};
427 0         0 printf( "\t% 8d\t% 16d\t%s\n", $ev, $pos, $t0 );
428             }
429 0         0 print "\n";
430             }
431              
432             }
433 0 0       0 print "\n" if $dirty;
434              
435             }
436              
437              
438             sub ReadEvent {
439 2     2 1 2894 my $self = shift;
440 2         8 my ( $stream, $inrun, $inevent, $tail )
441             = _check_args( \@_, qw(stream run event) );
442              
443             # turn stream selection into an array ref, however passed.
444 2         4 my $streams = {};
445 2 50       7 if ( defined($stream) ) {
446 0 0       0 if ( ref($stream) eq '' ) {
447 0         0 $stream = [$stream];
448             }
449              
450 0         0 foreach my $s ( @{$stream} ) {
  0         0  
451 0         0 $streams->{$s} = 1;
452             }
453             $streams->{0} = 1
454 0 0 0     0 unless exists( $tail->{no_global} ) && !$tail->{no_global};
455             }
456              
457 2         3 my $fh = $self->{FH};
458 2 50       8 croak("data file is not open") unless defined $fh;
459              
460 2 100 66     8 if ( defined($inrun) || defined($inevent) ) {
461             return undef
462             unless
463 1 50       6 defined( $self->FindEvent( run => $inrun, event => $inevent ) );
464             }
465              
466 2         7 my $p = $self->_tell();
467             my $ev = {
468             GPIB => [],
469             COMMENT => [],
470             STREAM => {},
471             FILEHEADER => undef,
472             RUNHEADER => undef,
473 2         42 CONFIG => clone( $self->{CONFIG} ),
474             };
475              
476 2 100       9 if ( !defined( $self->{INDEX} ) ) {
477              
478 1         3 my $inevent = 0;
479              
480 1         678 while (<$fh>) {
481 116         226 chomp;
482 116         250 my $pnext = $self->_tell();
483 116 50       1047 if ( !/^(\d+)(.)(.*)\s*$/ ) {
484 0         0 carp("Error parsing trace file: $_");
485 0         0 $p = $pnext;
486 0         0 next;
487             }
488 116         316 my $str = $1 + 0;
489 116         202 my $cc = $2;
490 116         4223 my $rest = eval($3);
491              
492 116 100 66     585 if ( $cc eq '*' ) {
    100          
    50          
493 4 100       34 if ( $rest =~ /^Lab::[\w\:]+::new\s/i ) {
    100          
    50          
    0          
494             ;
495             }
496             elsif ( $rest =~ /^start\s+run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i )
497             {
498 1 50       4 last if $inevent;
499             }
500             elsif ( $rest
501             =~ /^event\s*(\d+)\s*run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i ) {
502 2 100       8 last if $inevent;
503              
504 1         2 $inevent = 1;
505 1         5 $ev->{EVENT} = $1 + 0;
506 1         5 $ev->{RUN} = $2 + 0;
507 1         5 $ev->{TIME} = $3;
508 1         3 $ev->{POSITION} = $p;
509 1 50       4 if ( $self->{CONFIG}->{clone_headers} ) {
510             $ev->{FILEHEADER} = clone( $self->{FILEHEADER} )
511 1 50       3 if defined $self->{FILEHEADER};
512             $ev->{RUNHEADER} = clone( $self->{RUNHEADER} )
513 1 50       4 if defined $self->{RUNHEADER};
514             }
515             }
516             elsif ( $rest =~ /^stop\s+run(\d+)\s.*\\?\@\s*([\d\.]+)/i ) {
517 0 0       0 last if $inevent;
518             }
519             else {
520 0         0 carp("ignoring unknown control sequence: $rest");
521             }
522             }
523             elsif ( $cc eq '|' ) {
524 1 0 0     6 if ( $inevent
      33        
525             && ( !defined($stream) || exists( $streams->{$str} ) ) ) {
526              
527 0         0 push( @{ $ev->{COMMENT} }, $rest );
  0         0  
528             $ev->{STREAM}->{$str} = {
529             COMMENT => [],
530             GPIB => [],
531             NUMBER => $str,
532             CONFIG => clone( $self->{CONFIG} ),
533 0 0       0 } unless exists $ev->{STREAM}->{$str};
534 0         0 push( @{ $ev->{STREAM}->{$str}->{COMMENT} }, $rest );
  0         0  
535             }
536             }
537             elsif ( $cc eq '<' || $cc eq '>' ) {
538 111 50 33     242 if ( $inevent
      66        
539             && ( !defined($stream) || exists( $streams->{$str} ) ) ) {
540 0         0 push( @{ $ev->{GPIB} }, $cc . $rest )
541 21 50       46 if $self->{CONFIG}->{combine_gpib};
542             $ev->{STREAM}->{$str} = {
543             COMMENT => [],
544             GPIB => [],
545             NUMBER => $str,
546             CONFIG => clone( $self->{CONFIG} ),
547 21 100       61 } unless exists $ev->{STREAM}->{$str};
548 21         46 push( @{ $ev->{STREAM}->{$str}->{GPIB} }, $cc . $rest );
  21         70  
549             }
550             }
551             else {
552 0         0 carp("unknown trace control char '$cc'\n");
553             }
554 115         585 $p = $pnext;
555             }
556              
557 1 50       4 return undef unless $inevent;
558 1         5 $self->_seek($p);
559 1         25 $self->{RUN} = $ev->{RUN};
560 1         4 $self->{EVENT} = $ev->{EVENT};
561              
562             }
563             else {
564 1         2 my $run = $self->{RUN};
565 1         3 my $event = $self->{EVENT} + 1;
566 1 50       4 return undef unless exists $self->{INDEX}->{RUN}->{$run};
567             return undef
568 1 50       5 unless exists $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event};
569             my $pev
570 1         2 = $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event}->{POSITION};
571 1         3 $ev->{RUN} = $run;
572 1         4 $ev->{EVENT} = $self->{EVENT} = $event;
573 1         3 $ev->{POSITION} = $pev;
574             $ev->{TIME}
575 1         2 = $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event}->{TIME};
576 1         3 $ev->{STREAM} = {};
577 1         18 $ev->{GPIB} = [];
578 1         3 $ev->{COMMENT} = [];
579 1         8 $ev->{CONFIG} = clone( $self->{CONFIG} );
580              
581 1 50       5 if ( $self->{CONFIG}->{clone_headers} ) {
582             $ev->{FILEHEADER} = clone( $self->{FILEHEADER} )
583 1 50       3 if defined $self->{FILEHEADER};
584             $ev->{RUNHEADER} = clone( $self->{RUNHEADER} )
585 1 50       4 if defined $self->{RUNHEADER};
586             }
587              
588 1         4 $self->_seek($pev);
589 1         29 my $foo = <$fh>;
590 1         3 my $p;
591 1         5 while (<$fh>) {
592 22         58 $p = $self->_tell();
593 22 50       228 if ( !/^(\d+)(.)(.*)\s*$/ ) {
594 0         0 carp("Error parsing trace file at line : $_");
595 0         0 next;
596             }
597 22         65 my $str = $1 + 0;
598 22         41 my $cc = $2;
599 22         1037 my $rest = eval($3);
600              
601 22 100       74 last if $cc eq '*';
602 21 0 33     47 next unless !defined($stream) || exists( $streams->{$str} );
603             $ev->{STREAM}->{$str} = {
604             COMMENT => [],
605             GPIB => [],
606             NUMBER => $str,
607             CONFIG => clone( $self->{CONFIG} ),
608 21 100       56 } unless exists $ev->{STREAM}->{$str};
609              
610 21 50 66     76 if ( $cc eq '|' ) {
    50          
611 0         0 push( @{ $ev->{COMMENT} }, $rest );
  0         0  
612 0         0 push( @{ $ev->{STREAM}->{$str}->{COMMENT} }, $rest );
  0         0  
613             }
614             elsif ( $cc eq '>' || $cc eq '<' ) {
615 0         0 push( @{ $ev->{GPIB} }, $cc . $rest )
616 21 50       44 if $self->{CONFIG}->{combine_gpib};
617 21         25 push( @{ $ev->{STREAM}->{$str}->{GPIB} }, $cc . $rest );
  21         182  
618             }
619             else {
620 0         0 carp("unknown trace control char '$cc'\n");
621             }
622             }
623             }
624 2         7 $self->_seek($p); # position at start of next event/control line
625 2         46 return $ev;
626             }
627              
628              
629             sub ReadFileHeader {
630 1     1 1 3 my $self = shift;
631 1         4 my ( $in, $tail ) = _check_args( \@_, 'shift' );
632              
633 1         3 my $fh = $self->{FH};
634 1 50       4 croak("No trace file open") unless defined $fh;
635              
636 1         2 my $shift = 0;
637 1 50 33     5 if ( defined($in) && $in =~ /^\s*(Y|T|[1-9])/i ) {
638 0         0 $shift = 1;
639             }
640 1         3 $self->{FILEHEADER} = undef;
641              
642 1         5 my $ipos = $self->_tell();
643              
644 1         7 my $p = $self->{FILE_BEGIN};
645 1         5 $self->_seek($p);
646 1         14 my $hdr = {};
647              
648 1         4 $hdr->{POSITION} = $p;
649 1         3 $hdr->{STREAM} = {};
650              
651 1         22 while (<$fh>) {
652 85         178 my $pnext = $self->_tell();
653 85 50       779 if ( !/^(\d+)(.)(.*)\s*$/ ) {
654 0         0 carp("Error parsing trace file : $_");
655 0         0 $p = $pnext;
656 0         0 next;
657             }
658 85         236 my $str = $1 + 0;
659 85         141 my $cc = $2;
660 85         2964 my $rest = eval($3);
661              
662             $hdr->{STREAM}->{$str} = {
663             COMMENT => [],
664             GPIB => [],
665             NUMBER => $str,
666             CONNECT => undef,
667 85 100       295 } unless exists $hdr->{STREAM}->{$str};
668              
669 85 100 66     336 if ( $cc eq '*' ) {
    100          
    50          
670 2 100       10 last if $rest =~ /^start\s+run/i;
671 1 50       4 last if $rest =~ /^event/i;
672 1 50       5 last if $rest =~ /^stop/i;
673 1 50       4 if ( $rest =~ /^Lab::/i ) {
674 1         4 $hdr->{STREAM}->{$str}->{CONNECT} = $rest;
675             }
676             else {
677 0         0 carp("ignoring unknown control sequence: $rest");
678             }
679             }
680             elsif ( $cc eq '|' ) {
681 1         2 push( @{ $hdr->{STREAM}->{$str}->{COMMENT} }, $rest );
  1         4  
682             }
683             elsif ( $cc eq '<' || $cc eq '>' ) {
684 82         101 push( @{ $hdr->{STREAM}->{$str}->{GPIB} }, $cc . $rest );
  82         247  
685             }
686             else {
687 0         0 carp("unknown trace control char '$cc' at line\n");
688             }
689 84         368 $p = $pnext;
690             }
691 1 50       7 if ($shift) {
692 0         0 $self->_seek($p);
693             }
694             else {
695 1         8 $self->_seek($ipos);
696             }
697 1         23 $self->{FILEHEADER} = $hdr;
698 1         3 return $hdr;
699             }
700              
701              
702             sub ReadRunHeader {
703 0     0 1 0 my $self = shift;
704 0         0 my ( $selrun, $tail ) = _check_args( \@_, qw(run) );
705              
706 0         0 my $fh = $self->{FH};
707 0 0       0 croak("no tracefile opened") unless defined $fh;
708              
709 0         0 my $hdr = {
710             STREAM => {},
711             };
712 0         0 my $ipos = $self->_tell();
713              
714 0 0 0     0 if ( defined( $self->{INDEX} ) && defined($selrun) ) {
715 0 0       0 if ( exists( $self->{INDEX}->{RUN}->{$selrun} ) ) {
716 0         0 $self->_seek( $self->{INDEX}->{RUN}->{$selrun}->{POSITION} );
717 0         0 $self->{RUN} = $selrun;
718             }
719             else {
720 0         0 return undef;
721             }
722             }
723             else {
724              
725 0         0 my $p = $ipos;
726 0         0 my $foundrun = 0;
727              
728 0         0 while (<$fh>) {
729 0         0 my $pnext = $self->_tell();
730              
731 0 0       0 if ( !/^(\d+)(.)(.*)\s*$/ ) {
732 0         0 carp("Error parsing trace file : $_");
733 0         0 next;
734             }
735 0         0 my $str = $1 + 0;
736 0         0 my $cc = $2;
737 0         0 my $rest = eval($3);
738              
739 0 0       0 if ( $cc eq '*' ) {
740 0 0       0 if ( $rest =~ /^start\s+run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i ) {
741 0         0 my $run = $1 + 0;
742 0 0 0     0 if ( !defined($selrun) || $selrun == $run ) {
743 0         0 $foundrun = 1;
744 0         0 $self->{RUN} = $run;
745 0         0 last;
746             }
747             }
748             }
749 0         0 $p = $pnext;
750             }
751 0 0       0 if ( !$foundrun ) {
752 0         0 $self->_seek($ipos);
753 0         0 return undef;
754             }
755 0         0 $self->_seek($p);
756             }
757              
758 0         0 my $p = $self->_tell();
759 0         0 while (<$fh>) {
760 0         0 my $pnext = $self->_tell();
761              
762 0 0       0 if ( !/^(\d+)(.)(.*)\s*$/ ) {
763 0         0 carp("Error parsing trace file: $_");
764 0         0 next;
765             }
766 0         0 my $str = $1 + 0;
767 0         0 my $cc = $2;
768 0         0 my $rest = eval($3);
769              
770             $hdr->{STREAM}->{$str} = {
771             COMMENT => [],
772             GPIB => [],
773             NUMBER => $str,
774 0 0       0 } unless exists $hdr->{STREAM}->{$str};
775              
776 0 0 0     0 if ( $cc eq '*' ) {
    0          
    0          
777 0 0       0 if ( $rest =~ /^start\s+run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i ) {
    0          
778 0         0 my $run = $1 + 0;
779 0 0       0 last if $self->{RUN} != $run;
780              
781 0         0 $hdr->{RUN} = $run;
782 0         0 $hdr->{POSITION} = $p;
783 0         0 $hdr->{STARTTIME} = $2;
784              
785             }
786             elsif ( $rest =~ /^(event|stop|Lab::)/i ) {
787 0         0 last;
788             }
789             else {
790 0         0 carp("ignoring unknown control sequence : $rest");
791             }
792             }
793             elsif ( $cc eq '|' ) {
794 0         0 push( @{ $hdr->{STREAM}->{$str}->{COMMENT} }, $rest );
  0         0  
795             }
796             elsif ( $cc eq '>' || $cc eq '<' ) {
797 0         0 push( @{ $hdr->{STREAM}->{$str}->{GPIB} }, $cc . $rest );
  0         0  
798             }
799             else {
800 0         0 carp("unknown trace control char '$cc'\n");
801             }
802 0         0 $p = $pnext;
803             }
804 0         0 $self->_seek($p);
805              
806 0         0 $self->{RUNHEADER} = $hdr;
807 0         0 return $hdr;
808             }
809              
810              
811             sub FindEvent {
812 1     1 1 3 my $self = shift;
813 1         3 my ( $run, $event, $tail ) = _check_args( \@_, 'run', 'event' );
814              
815 1 50 33     8 if ( !defined($run) || $run <= 0 ) {
816 0         0 $run = $self->{RUN};
817             }
818 1 50 33     7 if ( !defined($run) || $run <= 0 ) {
819 0         0 carp("invalid run (undef? <=0?)");
820 0         0 return undef;
821             }
822              
823 1 50       4 if ( exists( $self->{INDEX} ) ) {
824 1 50       5 return undef unless exists $self->{INDEX}->{RUN}->{$run};
825             my (@ev)
826 1         3 = ( sort( keys( %{ $self->{INDEX}->{RUN}->{$run}->{EVENT} } ) ) );
  1         9  
827 1 50       5 if ( !defined($event) ) {
828 0 0       0 if ( defined( $self->{EVENT} ) ) {
829 0         0 $event = $self->{EVENT} + 1;
830             }
831             else {
832 0         0 $event = 0;
833             }
834             }
835 1 50       9 if ( $event == 0 ) {
    50          
836 0         0 $event = $ev[0];
837             }
838             elsif ( $event < 0 ) {
839 0         0 $event = $ev[$event];
840             }
841             return undef
842 1 50       4 unless exists $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event};
843 1         4 my $p = $self->{INDEX}->{RUN}->{$run}->{EVENT}->{$event}->{POSITION};
844 1         4 $self->_seek($p);
845 1         21 return $p;
846             }
847             else {
848 0         0 my $fh = $self->{FH};
849 0 0       0 croak("no file opened") unless defined $fh;
850              
851 0         0 my $p = $self->_tell();
852 0         0 my $pstart = $p;
853 0         0 my $foundrun = 0;
854 0         0 my $foundevent = 0;
855 0         0 my $wrapped = 0;
856              
857 0 0       0 if ( !defined($event) ) {
858 0 0       0 if ( defined( $self->{EVENT} ) ) {
859 0         0 $event = $self->{EVENT} + 1;
860             }
861             else {
862 0         0 $event = 0;
863             }
864             }
865 0 0       0 $event = 0 if $event < 0;
866              
867 0         0 while ( !$wrapped ) {
868 0         0 while (<$fh>) {
869 0         0 my $pnext = $self->_tell();
870 0 0       0 last if $pnext == $pstart; # wrapped the file
871              
872 0 0       0 if ( !/^(\d+)(.)(.*)\s*$/ ) {
873 0         0 carp("Error parsing trace file: $_");
874 0         0 next;
875             }
876 0         0 my $str = $1 + 0;
877 0         0 my $cc = $2;
878 0         0 my $rest = eval($3);
879              
880 0 0       0 if ( $cc eq '*' ) {
881 0 0       0 if (
    0          
    0          
882             $rest =~ /^start\s+run\s*(\d+)\s*\\?\@\s*([\d\.]+)/i )
883             {
884 0         0 my $gotrun = $1 + 0;
885 0 0 0     0 if ( $foundrun && ( $gotrun != $run ) )
886             { # ran off the end of desired run, wrap
887 0         0 $foundrun = 0;
888 0         0 last;
889             }
890 0         0 $foundrun = ( $gotrun == $run );
891             }
892             elsif ( $rest =~ /^event\s*(\d+)\s*run\s*(\d+)/i ) {
893 0         0 my $gotev = $1 + 0;
894 0         0 my $gotrun = $2 + 0;
895 0 0 0     0 if ( $foundrun && ( $gotrun != $run ) )
896             { # ran off the end of desired run, wrap
897 0         0 $foundrun = 0;
898 0         0 last;
899             }
900 0         0 $foundrun = ( $gotrun == $run );
901 0 0       0 if ($foundrun) {
902 0 0 0     0 $foundevent = 1
903             if $event == 0 || $event == $gotev;
904 0 0       0 last if $foundevent;
905             }
906             }
907             elsif ( $rest =~ /^stop\s+run\s*(\d+)/i ) {
908 0         0 my $gotrun = $1 + 0;
909 0 0       0 if ($foundrun)
910             { # ran off the end of desired run, wrap
911 0         0 $foundrun = 0;
912 0         0 last; # wrap the file
913             }
914             }
915             }
916 0         0 $p = $pnext;
917             }
918 0 0       0 last if $wrapped;
919 0 0 0     0 last if $foundrun && $foundevent;
920 0         0 $wrapped = 1;
921 0         0 $p = $self->{FILE_BEGIN};
922 0         0 $self->_seek($p);
923             }
924 0 0 0     0 if ( !$foundrun || !$foundevent ) {
925 0         0 $self->_seek($pstart);
926 0         0 return undef;
927             }
928 0         0 $self->{RUN} = $run;
929 0         0 $self->{EVENT} = $event;
930 0         0 $self->_seek($p);
931 0         0 return $p;
932             }
933             }
934              
935              
936             sub PrintDefaultAnalyzer {
937 0     0 1 0 my $self = shift;
938 0         0 my ( $in, $tail ) = _check_args( \@_, 'stream' );
939              
940 0         0 my $stream;
941 0 0       0 if ( defined($in) ) {
942 0         0 $stream = {};
943 0 0       0 if ( ref($in) eq 'ARRAY' ) {
    0          
944 0         0 $stream->{$in} = 1;
945             }
946             elsif ( ref($in) eq '' ) {
947 0         0 $stream->{$in} = 1;
948 0         0 foreach my $k ( keys( %{$tail} ) ) {
  0         0  
949 0 0       0 next unless $k =~ /^_tail\d+$/;
950 0         0 $stream->{ $tail->{$k} } = 1;
951             }
952             }
953             else {
954 0         0 croak("parameter type mismatch");
955             }
956             }
957              
958 0 0       0 $self->ReadFileHeader() unless defined $self->{FILEHEADER};
959              
960 0         0 my $dirty = 0;
961 0         0 foreach my $s ( sort( keys( %{ $self->{FILEHEADER}->{STREAM} } ) ) ) {
  0         0  
962 0 0       0 next if $s == 0;
963 0 0 0     0 next if defined($stream) && !exists( $stream->{$s} );
964 0         0 my $aType = $self->_findDefAnalyzer($s);
965 0 0       0 next unless defined $aType;
966              
967 0 0       0 print "Stream\tAnalyzer\n" unless $dirty;
968 0         0 $dirty = 1;
969 0         0 print " $s \t$aType\n";
970             }
971 0 0       0 print "\n" if $dirty;
972             }
973              
974              
975             sub ConnectAnalyzer {
976 1     1 1 2207 my $self = shift;
977 1         6 my ( $instr, $inmod, $tail ) = _check_args( \@_, qw(stream module) );
978              
979 1 50       8 $self->ReadFileHeader() unless defined $self->{FILEHEADER};
980 1         2 foreach my $str ( keys( %{ $self->{FILEHEADER}->{STREAM} } ) ) {
  1         6  
981 2 100       8 next if $str == 0;
982 1 50 33     4 if ( !defined($instr) || $instr == $str ) {
983 1         5 my $defmod = $self->_findDefAnalyzer($str);
984 1 50       4 if ( defined($inmod) ) {
985 0 0 0     0 if ( $inmod !~ /::/
      0        
      0        
986             && !-e "$inmod.pm"
987             && defined($defmod)
988             && $defmod =~ /::${inmod}$/ ) { # default short name
989 0         0 $inmod = $defmod;
990             }
991             }
992             else {
993 1         3 $inmod = $self->_findDefAnalyzer($str);
994             }
995 1 50       5 if ( !defined($inmod) ) {
996 0         0 carp("No analysis module defined for stream $str");
997 0         0 return;
998             }
999 1 50       4 if ( !exists( $_LOADED->{$inmod} ) ) {
1000 1     1   109 eval("use $inmod;");
  1         660  
  1         4  
  1         26  
1001 1 50       6 croak($@) if $@;
1002 1         6 $_LOADED->{$inmod} = 1;
1003             }
1004 1         8 my $strhdr = $self->{FILEHEADER}->{STREAM}->{$str};
1005              
1006             $self->{FILEHEADER}->{STREAM}->{$str}->{ANALYSIS} = []
1007             unless
1008 1 50       8 exists $self->{FILEHEADER}->{STREAM}->{$str}->{ANALYSIS};
1009              
1010 1         2 my $a;
1011 1         87 eval( '$a = ' . $inmod . '->new(stream=>$strhdr);' );
1012 1 50       7 croak("error connecting analyzer $inmod") unless defined $a;
1013             push(
1014 1         2 @{ $self->{FILEHEADER}->{STREAM}->{$str}->{ANALYSIS} },
  1         8  
1015             $a
1016             );
1017             }
1018             }
1019             }
1020              
1021             sub _findDefAnalyzer {
1022 2     2   4 my $self = shift;
1023 2         4 my $str = shift;
1024 2 50       5 return undef if $str == 0;
1025 2 50       7 $self->ReadFileHeader() unless defined $self->{FILEHEADER};
1026 2 50       6 return undef unless exists $self->{FILEHEADER}->{STREAM}->{$str};
1027              
1028 2         3 my $con = $self->{FILEHEADER}->{STREAM}->{$str}->{CONNECT};
1029              
1030 2         5 foreach my $aNum ( sort( keys( %{$_DefaultAnalyzer} ) ) ) {
  2         10  
1031 2         5 my $aMatch = $_DefaultAnalyzer->{$aNum}->{MATCH};
1032 2 50       94 next unless $con =~ /$aMatch/;
1033 2         8 my $aType = $_DefaultAnalyzer->{$aNum}->{TYPE};
1034 2         7 return $aType;
1035             }
1036 0         0 return undef;
1037             }
1038              
1039              
1040             sub Analyze {
1041 1     1 1 8 my $self = shift;
1042 1         3 my ( $event, $opts, $str, $tail )
1043             = _check_args( \@_, 'event', 'options', 'stream' );
1044              
1045 1 50 33     9 if ( !defined($event) || ref($event) ne 'HASH' ) {
1046 0         0 carp("bad/missing event");
1047 0         0 return undef;
1048             }
1049 1 50       5 return undef unless exists $self->{FILEHEADER};
1050 1 50       3 return undef unless exists $self->{FILEHEADER}->{STREAM};
1051 1 50       4 $opts = {} unless defined $opts;
1052              
1053 1         2 my $stream;
1054              
1055 1 50       4 if ( defined($str) ) {
1056 0 0       0 if ( ref($str) eq 'ARRAY' ) {
    0          
1057 0         0 $stream = {};
1058 0         0 foreach my $s ( @{$str} ) {
  0         0  
1059 0         0 $stream->{$s} = 1;
1060             }
1061             }
1062             elsif ( ref($str) eq '' ) {
1063 0         0 $stream = { $str => 1 };
1064 0         0 foreach my $k ( sort( keys( %{$tail} ) ) ) {
  0         0  
1065 0 0       0 next unless $k =~ /^_tail\d+/i;
1066 0 0       0 next unless $tail->{$k} =~ /^\d+$/;
1067 0         0 $stream->{ $tail->{$k} } = 1;
1068 0         0 delete( $tail->{$k} );
1069             }
1070             }
1071             else {
1072 0         0 carp("bad stream parameter");
1073             }
1074             }
1075              
1076             #
1077             # scan through the streams, skipping ones we don't analyze
1078             #
1079              
1080 1         2 foreach my $s ( sort( keys( %{ $self->{FILEHEADER}->{STREAM} } ) ) ) {
  1         7  
1081 2 50 33     10 if ( !defined($stream) || exists( $stream->{$s} ) ) {
1082             next
1083 2 100       8 unless exists $self->{FILEHEADER}->{STREAM}->{$s}->{ANALYSIS};
1084              
1085             # if multiple analyses are connected, do them in sequence
1086              
1087 1         3 foreach
1088 1         3 my $a ( @{ $self->{FILEHEADER}->{STREAM}->{$s}->{ANALYSIS} } )
1089             {
1090 1         5 $event = $a->Analyze( $event, $opts );
1091 1 50       10 if ( !defined($event) ) {
1092 0         0 carp( "Stream $s analysis " . ref($a) . " failed" );
1093 0         0 return undef;
1094             }
1095             }
1096             }
1097             }
1098              
1099 1         8 return $event;
1100             }
1101              
1102             1; # End of Lab::Data::Analysis
1103              
1104             __END__
1105              
1106             =pod
1107              
1108             =encoding UTF-8
1109              
1110             =head1 NAME
1111              
1112             Lab::Data::Analysis - Analyze data from 'Trace' files
1113              
1114             =head1 VERSION
1115              
1116             version 3.881
1117              
1118             =head1 SYNOPSIS
1119              
1120             use Lab::Data::Analysis;
1121              
1122             my $t = Lab::Data::Analysis->new();
1123              
1124             $t->open($tracefile);
1125              
1126             RANDOM ACCESS:
1127              
1128             $t->MakeIndex();
1129            
1130             $t->PrintIndex();
1131              
1132             my $fhdr = $t->ReadFileHeader();
1133              
1134             my $rhdr = $t->ReadRunHeader(run=>3);
1135              
1136             my $ev = $t->ReadEvent(run=>3, event=>77);
1137              
1138            
1139             ... do analysis...
1140              
1141              
1142              
1143             SEQUENTIAL:
1144              
1145             my $fhdr = $t->ReadFileHeader();
1146              
1147             while (defined($rhdr = $t->ReadRunHeader()) {
1148            
1149             print "Run: ",$rhdr->{RUN},"\n";
1150              
1151             while (defined($ev = $t->ReadEvent()) {
1152            
1153             print "Event: ", $ev->{EVENT}, "\n";
1154              
1155             do analysis...
1156             }
1157             }
1158              
1159              
1160             Note that "random access" and "sequential" can be mixed,
1161             if you keep track of the file position.
1162              
1163             =head1 SUBROUTINES/METHODS
1164              
1165             =head2 new
1166              
1167             my $t = Lab::Data::Analysis->new(); # do 'open' later
1168              
1169             my $t = Lab::Data::Analysis->new($tracefile);
1170              
1171             my $t = Lab::Data::Analysis->new( file => $tracefile,
1172             ...options );
1173              
1174             =head2 open
1175              
1176             $t->open($file);
1177              
1178             $t->open(file=>$file, ...);
1179              
1180             Open a trace file for reading.
1181              
1182             =head2 rewind
1183              
1184             $t->rewind();
1185              
1186             Position to beginning of file for sequential access.
1187              
1188             Not sure that this is really needed: ReadFileHeader() automatically
1189             goes to the beginning of the file; ReadRunHeader(run=>firstrun) should
1190             read the header of the first run in the file, and leave the file
1191             positioned to start reading events.
1192              
1193             =head2 MakeIndex
1194              
1195             $t->MakeIndex();
1196              
1197             Compile an index of Runs/Events in a tracefile, for later use.
1198              
1199             Be warned: this may take some time for large files.
1200              
1201             =head2 PrintIndex
1202              
1203             $t->PrintIndex();
1204              
1205             Print an index of the tracefile, showing locations of runs/events, etc.
1206              
1207             =head2 ReadEvent
1208              
1209             my $event = $t->ReadEvent();
1210              
1211             my $event = $t->ReadEvent($stream);
1212              
1213             my $event = $t->ReadEvent([$stream1[, $stream2...]]);
1214              
1215             my $event = $t->ReadEvent(stream=>$stream);
1216              
1217             my $event = $t->ReadEvent(stream=>[$stream1[,$stream2...]]);
1218              
1219             Also can use a no_global=>1 option to exclude the 'global' (stream=0) stream,
1220             which just has comments. Adding run=>$run, event=>$event parameters causes
1221             FindEvent(run=>..., event=>...) to be called before reading the event.
1222              
1223             Read an event, starting at the current file position. This may
1224             involve skipping over (and ignoring) lines until reaching the next EVENT
1225             line. The event is returned in a hash structure, containing the
1226             raw data in all data streams for the event, up to the following EVENT
1227             marker, the "STOP RUN" marker, or the end of the file.
1228              
1229             Returns 'undef' if no more events remain in the file.
1230              
1231             Data streams can be selected by passing $stream parameter,
1232             and multiple streams by passing reference to an array.
1233              
1234             =head2 ReadFileHeader
1235              
1236             my $hdr = $t->ReadFileHeader([$shift]);
1237              
1238             my $hdr = $t->ReadFileHeader(shift=>$shift);
1239              
1240             Read the header of the data file (before the start of the
1241             first run), and store in a hashref. If '$shift' is true
1242             (=1,'yes', 'true') then leave the file positioned after
1243             the file header. The 'shift' parameter is usually not
1244             needed, because ReadRunHeader will just read from the
1245             start of the file to the first run.
1246              
1247             If 'shift' is not specified, then the file position is
1248             restored to where it was prior to the ReadFileHeader
1249             call, which can be useful if the file header is
1250             read later in the analysis.
1251              
1252             =head2 ReadRunHeader
1253              
1254             my $rhdr = $t->ReadRunHeader();
1255              
1256             my $rhdr = $t->ReadRunHeader($run);
1257              
1258             my $rhdr = $t->ReadRunHeader(run=>$run);
1259              
1260             Reads the header information between the start of run and the first
1261             event. If the run number is not given, reads from the current file
1262             position until the run is found. Returns undef if the file is not
1263             found.
1264              
1265             Returns a hashref with the information, and leaves the file
1266             positioned at the first event of the run.
1267              
1268             =head2 FindEvent
1269              
1270             $t->FindEvent($run,$event);
1271              
1272             $t->FindEvent(run=>$run, event=>$event);
1273              
1274             Find the specified event in the specified run (if run=undef or <=0, then
1275             use current run). Returns undef if the event is not found, otherwise
1276             returns the file position and leaves the file positioned so that ReadEvent
1277             will read the specified event.
1278              
1279             If event is undefined, defaults to the 'next event'; for files that
1280             have been indexed event=-1 returns the LAST event (-2 next to last, etc).
1281             Without an index event < 0 is treated as event=0.
1282              
1283             Note that this routine is MUCH more efficient if an index is created.
1284              
1285             =head2 PrintDefaultAnalyzer
1286              
1287             $t->PrintDefaultAnalyzer([$stream, $stream,...]);
1288              
1289             $t->PrintDefaultAnalyzer(stream=>[$stream1,$stream2,...]);
1290              
1291             Print the 'default' analyzer modules for the selected streams
1292             (default = 'all streams'). If the file header has not yet been
1293             read, this routine reads the file header to get the setup information
1294             about the data streams.
1295              
1296             =head2 ConnectAnalyzer
1297              
1298             $t->ConnectAnalyzer([[$stream],$module]);
1299              
1300             $t->ConnectAnalyzer(stream=>$stream, module=>$module);
1301              
1302             Connect an analysis module to a data stream. If the stream
1303             is unspecified, try to connect to all data streams. If
1304             the module is unspecified, try to use a 'default' module
1305             for the stream. Note that connecting multiple analysis
1306             modules to a stream results in the module being called
1307             in sequence, using the result from the previous
1308             analysis module.
1309              
1310             Default modules are in Lab::Data::Analysis:: ...
1311              
1312             =head2 Analyze
1313              
1314             my $ev = $t->Analyze($ev[,$options,[,$stream1,$stream2,...]);
1315              
1316             my $ev = $t->Analyze(event=>$ev[, stream=>$stream1][,analyzeroptions=>..]);
1317              
1318             my $ev = $t->Analyze(event=>$ev[, stream=>[$stream1,$stream2,...]
1319             [, analyzeroptions=>]);
1320              
1321             runs the analysis chain on the given event, for the given streams
1322             (default: all streams). The event is returned with analysis
1323             data added to the hashref. Options for the analyzer (in the
1324             key=>value form) can be passed if the hash calling form is used.
1325              
1326             =head1 COPYRIGHT AND LICENSE
1327              
1328             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
1329              
1330             Copyright 2016 Charles Lane
1331             2017 Andreas K. Huettel
1332             2020 Andreas K. Huettel
1333              
1334              
1335             This is free software; you can redistribute it and/or modify it under
1336             the same terms as the Perl 5 programming language system itself.
1337              
1338             =cut