File Coverage

blib/lib/Lab/Data/Analysis/TekTDS.pm
Criterion Covered Total %
statement 158 219 72.1
branch 37 74 50.0
condition 9 27 33.3
subroutine 15 16 93.7
pod 2 2 100.0
total 221 338 65.3


line stmt bran cond sub pod time code
1             package Lab::Data::Analysis::TekTDS;
2             #ABSTRACT: Analysis routine for Tektronix TDS1000/TDS2000/etc. scopes
3             $Lab::Data::Analysis::TekTDS::VERSION = '3.880';
4 2     2   1968 use v5.20;
  2         9  
5              
6 2     2   11 use strict;
  2         4  
  2         48  
7 2     2   11 use warnings;
  2         3  
  2         58  
8 2     2   17 use Carp;
  2         5  
  2         146  
9 2     2   15 use Data::Dumper;
  2         4  
  2         105  
10 2     2   541 use Lab::SCPI;
  2         6  
  2         175  
11 2     2   1335 use Lab::Instrument::TDS2024B;
  2         8  
  2         103  
12 2     2   16 use Lab::Data::Analysis;
  2         5  
  2         53  
13 2     2   10 use Clone qw(clone);
  2         10  
  2         2495  
14              
15             our @ISA = ("Lab::Data::Analysis");
16              
17             our $DEBUG = 0;
18              
19             # default config values, copied to $self->{CONFIG} initially
20              
21             our $DEFAULT_CONFIG = {};
22              
23              
24             sub new {
25 1     1 1 6 my $proto = shift;
26 1   33     10 my $class = ref($proto) || $proto;
27 1         3 my $self = {};
28 1         3 bless $self, $class;
29              
30 1         6 my ( $stream, $tail )
31             = Lab::Data::Analysis::_check_args( \@_, qw(stream) );
32              
33 1         6 $self->{STREAM} = $stream; # hash of stream fileheader info
34              
35 1         9 return $self;
36             }
37              
38              
39             sub Analyze {
40 1     1 1 3 my $self = shift;
41 1         3 my $event = shift;
42              
43             # handle analysis options
44 1         2 my $option = shift;
45 1 50 33     6 $option = {} unless defined $option && ref($option) eq 'HASH';
46 1 50       6 $option->{dropraw} = 0 unless exists $option->{dropraw};
47 1 50       5 $option->{interpolate} = 1 unless exists $option->{interpolate};
48 1 50       4 $option->{print_summary} = 0 unless exists $option->{print_summary};
49              
50 1         3 my $stream = $self->{STREAM}->{NUMBER};
51              
52 1         2 my $a = {};
53 1         2 $a->{MODULE} = 'TekTDS';
54 1         2 $a->{RAW} = {};
55 1         3 $a->{RAW}->{CHAN} = {};
56 1         1 $a->{CHAN} = {};
57 1         3 $a->{COMMENT} = [];
58 1         3 $a->{RUN} = $event->{RUN};
59 1         3 $a->{EVENT} = $event->{EVENT};
60 1         1 $a->{STREAM} = $stream;
61 1         18 $a->{OPTIONS} = clone($option);
62              
63             # event->{ANALYZE}->{stream#}->{TekTDS}->{analysis stuff}?
64              
65 1         2 foreach my $c ( @{ $event->{STREAM}->{$stream}->{COMMENT} } ) {
  1         7  
66 0         0 push( @{ $a->{COMMENT} }, $c );
  0         0  
67             }
68              
69 1         2 my $ch;
70 1         2 my $seq = [];
71 1         1 foreach my $g ( @{ $event->{STREAM}->{$stream}->{GPIB} } ) {
  1         8  
72 21         55 my $str = substr( $g, 1 );
73 21 100       53 next if $str =~ /^\d+/;
74 15         36 $seq = scpi_parse_sequence( $str, $seq );
75             }
76 1 50       5 print "seq = ", Dumper($seq), "\n", if $DEBUG;
77              
78             my $fseq = scpi_flat(
79             $seq,
80             $Lab::Instrument::TDS2024B::fields{scpi_override}
81 1         4 );
82 1 50       7 print "fseq = ", Dumper($fseq), "\n" if $DEBUG;
83              
84 1         5 for ( my $j = 0; exists( $fseq->[$j] ); $j++ ) {
85 36 100       68 if ( exists( $fseq->[$j]->{'DAT:SOU'} ) ) {
86 2         15 $ch = $fseq->[$j]->{'DAT:SOU'};
87             $a->{RAW}->{CHAN}->{$ch} = {}
88 2 100       9 unless exists $a->{RAW}->{CHAN}->{$ch};
89 2 100       6 $a->{CHAN}->{$ch} = {} unless exists $a->{CHAN}->{$ch};
90 2         4 $a->{CHAN}->{$ch}->{CHAN} = $ch;
91 2         4 $a->{RAW}->{CHAN}->{$ch}->{CHAN} = $ch;
92             }
93              
94 36         43 foreach my $k ( keys( %{ $fseq->[$j] } ) ) {
  36         85  
95 36 50       80 print "\$fseq->[$j]->{$k} = '", $fseq->[$j]->{$k}, "'\n"
96             if $DEBUG;
97 36 100       96 if ( $k =~ /^(DAT|WFMP|CURV)/ ) {
98 25         101 $a->{RAW}->{CHAN}->{$ch}->{$k} = $fseq->[$j]->{$k};
99             }
100             }
101              
102             }
103 1 50       4 print Dumper($a) if $DEBUG > 2;
104 1 50       4 $self->_PrintSummary( $a, $option ) if $option->{print_summary};
105              
106 1         1 foreach $ch ( keys( %{ $a->{RAW}->{CHAN} } ) ) {
  1         7  
107 1         2 my $id = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:WFI'};
108 1         4 my $x0 = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:XZE'};
109 1         2 my $y0 = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:YZE'};
110 1         3 my $dx = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:XIN'};
111 1         2 my $xoff = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:PT_O'};
112 1         2 my $xun = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:XUN'};
113 1         3 my $dy = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:YMU'};
114 1         2 my $yoff = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:YOF'};
115 1         2 my $yun = $a->{RAW}->{CHAN}->{$ch}->{'WFMP:YUN'};
116 1         2 my $j0 = $a->{RAW}->{CHAN}->{$ch}->{'DAT:STAR'};
117 1         2 my $j1 = $a->{RAW}->{CHAN}->{$ch}->{'DAT:STOP'};
118 1         2 my $enc = $a->{RAW}->{CHAN}->{$ch}->{'DAT:ENC'};
119 1         2 my $wd = $a->{RAW}->{CHAN}->{$ch}->{'DAT:WID'};
120 1         3 my $d = $a->{RAW}->{CHAN}->{$ch}->{'CURV'};
121              
122 1         7 $id =~ s/^\"(.*)\"/$1/;
123 1         3 $a->{CHAN}->{$ch}->{ID} = $id;
124 1         8 $xun =~ s/^\"(.*)\"/$1/;
125 1         4 $yun =~ s/^\"(.*)\"/$1/;
126 1         2 $a->{CHAN}->{$ch}->{Xunit} = $xun;
127 1         3 $a->{CHAN}->{$ch}->{Yunit} = $yun;
128 1         2 $a->{CHAN}->{$ch}->{DX} = $dx;
129 1         8 $a->{CHAN}->{$ch}->{X} = [];
130              
131 1         17 my (@dat) = _extractWaveform( $enc, $wd, $d );
132 1         13 my ( $ymin, $ymax );
133 1 50       5 if ( $a->{RAW}->{CHAN}->{$ch}->{'WFMP:PT_F'} eq 'Y' ) {
134 1         3 $a->{CHAN}->{$ch}->{Y} = [];
135 1         5 $a->{CHAN}->{$ch}->{START} = $j0;
136 1         3 $a->{CHAN}->{$ch}->{STOP} = $j1;
137 1         5 for ( my $j = 0; $j <= $#dat; $j++ ) {
138 2500         4962 $a->{CHAN}->{$ch}->{X}->[ $j + $j0 ]
139             = $x0 + $dx * ( $j - $xoff );
140 2500         3814 my $y = $y0 + $dy * ( $dat[$j] - $yoff );
141 2500         4158 $a->{CHAN}->{$ch}->{Y}->[ $j + $j0 ] = $y;
142 2500 100 100     6631 $ymin = $y unless defined $ymin && $y > $ymin;
143 2500 100 100     7686 $ymax = $y unless defined $ymax && $y < $ymax;
144             }
145             }
146             else { #envelope
147 0         0 $a->{CHAN}->{$ch}->{Y0} = [];
148 0         0 $a->{CHAN}->{$ch}->{Y1} = [];
149 0         0 $a->{CHAN}->{$ch}->{START} = $j0;
150 0         0 $a->{CHAN}->{$ch}->{STOP} = $j0 + $#dat / 2;
151 0         0 for ( my $j = 0; $j <= $#dat; $j += 2 ) {
152 0         0 $a->{CHAN}->{$ch}->{X}->[ $j / 2 + $j0 ]
153             = $x0 + $dx * ( $j - $xoff );
154 0         0 my $y = $y0 + $dy * ( $dat[$j] - $yoff );
155 0         0 $a->{CHAN}->{$ch}->{Y0}->[ $j / 2 + $j0 ] = $y;
156 0 0 0     0 $ymin = $y unless defined $ymin && $y > $ymin;
157 0 0 0     0 $ymax = $y unless defined $ymax && $y < $ymax;
158              
159 0         0 $y = $y0 + $dy * ( $dat[ $j + 1 ] - $yoff );
160 0         0 $a->{CHAN}->{$ch}->{Y1}->[ $j / 2 + $j0 ] = $y;
161 0 0 0     0 $ymin = $y unless defined $ymin && $y > $ymin;
162 0 0 0     0 $ymax = $y unless defined $ymax && $y < $ymax;
163             }
164             }
165 1         6 $a->{CHAN}->{$ch}->{YMIN} = $ymin;
166 1         4 $a->{CHAN}->{$ch}->{YMAX} = $ymax;
167              
168 1         5 $a->{CHAN}->{$ch}->{XMIN} = $a->{CHAN}->{$ch}->{X}->[$j0];
169 1         4 $a->{CHAN}->{$ch}->{XMAX} = $a->{CHAN}->{$ch}->{X}->[$j1];
170              
171             #
172             # creates an anonymous sub that interpolates into the waveform
173             #
174 1 50       6 if ( $option->{interpolate} ) {
175             $a->{CHAN}->{$ch}->{Yfunc} = sub {
176 2     2   26 use feature 'state';
  2         4  
  2         2801  
177 10     10   36 state $hchan;
178 10 100       19 $hchan = $a->{CHAN}->{$ch} unless defined $hchan;
179 10         21 return ( _interpolate( $hchan, @_ ) );
180 1         13 };
181 1         6 $a->{CHAN}->{$ch}->{Yfunc}->(0); # initialize state var
182             }
183             }
184 1 50       4 delete( $a->{RAW} ) if $option->{dropraw};
185 1 50       7 $event->{ANALYZE} = {} unless exists $event->{ANALYZE};
186              
187             $event->{ANALYZE}->{$stream} = {}
188 1 50       7 unless exists $event->{ANALYZE}->{$stream};
189              
190 1         3 $event->{ANALYZE}->{$stream}->{TekTDS} = $a;
191              
192             # push(@{$event->{ANALYZED}},$a);
193 1         88 return $event;
194             }
195              
196             sub _interpolate {
197 10     10   16 my $h = shift; # hash pointer to {CHAN}->{$ch}
198 10 50       23 if ( ref($h) ne 'HASH' ) {
199 0         0 carp("bad hash pointer for wfd interpolation");
200 0         0 return undef;
201             }
202 10         13 my $x = shift;
203              
204 10 50 33     35 return undef if $x < $h->{XMIN} || $x > $h->{XMAX};
205              
206 10         21 my $nx = ( $x - $h->{XMIN} ) / $h->{DX};
207 10         16 my $nx0 = int($nx);
208 10         13 my ( $y0, $y1, $ry0, $ry1 );
209 10 50       23 if ( exists( $h->{Y} ) ) {
210 10         13 $y0 = $h->{Y}->[$nx0];
211 10         20 $y1 = $h->{Y}->[ $nx0 + 1 ];
212 10         81 return $y0 + ( $y1 - $y0 ) * ( $nx - $nx0 );
213             }
214             else {
215 0         0 $y0 = $h->{Y0}->[$nx0];
216 0         0 $y1 = $h->{Y0}->[ $nx0 + 1 ];
217 0         0 $ry0 = ( $y1 - $y0 ) * ( $nx - $nx0 );
218              
219 0         0 $y0 = $h->{Y1}->[$nx0];
220 0         0 $y1 = $h->{Y1}->[ $nx0 + 1 ];
221 0         0 $ry1 = ( $y1 - $y0 ) * ( $nx - $nx0 );
222 0         0 return ( $ry0, $ry1 );
223             }
224             }
225              
226             sub _PrintSummary {
227 0     0   0 my $self = shift;
228 0         0 my $a = shift;
229 0         0 my $opt = shift;
230              
231             print "TekTDS Analysis Summary: Run ", $a->{RUN},
232 0         0 " Event ", $a->{EVENT}, " Stream ", $a->{STREAM}, "\n";
233              
234 0         0 print "\nAnalysis Options:\n";
235 0         0 foreach my $k ( sort( keys( %{ $a->{OPTIONS} } ) ) ) {
  0         0  
236 0         0 print "\t $k = ", $a->{OPTIONS}->{$k}, "\n";
237             }
238              
239 0         0 print "\nDAQ inline comments:\n";
240 0         0 foreach my $c ( @{ $a->{COMMENT} } ) {
  0         0  
241 0         0 print "\t \"$c\"\n";
242             }
243              
244 0         0 print "\nChannels:";
245 0         0 foreach my $ch ( sort( keys( %{ $a->{RAW}->{CHAN} } ) ) ) {
  0         0  
246 0         0 print " $ch";
247             }
248 0         0 print "\n";
249              
250 0         0 foreach my $ch ( sort( keys( %{ $a->{RAW}->{CHAN} } ) ) ) {
  0         0  
251 0         0 print "Channel $ch info: \n";
252              
253 0         0 foreach my $k ( sort( keys( %{ $a->{RAW}->{CHAN}->{$ch} } ) ) ) {
  0         0  
254 0 0       0 next if $k =~ /\?$/;
255 0 0       0 next if $k eq 'CURV';
256 0         0 my $key = sprintf( "%-18s", $k );
257 0         0 print "\t$key : ", $a->{RAW}->{CHAN}->{$ch}->{$k}, "\n";
258             }
259 0         0 print "\n";
260             }
261              
262             }
263              
264             sub _extractWaveform {
265 1     1   6 my $enc = shift;
266 1         1 my $wd = shift;
267 1         2 my $dat = shift;
268              
269 1         2 my (@result);
270              
271 1         6 $enc =~ s/^\s*//;
272              
273 1 50       9 if ( $enc =~ /^ASC/i ) {
274 0         0 @result = split( /,/, $dat );
275             }
276             else {
277 1 50       12 if ( substr( $dat, 0, 2 ) !~ /^#\d/ ) {
278 0         0 croak("bad binary curve data");
279             }
280 1         2 my $nx = substr( $dat, 1, 1 );
281 1         3 my $n = substr( $dat, 2, $nx );
282 1         1 my $form;
283 1 50       6 if ( $wd == 1 ) {
284 1 50       3 if ( $enc =~ /^RPB/i ) {
285 1         2 $form = 'C';
286             }
287             else {
288 0         0 $form = 'c';
289             }
290             }
291             else {
292 0 0       0 if ( $enc =~ /RPB/i ) {
293 0         0 $form = 'S'; # unsigned
294             }
295             else {
296 0         0 $form = 's'; # RIB signed
297             }
298 0 0       0 if ( $enc =~ /^S/i ) { # LSB first
299 0         0 $form .= '<';
300             }
301             else {
302 0         0 $form .= '>'; # MSB first
303             }
304             }
305 1         3 $form .= '*';
306 1         269 @result = unpack( $form, substr( $dat, $nx + 2 ) );
307             }
308 1         213 return (@result);
309             }
310              
311             1; # End of Lab::Data::Analysis::TekTDS
312              
313             __END__
314              
315             =pod
316              
317             =encoding UTF-8
318              
319             =head1 NAME
320              
321             Lab::Data::Analysis::TekTDS - Analysis routine for Tektronix TDS1000/TDS2000/etc. scopes
322              
323             =head1 VERSION
324              
325             version 3.880
326              
327             =head1 SUBROUTINES/METHODS
328              
329             =head2 new
330              
331             my $a = Lab::Data::Analysis::TekTDS->new(stream=>$stream);
332              
333             create a new TekTDS analysis object; for use by Lab::Data::Analysis
334             code
335              
336             =head2 Analyze
337              
338             my $event = $a->Analyze($event[, optionshash]);
339              
340             Do TekTDS analysis on an event (passed by hashref); the
341             results of the analysis are stored in the hashref, and the
342             hashref is returned.
343              
344             If there is an error, "undef" is returned.
345              
346             The analysis results can be found in
347              
348             $event->{CHAN}->{$channel}->{
349              
350             CHAN => channel name,
351              
352             X => [ ... x values ... typically times ],
353              
354             Yunit => unit for Y scale,
355              
356             Xunit => unit for X scale,
357              
358             ID => ID string describing waveform,
359              
360             START => $jstart ... $X->[$jstart] is first sample
361            
362             STOP => $jstop ... $X->[$jstop] is last sample
363              
364             two options:
365              
366             Y => [ ... y values... typically voltages ],
367              
368             or
369              
370             YMIN => [ ... min y values ...], YMAX=> [... max y values..],
371              
372             The YMIN,YMAX arrays are returned for 'envelope' type waveforms.
373              
374             To get the usual time/voltage pairs:
375              
376             for ($j = $ev->{CHAN}->{CH1}->{START};
377            
378             $j <= $ev->{CHAN}->{CH1}->{STOP}; $j++) {
379              
380             $t = $ev->{CHAN}->{CH1}->X->[$j];
381              
382             $v = $ev->{CHAN}->{CH1}->Y->[$j];
383              
384             }
385              
386             Analysis options:
387              
388             dropraw => [def: 0] ... drop the raw analysis intermediate results
389             interpolate => [def: 1] ... create a Yfunc interpolation function
390             print_summary => [def: 0] ..print a summary of waveform info
391              
392             =head1 COPYRIGHT AND LICENSE
393              
394             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
395              
396             Copyright 2016 Charles Lane
397             2017 Andreas K. Huettel
398             2020 Andreas K. Huettel
399              
400              
401             This is free software; you can redistribute it and/or modify it under
402             the same terms as the Perl 5 programming language system itself.
403              
404             =cut