File Coverage

blib/lib/Lab/Data/Analysis/WaveRunner.pm
Criterion Covered Total %
statement 33 413 7.9
branch 0 166 0.0
condition 0 32 0.0
subroutine 12 25 48.0
pod 2 3 66.6
total 47 639 7.3


line stmt bran cond sub pod time code
1             package Lab::Data::Analysis::WaveRunner;
2             #ABSTRACT: Analysis routine for LeCroy WaveRunner/etc. scopes
3             $Lab::Data::Analysis::WaveRunner::VERSION = '3.880';
4 1     1   2042 use v5.20;
  1         4  
5              
6 1     1   9 use strict;
  1         4  
  1         22  
7 1     1   5 use warnings;
  1         2  
  1         24  
8 1     1   6 use English;
  1         2  
  1         9  
9 1     1   392 use Carp;
  1         5  
  1         48  
10 1     1   7 use Data::Dumper;
  1         4  
  1         38  
11 1     1   5 use Lab::Data::Analysis;
  1         2  
  1         43  
12 1     1   7 use Clone qw(clone);
  1         2  
  1         3896  
13              
14             our @ISA = ("Lab::Data::Analysis");
15              
16             our $DEBUG = 0;
17              
18             # default config values, copied to $self->{CONFIG} initially
19              
20             our $DEFAULT_CONFIG = {};
21              
22             # template => [ [name, { hash}], ... ]
23              
24             our $DEFAULT_TEMPLATE = [
25             [
26             'WAVEDESC:BLOCK',
27             [
28             {
29             'TYPE' => 'string',
30             'NAME' => 'DESCRIPTOR_NAME',
31             'OFFSET' => '0'
32             },
33             {
34             'OFFSET' => '16',
35             'NAME' => 'TEMPLATE_NAME',
36             'TYPE' => 'string'
37             },
38             {
39             'ENUM' => {
40             '0' => 'byte',
41             '1' => 'word'
42             },
43             'NAME' => 'COMM_TYPE',
44             'TYPE' => 'enum',
45             'OFFSET' => '32'
46             },
47             {
48             'TYPE' => 'enum',
49             'NAME' => 'COMM_ORDER',
50             'ENUM' => {
51             '0' => 'HIFIRST',
52             '1' => 'LOFIRST'
53             },
54             'OFFSET' => '34'
55             },
56             {
57             'OFFSET' => '36',
58             'TYPE' => 'long',
59             'NAME' => 'WAVE_DESCRIPTOR'
60             },
61             {
62             'OFFSET' => '40',
63             'NAME' => 'USER_TEXT',
64             'TYPE' => 'long'
65             },
66             {
67             'NAME' => 'RES_DESC1',
68             'TYPE' => 'long',
69             'OFFSET' => '44'
70             },
71             {
72             'OFFSET' => '48',
73             'TYPE' => 'long',
74             'NAME' => 'TRIGTIME_ARRAY'
75             },
76             {
77             'NAME' => 'RIS_TIME_ARRAY',
78             'TYPE' => 'long',
79             'OFFSET' => '52'
80             },
81             {
82             'OFFSET' => '56',
83             'NAME' => 'RES_ARRAY1',
84             'TYPE' => 'long'
85             },
86             {
87             'TYPE' => 'long',
88             'NAME' => 'WAVE_ARRAY_1',
89             'OFFSET' => '60'
90             },
91             {
92             'NAME' => 'WAVE_ARRAY_2',
93             'TYPE' => 'long',
94             'OFFSET' => '64'
95             },
96             {
97             'NAME' => 'RES_ARRAY2',
98             'TYPE' => 'long',
99             'OFFSET' => '68'
100             },
101             {
102             'NAME' => 'RES_ARRAY3',
103             'TYPE' => 'long',
104             'OFFSET' => '72'
105             },
106             {
107             'NAME' => 'INSTRUMENT_NAME',
108             'TYPE' => 'string',
109             'OFFSET' => '76'
110             },
111             {
112             'OFFSET' => '92',
113             'TYPE' => 'long',
114             'NAME' => 'INSTRUMENT_NUMBER'
115             },
116             {
117             'OFFSET' => '96',
118             'TYPE' => 'string',
119             'NAME' => 'TRACE_LABEL'
120             },
121             {
122             'OFFSET' => '112',
123             'TYPE' => 'word',
124             'NAME' => 'RESERVED1'
125             },
126             {
127             'NAME' => 'RESERVED2',
128             'TYPE' => 'word',
129             'OFFSET' => '114'
130             },
131             {
132             'OFFSET' => '116',
133             'TYPE' => 'long',
134             'NAME' => 'WAVE_ARRAY_COUNT'
135             },
136             {
137             'TYPE' => 'long',
138             'NAME' => 'PNTS_PER_SCREEN',
139             'OFFSET' => '120'
140             },
141             {
142             'OFFSET' => '124',
143             'NAME' => 'FIRST_VALID_PNT',
144             'TYPE' => 'long'
145             },
146             {
147             'OFFSET' => '128',
148             'NAME' => 'LAST_VALID_PNT',
149             'TYPE' => 'long'
150             },
151             {
152             'NAME' => 'FIRST_POINT',
153             'TYPE' => 'long',
154             'OFFSET' => '132'
155             },
156             {
157             'OFFSET' => '136',
158             'TYPE' => 'long',
159             'NAME' => 'SPARSING_FACTOR'
160             },
161             {
162             'OFFSET' => '140',
163             'TYPE' => 'long',
164             'NAME' => 'SEGMENT_INDEX'
165             },
166             {
167             'NAME' => 'SUBARRAY_COUNT',
168             'TYPE' => 'long',
169             'OFFSET' => '144'
170             },
171             {
172             'TYPE' => 'long',
173             'NAME' => 'SWEEPS_PER_ACQ',
174             'OFFSET' => '148'
175             },
176             {
177             'TYPE' => 'word',
178             'NAME' => 'POINTS_PER_PAIR',
179             'OFFSET' => '152'
180             },
181             {
182             'OFFSET' => '154',
183             'NAME' => 'PAIR_OFFSET',
184             'TYPE' => 'word'
185             },
186             {
187             'TYPE' => 'float',
188             'NAME' => 'VERTICAL_GAIN',
189             'OFFSET' => '156'
190             },
191             {
192             'OFFSET' => '160',
193             'TYPE' => 'float',
194             'NAME' => 'VERTICAL_OFFSET'
195             },
196             {
197             'NAME' => 'MAX_VALUE',
198             'TYPE' => 'float',
199             'OFFSET' => '164'
200             },
201             {
202             'OFFSET' => '168',
203             'TYPE' => 'float',
204             'NAME' => 'MIN_VALUE'
205             },
206             {
207             'NAME' => 'NOMINAL_BITS',
208             'TYPE' => 'word',
209             'OFFSET' => '172'
210             },
211             {
212             'NAME' => 'NOM_SUBARRAY_COUNT',
213             'TYPE' => 'word',
214             'OFFSET' => '174'
215             },
216             {
217             'OFFSET' => '176',
218             'TYPE' => 'float',
219             'NAME' => 'HORIZ_INTERVAL'
220             },
221             {
222             'NAME' => 'HORIZ_OFFSET',
223             'TYPE' => 'double',
224             'OFFSET' => '180'
225             },
226             {
227             'NAME' => 'PIXEL_OFFSET',
228             'TYPE' => 'double',
229             'OFFSET' => '188'
230             },
231             {
232             'TYPE' => 'unit_definition',
233             'NAME' => 'VERTUNIT',
234             'OFFSET' => '196'
235             },
236             {
237             'OFFSET' => '244',
238             'TYPE' => 'unit_definition',
239             'NAME' => 'HORUNIT'
240             },
241             {
242             'OFFSET' => '292',
243             'NAME' => 'HORIZ_UNCERTAINTY',
244             'TYPE' => 'float'
245             },
246             {
247             'OFFSET' => '296',
248             'NAME' => 'TRIGGER_TIME',
249             'TYPE' => 'time_stamp'
250             },
251             {
252             'TYPE' => 'float',
253             'NAME' => 'ACQ_DURATION',
254             'OFFSET' => '312'
255             },
256             {
257             'OFFSET' => '316',
258             'TYPE' => 'enum',
259             'ENUM' => {
260             '8' => 'centered_RIS',
261             '1' => 'interleaved',
262             '2' => 'histogram',
263             '7' => 'sequence_obsolete',
264             '4' => 'filter_coefficient',
265             '0' => 'single_sweep',
266             '5' => 'complex',
267             '9' => 'peak_detect',
268             '3' => 'graph',
269             '6' => 'extrema'
270             },
271             'NAME' => 'RECORD_TYPE'
272             },
273             {
274             'TYPE' => 'enum',
275             'NAME' => 'PROCESSING_DONE',
276             'ENUM' => {
277             '0' => 'no_processing',
278             '5' => 'no_result',
279             '2' => 'interpolated',
280             '4' => 'autoscaled',
281             '7' => 'cumulative',
282             '3' => 'sparsed',
283             '6' => 'rolling',
284             '1' => 'fir_filter'
285             },
286             'OFFSET' => '318'
287             },
288             {
289             'OFFSET' => '320',
290             'TYPE' => 'word',
291             'NAME' => 'RESERVED5'
292             },
293             {
294             'TYPE' => 'word',
295             'NAME' => 'RIS_SWEEPS',
296             'OFFSET' => '322'
297             },
298             {
299             'TYPE' => 'enum',
300             'ENUM' => {
301             '44' => '500_s/div',
302             '42' => '100_s/div',
303             '32' => '50_ms/div',
304             '10' => '2_ns/div',
305             '29' => '5_ms/div',
306             '23' => '50_us/div',
307             '38' => '5_s/div',
308             '18' => '1_us/div',
309             '0' => '1_ps/div',
310             '4' => '20_ps/div',
311             '17' => '500_ns/div',
312             '20' => '5_us/div',
313             '2' => '5_ps/div',
314             '14' => '50_ns/div',
315             '12' => '10_ns/div',
316             '36' => '1_s/div',
317             '22' => '20_us/div',
318             '9' => '1_ns/div',
319             '26' => '500_us/div',
320             '37' => '2_s/div',
321             '24' => '100_us/div',
322             '31' => '20_ms/div',
323             '1' => '2_ps/div',
324             '25' => '200_us/div',
325             '47' => '5_ks/div',
326             '5' => '50_ps/div',
327             '27' => '1_ms/div',
328             '6' => '100_ps/div',
329             '40' => '20_s/div',
330             '3' => '10_ps/div',
331             '39' => '10_s/div',
332             '35' => '500_ms/div',
333             '28' => '2_ms/div',
334             '46' => '2_ks/div',
335             '8' => '500_ps/div',
336             '15' => '100_ns/div',
337             '7' => '200_ps/div',
338             '33' => '100_ms/div',
339             '11' => '5_ns/div',
340             '41' => '50_s/div',
341             '100' => 'EXTERNAL',
342             '34' => '200_ms/div',
343             '16' => '200_ns/div',
344             '19' => '2_us/div',
345             '21' => '10_us/div',
346             '13' => '20_ns/div',
347             '43' => '200_s/div',
348             '45' => '1_ks/div',
349             '30' => '10_ms/div'
350             },
351             'NAME' => 'TIMEBASE',
352             'OFFSET' => '324'
353             },
354             {
355             'ENUM' => {
356             '1' => 'ground',
357             '3' => 'ground',
358             '0' => 'DC_50_Ohms',
359             '2' => 'DC_1MOhm',
360             '4' => 'AC_1MOhm'
361             },
362             'NAME' => 'VERT_COUPLING',
363             'TYPE' => 'enum',
364             'OFFSET' => '326'
365             },
366             {
367             'OFFSET' => '328',
368             'TYPE' => 'float',
369             'NAME' => 'PROBE_ATT'
370             },
371             {
372             'OFFSET' => '332',
373             'ENUM' => {
374             '15' => '100_mV/div',
375             '8' => '500_uV/div',
376             '23' => '50_V/div',
377             '10' => '2_mV/div',
378             '26' => '500_V/div',
379             '9' => '1_mV/div',
380             '22' => '20_V/div',
381             '11' => '5_mV/div',
382             '12' => '10_mV/div',
383             '2' => '5_uV/div',
384             '20' => '5_V/div',
385             '14' => '50_mV/div',
386             '7' => '200_uV/div',
387             '17' => '500_mV/div',
388             '4' => '20_uV/div',
389             '0' => '1_uV/div',
390             '18' => '1_V/div',
391             '13' => '20_mV/div',
392             '25' => '200_V/div',
393             '1' => '2_uV/div',
394             '21' => '10_V/div',
395             '19' => '2_V/div',
396             '16' => '200_mV/div',
397             '24' => '100_V/div',
398             '3' => '10_uV/div',
399             '6' => '100_uV/div',
400             '27' => '1_kV/div',
401             '5' => '50_uV/div'
402             },
403             'NAME' => 'FIXED_VERT_GAIN',
404             'TYPE' => 'enum'
405             },
406             {
407             'OFFSET' => '334',
408             'ENUM' => {
409             '0' => 'off',
410             '1' => 'on'
411             },
412             'NAME' => 'BANDWIDTH_LIMIT',
413             'TYPE' => 'enum'
414             },
415             {
416             'NAME' => 'VERTICAL_VERNIER',
417             'TYPE' => 'float',
418             'OFFSET' => '336'
419             },
420             {
421             'OFFSET' => '340',
422             'NAME' => 'ACQ_VERT_OFFSET',
423             'TYPE' => 'float'
424             },
425             {
426             'TYPE' => 'enum',
427             'ENUM' => {
428             '2' => 'CHANNEL_3',
429             '0' => 'CHANNEL_1',
430             '9' => 'UNKNOWN',
431             '1' => 'CHANNEL_2',
432             '3' => 'CHANNEL_4'
433             },
434             'NAME' => 'WAVE_SOURCE',
435             'OFFSET' => '344'
436             }
437             ]
438             ],
439             [
440             'USERTEXT:BLOCK',
441             [
442             {
443             'OFFSET' => '0',
444             'NAME' => 'TEXT',
445             'TYPE' => 'text'
446             }
447             ]
448             ],
449             [
450             'TRIGTIME:ARRAY',
451             [
452             {
453             'OFFSET' => '0',
454             'TYPE' => 'double',
455             'NAME' => 'TRIGGER_TIME'
456             },
457             {
458             'NAME' => 'TRIGGER_OFFSET',
459             'TYPE' => 'double',
460             'OFFSET' => '8'
461             }
462             ]
463             ],
464             [
465             'RISTIME:ARRAY',
466             [
467             {
468             'OFFSET' => '0',
469             'TYPE' => 'double',
470             'NAME' => 'RIS_OFFSET'
471             }
472             ]
473             ],
474             [
475             'DATA_ARRAY_1:ARRAY',
476             [
477             {
478             'NAME' => 'MEASUREMENT',
479             'TYPE' => 'data',
480             'OFFSET' => '0'
481             }
482             ]
483             ],
484             [
485             'DATA_ARRAY_2:ARRAY',
486             [
487             {
488             'OFFSET' => '0',
489             'NAME' => 'MEASUREMENT',
490             'TYPE' => 'data'
491             }
492             ]
493             ],
494             [
495             'SIMPLE:ARRAY',
496             [
497             {
498             'TYPE' => 'data',
499             'NAME' => 'MEASUREMENT',
500             'OFFSET' => '0'
501             }
502             ]
503             ],
504             [
505             'DUAL:ARRAY',
506             [
507             {
508             'TYPE' => 'data',
509             'NAME' => 'MEASUREMENT_1',
510             'OFFSET' => '0'
511             },
512             {
513             'NAME' => 'MEASUREMENT_2',
514             'TYPE' => 'data',
515             'OFFSET' => '0'
516             }
517             ]
518             ]
519             ];
520              
521              
522             sub new {
523 0     0 1 0 my $proto = shift;
524 0   0     0 my $class = ref($proto) || $proto;
525 0         0 my $self = {};
526 0         0 bless $self, $class;
527              
528 0         0 my ( $stream, $tail )
529             = Lab::Data::Analysis::_check_args( \@_, qw(stream) );
530              
531 0         0 $self->{STREAM} = $stream; # hash of stream fileheader info
532 0         0 $self->{TEMPLATE} = clone($DEFAULT_TEMPLATE);
533 0         0 $self->{PARSED_HEADER} = 0;
534 0         0 $self->{BYTEORDER} = '>'; # MSB, gets fixed when waveform read
535              
536 0         0 return $self;
537             }
538              
539              
540             sub Analyze {
541 0     0 1 0 my $self = shift;
542 0         0 my $event = shift;
543              
544             # handle analysis options
545 0         0 my $option = shift;
546 0 0 0     0 $option = {} unless defined $option && ref($option) eq 'HASH';
547 0 0       0 $option->{dropraw} = 0 unless exists $option->{dropraw};
548 0 0       0 $option->{interpolate} = 1 unless exists $option->{interpolate};
549             $option->{use_default_template} = 0
550 0 0       0 unless exists $option->{use_default_template};
551 0 0       0 $option->{print_summary} = 0 unless exists $option->{print_summary};
552              
553 0 0       0 if ( !$option->{use_default_template} ) {
554 0 0       0 $self->_ParseHeader( $event, $option ) unless $self->{PARSED_HEADER};
555             }
556              
557 0         0 my $stream = $self->{STREAM}->{NUMBER};
558              
559 0         0 my $a = {};
560 0         0 $a->{MODULE} = 'WaveRunner';
561 0         0 $a->{RAW} = {};
562 0         0 $a->{RAW}->{CHAN} = {};
563 0         0 $a->{EVENT} = $event->{EVENT};
564 0         0 $a->{RUN} = $event->{RUN};
565 0         0 $a->{CHAN} = {};
566 0         0 $a->{COMMENT} = [];
567 0         0 $a->{STREAM} = $stream;
568 0         0 $a->{OPTIONS} = clone($option);
569              
570 0         0 foreach my $c ( @{ $event->{STREAM}->{$stream}->{COMMENT} } ) {
  0         0  
571 0         0 push( @{ $a->{COMMENT} }, $c );
  0         0  
572             }
573              
574 0         0 my $ch;
575 0         0 my $seq = [];
576 0         0 foreach my $g ( @{ $event->{STREAM}->{$stream}->{GPIB} } ) {
  0         0  
577 0 0       0 next unless substr( $g, 0, 1 ) eq '<'; # from scope
578 0         0 my $str = substr( $g, 1 );
579 0 0       0 next unless $str =~ /^\s*:?(\w+):(WF|WAVEFORM)\s*(\w+),/i;
580 0         0 $ch = uc($1);
581 0         0 my $parts = uc($3);
582 0         0 $g = $POSTMATCH;
583 0         0 $a->{RAW}->{CHAN}->{$ch} = $self->_ParseWaveform( $g, $option );
584 0         0 $a->{RAW}->{CHAN}->{$ch}->{PARTS} = $parts;
585             $a->{CHAN}->{$ch}
586 0         0 = $self->_AnalyzeWaveform( $a->{RAW}->{CHAN}->{$ch}, $option );
587             }
588              
589 0 0       0 print Dumper($a) if $DEBUG > 2;
590 0 0       0 $self->_PrintSummary( $a, $option ) if $option->{print_summary};
591              
592 0 0       0 $event->{ANALYZE} = {} unless exists $event->{ANALYZE};
593             $event->{ANALYZE}->{$stream} = {}
594 0 0       0 unless exists $event->{ANALYZE}->{$stream};
595              
596 0 0       0 delete( $a->{RAW} ) if $option->{dropraw};
597 0         0 $event->{ANALYZE}->{$stream}->{WaveRunner} = $a;
598              
599 0         0 return $event;
600             }
601              
602             sub _AnalyzeWaveform {
603 0     0   0 my $self = shift;
604 0         0 my $raw = shift;
605 0         0 my $opt = shift;
606              
607 0         0 my $a = {};
608              
609 0         0 my $id = $raw->{WAVEDESC}->{INSTRUMENT_NAME};
610 0         0 $id .= ', ' . $raw->{WAVEDESC}->{WAVE_SOURCE};
611 0         0 $id .= ': ' . $raw->{WAVEDESC}->{VERT_COUPLING};
612 0         0 $id .= ', 1:' . $raw->{WAVEDESC}->{PROBE_ATT};
613 0         0 $id .= ' ' . $raw->{WAVEDESC}->{TIMEBASE};
614 0         0 $id .= ' ' . $raw->{WAVEDESC}->{RECORD_TYPE};
615 0         0 $id .= ' ' . $raw->{WAVEDESC}->{FIXED_VERT_GAIN};
616             $id .= ' [' . $raw->{WAVEDESC}->{TRACE_LABEL} . ']'
617 0 0       0 if $raw->{WAVEDESC}->{TRACE_LABEL} ne '';
618 0         0 $a->{ID} = $id;
619              
620 0         0 $a->{VERTUNIT} = $raw->{WAVEDESC}->{VERTUNIT};
621 0         0 $a->{HORUNIT} = $raw->{WAVEDESC}->{HORUNIT};
622 0         0 $a->{TIME} = $raw->{WAVEDESC}->{TRIGGER_TIME};
623              
624 0         0 my $vgain = $raw->{WAVEDESC}->{VERTICAL_GAIN};
625 0         0 my $voff = $raw->{WAVEDESC}->{VERTICAL_OFFSET};
626 0         0 my $hint = $raw->{WAVEDESC}->{HORIZ_INTERVAL};
627 0         0 my $hoff = $raw->{WAVEDESC}->{HORIZ_OFFSET};
628 0         0 my $j0 = $raw->{WAVEDESC}->{FIRST_VALID_PNT};
629 0         0 my $j1 = $raw->{WAVEDESC}->{LAST_VALID_PNT};
630              
631 0         0 $a->{Y} = [];
632 0         0 $a->{X} = [];
633 0         0 my ( $ymax, $ymin );
634              
635 0         0 $a->{START} = $j0;
636 0         0 $a->{STOP} = $j1;
637 0         0 for ( my $j = $j0; $j <= $j1; $j++ ) {
638 0         0 my $d = $raw->{WAVE_ARRAY_1}->{MEASUREMENT}->[$j];
639 0         0 my $y = $d * $vgain - $voff;
640 0 0 0     0 $ymax = $y unless defined($ymax) && $ymax > $y;
641 0 0 0     0 $ymin = $y unless defined($ymin) && $ymin < $y;
642              
643 0         0 my $x = $j * $hint + $hoff;
644 0         0 $a->{Y}->[$j] = $y;
645 0         0 $a->{X}->[$j] = $x;
646             }
647 0         0 $a->{YMAX} = $ymax;
648 0         0 $a->{YMIN} = $ymin;
649 0         0 $a->{XMIN} = $j0 * $hint + $hoff;
650 0         0 $a->{XMAX} = $j1 * $hint + $hoff;
651              
652 0         0 return $a;
653             }
654              
655             sub _PrintSummary {
656 0     0   0 my $self = shift;
657 0         0 my $a = shift;
658 0         0 my $opt = shift;
659              
660             print "WaveRunner Analysis Summary: Run ", $a->{RUN}, " Event ",
661 0         0 $a->{EVENT}, " Stream ", $a->{STREAM}, "\n";
662              
663 0         0 print "\nAnalysis options:\n";
664 0         0 foreach my $k ( sort( keys( %{ $a->{OPTIONS} } ) ) ) {
  0         0  
665 0         0 print "\t $k = ", $a->{OPTIONS}->{$k}, "\n";
666             }
667              
668 0         0 print "\nDAQ inline comments:\n";
669 0         0 foreach my $c ( @{ $a->{COMMENT} } ) {
  0         0  
670 0         0 print "\t \"$c\"\n";
671             }
672              
673 0         0 print "\nChannels:";
674 0         0 foreach my $ch ( sort( keys( %{ $a->{RAW}->{CHAN} } ) ) ) {
  0         0  
675 0         0 print " $ch";
676             }
677 0         0 print "\n";
678              
679 0         0 foreach my $ch ( sort( keys( %{ $a->{RAW}->{CHAN} } ) ) ) {
  0         0  
680 0         0 print "Channel $ch summary: \n";
681             print "\tWaveform parts transmitted: ",
682 0         0 $a->{RAW}->{CHAN}->{$ch}->{PARTS}, "\n";
683              
684 0         0 foreach my $k (
685 0         0 sort( keys( %{ $a->{RAW}->{CHAN}->{$ch}->{WAVEDESC} } ) ) ) {
686 0         0 my $key = sprintf( "%-18s", $k );
687 0         0 print "\t$key : ", $a->{RAW}->{CHAN}->{$ch}->{WAVEDESC}->{$k},
688             "\n";
689             }
690 0         0 print "\n";
691             }
692              
693             }
694              
695             sub _trimNul {
696 0     0   0 my $s = shift;
697 0         0 $s =~ s/\0+$//;
698 0         0 return $s;
699             }
700              
701             sub _ParseWaveform {
702 0     0   0 my $self = shift;
703 0         0 my $w = shift;
704 0         0 my $opt = shift; # hash of analysis options
705              
706 0 0       0 if ( !defined( $self->{TEMPLATE} ) ) {
707 0         0 carp("no waveform template defined!");
708 0         0 return undef;
709             }
710              
711             # make in index by template part name
712 0 0 0     0 if ( !exists( $self->{TEMPLATE_PART} )
713             || !defined( $self->{TEMPLATE_PART} ) ) {
714 0         0 $self->{TEMPLATE_PART} = {};
715              
716 0         0 my $j = 0;
717 0         0 foreach my $part ( @{ $self->{TEMPLATE} } ) {
  0         0  
718 0         0 $self->{TEMPLATE_PART}->{ $self->{TEMPLATE}->[$j]->[0] } = $j;
719 0         0 $j++;
720             }
721             }
722              
723 0         0 my $a = {};
724              
725 0 0       0 if ( substr( $w, 0, 1 ) ne '#' ) {
726 0         0 carp("no leading # in waveform data");
727 0         0 return undef;
728             }
729 0         0 my $nd = substr( $w, 1, 1 );
730 0 0       0 if ( $nd !~ /[1-9]/ ) {
731 0         0 carp("no num digits digit in waveform data");
732 0         0 return undef;
733             }
734 0         0 my $n = substr( $w, 2, $nd );
735 0 0       0 if ( $n !~ /^\d+$/ ) {
736 0         0 carp("invalid digits in waveform data count");
737 0         0 return undef;
738             }
739 0         0 $w = substr( $w, 2 + $nd, $n ); # w is the binary string of wf data
740              
741             # need to find the byte order
742              
743 0 0       0 if ( !exists( $self->{TEMPLATE_PART}->{'WAVEDESC:BLOCK'} ) ) {
744 0         0 carp("waveform templates needs WAVEDESC:BLOCK!");
745 0         0 return undef;
746             }
747 0 0       0 if ( $self->{TEMPLATE_PART}->{'WAVEDESC:BLOCK'} != 0 ) {
748 0         0 carp("WAVEDESC:BLOCK must come first in waveform data");
749 0         0 return undef;
750             }
751              
752 0         0 my $wdesc = $self->{TEMPLATE}->[0]->[1];
753 0         0 foreach my $f ( @{$wdesc} ) {
  0         0  
754 0 0       0 next unless $f->{NAME} eq 'COMM_ORDER';
755 0         0 my $bord = unpack( 'S', substr( $w, $f->{OFFSET}, 2 ) );
756 0 0       0 if ( $bord == 0 ) {
757 0         0 $self->{BYTEORDER} = '>';
758             }
759             else {
760 0         0 $self->{BYTEORDER} = '<';
761             }
762 0         0 last;
763             }
764 0         0 $self->{COMM_TYPE} = 'byte'; # temp
765              
766             # now decode the WAVEDESC block
767 0         0 $a->{WAVEDESC} = {};
768 0         0 my $p = 0; # offset into overall data block
769 0         0 foreach my $f ( @{$wdesc} ) {
  0         0  
770 0         0 $f = $self->_fetchwf( $w, $f, $p );
771 0         0 $a->{WAVEDESC}->{ $f->{NAME} } = $f->{VALUE};
772             }
773 0         0 $p += $a->{WAVEDESC}->{WAVE_DESCRIPTOR};
774 0         0 $self->{COMM_TYPE} = $a->{WAVEDESC}->{COMM_TYPE}; # need for 'data'
775              
776             # usertext block
777 0         0 $a->{USER_TEXT} = '';
778 0         0 my $seglen = $a->{WAVEDESC}->{USER_TEXT};
779 0 0       0 if ( $seglen > 0 ) {
780 0         0 $a->{USER_TEXT} = _trimNul( substr( $w, $p, $seglen ) );
781 0         0 $p += $seglen;
782             }
783              
784             # arrays
785              
786 0         0 my $tname = {
787             TRIGTIME_ARRAY => 'TRIGTIME:ARRAY',
788             RIS_TIME_ARRAY => 'RISTIME:ARRAY',
789             RES_ARRAY1 => 'SIMPLE:ARRAY',
790             WAVE_ARRAY_1 => 'DATA_ARRAY_1:ARRAY',
791             WAVE_ARRAY_2 => 'DATA_ARRAY_2:ARRAY',
792             RES_ARRAY2 => 'SIMPLE:ARRAY',
793             RES_ARRAY3 => 'SIMPLE:ARRAY',
794             };
795              
796 0         0 foreach my $aname (
797             qw(TRIGTIME_ARRAY RIS_TIME_ARRAY RES_ARRAY1
798             WAVE_ARRAY_1 WAVE_ARRAY_2 RES_ARRAY2 RES_ARRAY3)
799             ) {
800 0 0       0 if ( !exists( $a->{WAVEDESC}->{$aname} ) ) {
801 0         0 carp("Array $aname length not found");
802 0         0 next;
803             }
804              
805 0         0 my $template = $tname->{$aname};
806              
807 0 0       0 if ( !exists( $self->{TEMPLATE_PART}->{$template} ) ) {
808 0         0 carp("No template found for $template");
809 0         0 next;
810             }
811 0         0 my $jtemp = $self->{TEMPLATE_PART}->{$template};
812 0         0 $wdesc = $self->{TEMPLATE}->[$jtemp]->[1];
813              
814             # each piece of the ARRAY thing gets its own array
815             # maybe not the best way to do it, but what is?
816              
817             # $a->{TRIGTIME_ARRAY}->{TRIGGER_TIME} = []
818             # $a->{TRIGTIME_ARRAY}->{TRIGGER_OFFSET} = []
819             # $a->{WAVE_ARRAY_1}->{MEASUREMENT} = []
820             # etc
821              
822 0         0 $seglen = $a->{WAVEDESC}->{$aname};
823              
824             # fill from data block, unless a "reserved" array
825 0 0       0 if ( $aname =~ /^RES_/ ) {
826 0         0 $p += $seglen;
827             }
828             else {
829              
830             # create the arrays
831 0         0 $a->{$aname} = {};
832 0         0 foreach my $f ( @{$wdesc} ) {
  0         0  
833 0         0 $a->{$aname}->{ $f->{NAME} } = [];
834             }
835              
836 0         0 while ( $seglen > 0 ) {
837 0         0 my $len = 0;
838 0         0 foreach my $f ( @{$wdesc} ) {
  0         0  
839 0 0       0 $f->{TYPE} = $self->{COMM_TYPE} if $f->{TYPE} eq 'data';
840              
841             # array of ONE type can be unpacked all at once.
842 0 0       0 if ( $#{$wdesc} == 0 ) {
  0         0  
843 0         0 my $fmt;
844 0         0 my $px = $p + $f->{OFFSET};
845             $fmt = "x[$px]c[" . $seglen . "]"
846 0 0       0 if $f->{TYPE} eq 'byte';
847             $fmt
848             = "x[$px]s["
849             . ( $seglen >> 1 ) . "]"
850             . $self->{BYTEORDER}
851 0 0       0 if $f->{TYPE} eq 'word';
852             $fmt
853             = "x[$px]l["
854             . ( $seglen >> 2 ) . "]"
855             . $self->{BYTEORDER}
856 0 0       0 if $f->{TYPE} eq 'long';
857 0 0       0 if ( defined($fmt) ) {
858             $a->{$aname}->{ $f->{NAME} }
859 0         0 = [ unpack( $fmt, $w ) ];
860 0         0 $len += $seglen;
861 0         0 next;
862             }
863             }
864              
865 0         0 my $d = $self->_fetchwf( $w, $f, $p );
866 0         0 $len += $d->{LENGTH};
867 0         0 push( @{ $a->{$aname}->{ $f->{NAME} } }, $d->{VALUE} );
  0         0  
868              
869             }
870 0         0 $p += $len;
871 0         0 $seglen -= $len;
872             }
873             }
874             }
875              
876 0         0 return $a;
877             }
878              
879             sub _fetchwf {
880 0     0   0 my $self = shift;
881 0         0 my $wdata = shift;
882 0         0 my $desc = shift;
883 0   0     0 my $offset = shift || 0;
884              
885 0         0 my $a = clone($desc);
886              
887 0         0 my $ord = $self->{BYTEORDER}; # '<' LSB '>' MSB
888 0         0 my $p = $a->{OFFSET} + $offset;
889 0         0 my $len = 1;
890              
891 0 0       0 if ( $a->{TYPE} eq 'data' ) {
892 0         0 $a->{TYPE} = $self->{COMM_TYPE};
893             }
894              
895 0 0       0 if ( $a->{TYPE} eq 'string' ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
896 0         0 $len = 16;
897 0         0 $a->{VALUE} = _trimNul( substr( $wdata, $p, $len ) );
898             }
899             elsif ( $a->{TYPE} eq 'byte' ) {
900 0         0 $len = 1;
901 0         0 $a->{VALUE} = unpack( 'c', substr( $wdata, $p, $len ) );
902             }
903             elsif ( $a->{TYPE} eq 'word' ) {
904 0         0 $len = 2;
905 0         0 $a->{VALUE} = unpack( 's' . $ord, substr( $wdata, $p, $len ) );
906             }
907             elsif ( $a->{TYPE} eq 'long' ) {
908 0         0 $len = 4;
909 0         0 $a->{VALUE} = unpack( 'l' . $ord, substr( $wdata, $p, $len ) );
910             }
911             elsif ( $a->{TYPE} eq 'float' ) {
912 0         0 $len = 4;
913             $a->{VALUE}
914 0         0 = _float( unpack( 'L' . $ord, substr( $wdata, $p, $len ) ) );
915             }
916             elsif ( $a->{TYPE} eq 'double' ) {
917              
918             # printf("Double bytes: %02x %02x %02x %02x %02x %02x %02x %02x\n",
919             # unpack('c*',substr($wdata,$p,8)));
920 0         0 $len = 8;
921 0         0 my (@long) = unpack( '(LL)' . $ord, substr( $wdata, $p, $len ) );
922 0 0       0 @long = reverse(@long) if $ord eq '<';
923              
924             # print "ord: $ord\n";
925             # printf("Double in: MSB 0x%08x LSB %08x\n",@long);
926 0         0 $a->{VALUE} = _double(@long);
927             }
928             elsif ( $a->{TYPE} eq 'time_stamp' ) {
929 0         0 $len = 16;
930 0         0 my ( $s1, $s2, $m, $h, $d, $mo, $y, $un )
931             = unpack( '(LLccccss)' . $ord, substr( $wdata, $p, $len ) );
932 0 0       0 ( $s1, $s2 ) = reverse( $s1, $s2 ) if $ord eq '<';
933 0         0 my $sec = _double( $s1, $s2 );
934 0         0 my $sstr = sprintf( '%.12f', $sec );
935 0 0       0 $sstr = '0' . $sstr if $sec < 10;
936 0         0 $a->{VALUE} = sprintf(
937             '%04d-%02d-%02d %02d:%02d:%s',
938             $y, $mo, $d, $h, $m, $sstr
939             );
940             }
941             elsif ( $a->{TYPE} eq 'unit_definition' ) {
942 0         0 $len = 48;
943 0         0 $a->{VALUE} = _trimNul( substr( $wdata, $p, $len ) );
944             }
945             elsif ( $a->{TYPE} eq 'enum' ) {
946 0         0 $len = 2;
947 0         0 my $ne = sprintf(
948             '%d',
949             unpack( 'S' . $ord, substr( $wdata, $p, $len ) )
950             );
951 0         0 $a->{VALUE} = $a->{ENUM}->{$ne};
952             }
953             elsif ( $a->{TYPE} eq 'text' ) {
954 0         0 $len = length($wdata);
955 0         0 $a->{VALUE} = _trimNul( substr( $wdata, $p ) );
956             }
957             else {
958 0         0 carp( "unknown waveform field type: " . $a->{TYPE} );
959 0         0 return undef;
960             }
961 0         0 $a->{LENGTH} = $len;
962 0         0 return $a;
963             }
964              
965             # IEEE754 single precision (binary32): assumes MSB data ('>')
966             sub _float {
967 0     0   0 my $str = shift;
968 0         0 my $s = ( $str >> 31 ) & 0x0001;
969 0         0 my $e = ( $str >> 23 ) & 0x00FF;
970 0         0 my $f = $str & 0x007FFFFF;
971 0         0 my $w = ( 2**( $e - 127 ) ) * ( 1 + ( $f / 0x00800000 ) );
972 0 0       0 $w = -$w if $s;
973 0         0 return $w;
974             }
975              
976 3     3 0 1704 sub double_from_hex { unpack 'd', scalar reverse pack 'H*', $_[0] }
977              
978 1     1   9 use constant POS_INF => double_from_hex '7FF0000000000000';
  1         2  
  1         6  
979 1     1   8 use constant NEG_INF => double_from_hex 'FFF0000000000000';
  1         4  
  1         3  
980 1     1   7 use constant NaN => double_from_hex '7FF8000000000000';
  1         3  
  1         4  
981              
982             sub _double # assumes MSB data input
983             {
984             # my ($bytes) = @_;
985             # my ($bottom, $top) = unpack ("LL", $bytes);
986 0     0     my ( $top, $bottom ) = @_;
987              
988             # Reference:
989             # http://en.wikipedia.org/wiki/Double_precision_floating-point_format
990              
991             # Eight zero bytes represents 0.0.
992 0 0         if ( $bottom == 0 ) {
    0          
993 0 0         if ( $top == 0 ) {
    0          
    0          
    0          
994 0           return 0;
995             }
996             elsif ( $top == 0x80000000 ) {
997 0           return -0;
998             }
999             elsif ( $top == 0x7ff00000 ) {
1000 0           return POS_INF;
1001             }
1002             elsif ( $top == 0xfff00000 ) {
1003 0           return NEG_INF;
1004             }
1005             }
1006             elsif ( $top == 0x7ff00000 ) {
1007 0           return NaN;
1008             }
1009 0           my $sign = $top >> 31;
1010              
1011             # print "sgn $sign\n";
1012 0           my $exponent = ( ( $top >> 20 ) & 0x7FF ) - 1023;
1013              
1014             # print "e = $exponent\n";
1015 0           my $e = ( $top >> 20 ) & 0x7FF;
1016 0           my $t = $top & 0xFFFFF;
1017              
1018             # printf ("--> !%011b%020b \n--> %032b\n", $e, $t, $top);
1019 0           my $mantissa = ( $bottom + ( $t * ( 2**32 ) ) ) / 2**52 + 1;
1020              
1021             # print "mant: $mantissa\n";
1022 0           my $double = (-1)**$sign * 2**$exponent * $mantissa;
1023              
1024             # print "double result: $double\n";
1025 0           return $double;
1026             }
1027              
1028             # IEEE754 double precision (binary64)
1029             #sub _Xdouble {
1030             # my $str = shift;
1031             # my $s = ($str >> 63) & 0x1;
1032             # my $e = ($str >> 52) & 0x7FF;
1033             # my $f = $str & 0x000FFFFFFFFFFFFF ;
1034             # my $w = (2**($e-1023))*(1+$f/0x0010000000000000);
1035             # $w = -$w if $s;
1036             # return $w;
1037             #}
1038              
1039             sub _interpolate {
1040 0     0     my $h = shift; # hash pointer to {CHAN}->{$ch}
1041 0 0         if ( ref($h) ne 'HASH' ) {
1042 0           carp("bad hash pointer for wfd interpolation");
1043 0           return undef;
1044             }
1045 0           my $x = shift;
1046              
1047 0 0 0       return undef if $x < $h->{XMIN} || $x > $h->{XMAX};
1048              
1049 0           my $nx = ( $x - $h->{XMIN} ) / $h->{DX};
1050 0           my $nx0 = int($nx);
1051 0           my ( $y0, $y1, $ry0, $ry1 );
1052 0 0         if ( exists( $h->{Y} ) ) {
1053 0           $y0 = $h->{Y}->[$nx0];
1054 0           $y1 = $h->{Y}->[ $nx0 + 1 ];
1055 0           return $y0 + ( $y1 - $y0 ) * ( $nx - $nx0 );
1056             }
1057             else {
1058 0           $y0 = $h->{Y0}->[$nx0];
1059 0           $y1 = $h->{Y0}->[ $nx0 + 1 ];
1060 0           $ry0 = ( $y1 - $y0 ) * ( $nx - $nx0 );
1061              
1062 0           $y0 = $h->{Y1}->[$nx0];
1063 0           $y1 = $h->{Y1}->[ $nx0 + 1 ];
1064 0           $ry1 = ( $y1 - $y0 ) * ( $nx - $nx0 );
1065 0           return ( $ry0, $ry1 );
1066             }
1067             }
1068              
1069             sub _extractWaveform {
1070 0     0     my $enc = shift;
1071 0           my $wd = shift;
1072 0           my $dat = shift;
1073              
1074 0           my (@result);
1075              
1076 0           $enc =~ s/^\s*//;
1077              
1078 0 0         if ( $enc =~ /^ASC/i ) {
1079 0           @result = split( /,/, $dat );
1080             }
1081             else {
1082 0 0         if ( substr( $dat, 0, 2 ) !~ /^#\d/ ) {
1083 0           croak("bad binary curve data");
1084             }
1085 0           my $nx = substr( $dat, 1, 1 );
1086 0           my $n = substr( $dat, 2, $nx );
1087 0           my $form;
1088 0 0         if ( $wd == 1 ) {
1089 0 0         if ( $enc =~ /^RPB/i ) {
1090 0           $form = 'C';
1091             }
1092             else {
1093 0           $form = 'c';
1094             }
1095             }
1096             else {
1097 0 0         if ( $enc =~ /RPB/i ) {
1098 0           $form = 'S'; # unsigned
1099             }
1100             else {
1101 0           $form = 's'; # RIB signed
1102             }
1103 0 0         if ( $enc =~ /^S/i ) { # LSB first
1104 0           $form .= '<';
1105             }
1106             else {
1107 0           $form .= '>'; # MSB first
1108             }
1109             }
1110 0           $form .= '*';
1111 0           @result = unpack( $form, substr( $dat, $nx + 2 ) );
1112             }
1113 0           return (@result);
1114             }
1115              
1116             sub _ParseTemplate {
1117 0     0     my $self = shift;
1118 0           my $input = shift;
1119              
1120 0           my $f;
1121 0           my (@blox) = ();
1122 0           my $inenum = 0;
1123 0           my $x;
1124             my $scopish;
1125              
1126             # my $ln = 0;
1127              
1128 0           foreach ( split( /\n/, $input ) ) {
1129              
1130             # $ln++;
1131 0           chomp;
1132 0 0         next if /^\s*$/;
1133              
1134 0 0 0       if ( !defined($scopish) && /^(TMPL|TEMPLATE)\s+\"/i ) {
1135 0           $scopish = 1;
1136 0           next;
1137             }
1138              
1139 0 0 0       if ( defined($scopish) && $scopish && /^\"/ ) {
      0        
1140 0           last;
1141             }
1142              
1143 0 0 0       if ( !defined($scopish) && /^(\/|0|;)/ ) {
1144 0           $scopish = 0;
1145             }
1146              
1147 0           s/;.*$//;
1148 0 0         next if /^\s*$/;
1149 0 0         next if /^\s*\/00/;
1150 0 0         next if /^\s*00/i;
1151 0 0         next if /^\s+\d[\d\s]+/; # funky 8 111 333 string
1152              
1153 0 0         if ($inenum) {
1154 0 0         if (/^\s*endenum(\s|$)/i) {
    0          
1155 0           $inenum = 0;
1156             }
1157             elsif (/^\s*_(\d+)\s+(.+)(\s|$)/i) {
1158 0           $x->{ENUM}->{$1} = $2;
1159             }
1160             else {
1161 0           carp("Error parsing enum entry: $_");
1162             }
1163 0           next;
1164             }
1165              
1166 0 0         if (/^\s*(\w+)\s*:\s*(\w+)/i) {
    0          
1167 0           my $entry = [ "$1:$2", [] ];
1168 0           $f = $entry->[1];
1169 0           push( @blox, $entry );
1170              
1171             }
1172             elsif (/^\s*\<\s*(\d+)\s*\>\s+(\w+):\s*(\w+)/i) {
1173 0           $x = {
1174             OFFSET => $1,
1175             NAME => $2,
1176             TYPE => $3,
1177             };
1178              
1179 0 0         if ( lc( $x->{TYPE} ) eq 'enum' ) {
1180 0           $x->{ENUM} = {};
1181 0           $inenum = 1;
1182             }
1183 0           push( @{$f}, $x );
  0            
1184             }
1185             else {
1186 0           carp("error parsing line: $_");
1187             }
1188             }
1189              
1190 0 0         print Dumper( \@blox ), "\n" if $DEBUG;
1191 0           return [@blox];
1192             }
1193              
1194             sub _ParseHeader {
1195 0     0     my $self = shift;
1196 0           my $ev = shift;
1197 0           my $opt = shift;
1198 0           my $stream = $self->{STREAM}->{NUMBER};
1199              
1200 0 0         if ( defined( $ev->{RUNHEADER} ) ) {
1201 0           foreach my $g ( @{ $ev->{RUNHEADER}->{STREAM}->{$stream}->{GPIB} } ) {
  0            
1202 0 0         next unless $g =~ /^\s*\<(TMPL|TEMPLATE)/i;
1203             $self->{TEMPLATE}
1204 0           = $self->_ParseTemplate( substr( $g, 1 ), $opt );
1205             }
1206             }
1207              
1208 0           $self->{PARSED_HEADER} = 1;
1209             }
1210              
1211             1; # End of Lab::Data::Analysis::TekTDS
1212              
1213             __END__
1214              
1215             =pod
1216              
1217             =encoding UTF-8
1218              
1219             =head1 NAME
1220              
1221             Lab::Data::Analysis::WaveRunner - Analysis routine for LeCroy WaveRunner/etc. scopes
1222              
1223             =head1 VERSION
1224              
1225             version 3.880
1226              
1227             =head1 SUBROUTINES/METHODS
1228              
1229             =head2 new
1230              
1231             my $a = Lab::Data::Analysis::WaveRunner->new(stream=>$stream);
1232              
1233             create a new WaveRunner analysis object; for use by Lab::Data::Analysis
1234             code
1235              
1236             =head2 Analyze
1237              
1238             my $event = $a->Analyze($event[, optionshash]);
1239              
1240             Do analysis on an event (passed by hashref); the
1241             results of the analysis are stored in the hashref, and the
1242             hashref is returned.
1243              
1244             If there is an error, "undef" is returned.
1245              
1246             The analysis results can be found in
1247              
1248             $event->{CHAN}->{$channel}->{
1249              
1250             CHAN => channel name,
1251              
1252             X => [ ... x values ... typically times ],
1253              
1254             Yunit => unit for Y scale,
1255              
1256             Xunit => unit for X scale,
1257              
1258             ID => ID string describing waveform,
1259              
1260             START => $jstart ... $X->[$jstart] is first sample
1261            
1262             STOP => $jstop ... $X->[$jstop] is last sample
1263              
1264             two options:
1265              
1266             Y => [ ... y values... typically voltages ],
1267              
1268             or
1269              
1270             YMIN => [ ... min y values ...], YMAX=> [... max y values..],
1271              
1272             The YMIN,YMAX arrays are returned for 'envelope' type waveforms.
1273              
1274             To get the usual time/voltage pairs:
1275              
1276             for ($j = $ev->{CHAN}->{CH1}->{START};
1277            
1278             $j <= $ev->{CHAN}->{CH1}->{STOP}; $j++) {
1279              
1280             $t = $ev->{CHAN}->{CH1}->X->[$j];
1281              
1282             $v = $ev->{CHAN}->{CH1}->Y->[$j];
1283              
1284             }
1285              
1286             Analysis options:
1287              
1288             dropraw => [def: 0] ... drop the raw analysis intermediate results
1289             interpolate => [def: 1] ... create a Yfunc interpolation function
1290              
1291             =head1 COPYRIGHT AND LICENSE
1292              
1293             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
1294              
1295             Copyright 2016 Charles Lane
1296             2017 Andreas K. Huettel
1297             2020 Andreas K. Huettel
1298              
1299              
1300             This is free software; you can redistribute it and/or modify it under
1301             the same terms as the Perl 5 programming language system itself.
1302              
1303             =cut