File Coverage

blib/lib/CracTools/SAMReader/SAMline.pm
Criterion Covered Total %
statement 230 291 79.0
branch 67 126 53.1
condition 12 30 40.0
subroutine 39 44 88.6
pod 39 39 100.0
total 387 530 73.0


line stmt bran cond sub pod time code
1             package CracTools::SAMReader::SAMline;
2             {
3             $CracTools::SAMReader::SAMline::DIST = 'CracTools';
4             }
5             # ABSTRACT: The object for manipulation a SAM line.
6             $CracTools::SAMReader::SAMline::VERSION = '1.25';
7              
8 3     3   13814 use strict;
  3         4  
  3         64  
9 3     3   9 use warnings;
  3         3  
  3         53  
10 3     3   9 use Carp;
  3         2  
  3         155  
11              
12 3     3   797 use CracTools::Utils;
  3         3  
  3         7788  
13              
14              
15             our %flags = ( MULTIPLE_SEGMENTS => 1,
16             PROPERLY_ALIGNED => 2,
17             UNMAPPED => 4,
18             NEXT_UNMAPPED => 8,
19             REVERSE_COMPLEMENTED => 16,
20             NEXT_REVERSE_COMPLEMENTED => 32,
21             FIRST_SEGMENT => 64,
22             LAST_SEGMENT => 128,
23             SECONDARY_ALIGNMENT => 256,
24             QUALITY_CONTROLS_FAILED => 512,
25             PCR_DUPLICATED => 1024,
26             CHIMERIC_ALIGNMENT => 2048,
27             );
28              
29              
30              
31             sub hasEvent {
32 2     2 1 12 my ($line,$event_type) = @_;
33 2 50 33     10 croak("Missing argument(s)") unless defined $line && defined $event_type;
34 2         47 return $line =~ /XE:Z:\d+:\d+:$event_type/i;
35             }
36              
37              
38             sub new {
39 8     8 1 9 my $class = shift;
40 8         12 my ($line) = @_;
41 8         11 chomp $line;
42 8         43 my ($qname,$flag,$rname,$pos,$mapq,$cigar,$rnext,$pnext,$tlen,$seq,$qual,@others) = split("\t",$line);
43 8         11 my %extended_fields;
44 8         16 foreach(@others) {
45 16         16 my $key = substr($_,0,2);
46 16         17 my $value = substr($_,5);
47             #my($key,$type,$value) = split(':',$_,3);
48             # Hack in case there is multiple field with the same tag
49 16 100       38 $extended_fields{$key} = defined $extended_fields{$key}? $extended_fields{$key}.";".$value : $value;
50             }
51            
52             # We do not want any "chr" string before the reference sequence value
53 8         14 $rname =~ s/^chr//;
54              
55 8         74 my $self = bless{
56             qname => $qname,
57             flag => $flag,
58             rname => $rname,
59             pos => $pos,
60             mapq => $mapq,
61             cigar => $cigar,
62             rnext => $rnext,
63             pnext => $pnext,
64             tlen => $tlen,
65             seq => $seq,
66             qual => $qual,
67             extended_fields => \%extended_fields,
68             line => $line,
69             }, $class;
70              
71 8         25 return $self;
72             }
73              
74              
75             sub isFlagged {
76 3     3 1 7 my $self = shift;
77 3         3 my $flag = shift;
78 3         7 return $self->flag & $flag;
79             }
80              
81              
82             sub getStrand {
83 0     0 1 0 my $self = shift;
84 0 0       0 if($self->isFlagged($flags{REVERSE_COMPLEMENTED})) {
85 0         0 return -1;
86             } else {
87 0         0 return 1;
88             }
89             }
90              
91              
92             sub getOriginalSeq {
93 0     0 1 0 my $self = shift;
94 0 0       0 if($self->isFlagged($flags{REVERSE_COMPLEMENTED})) {
95 0         0 return CracTools::Utils::reverseComplement($self->seq);
96             } else {
97 0         0 return $self->seq;
98             }
99             }
100              
101              
102             sub getLocAsCracFormat {
103 0     0 1 0 my $self = shift;
104 0         0 return $self->rname."|".$self->getStrand.",".$self->pos;
105             }
106              
107              
108             sub getPatch {
109 0     0 1 0 my $self = shift;
110 0         0 my $line_number = shift;
111 0 0       0 croak("Cannot generate patch without the line number in argument") unless defined $line_number;
112 0 0       0 if($self->updatedLine ne $self->line) {
113 0         0 my $line1 = $self->line;
114 0         0 my $line2 = $self->updatedLine;
115 0         0 chomp($line1);
116 0         0 chomp($line2);
117 0         0 return "@@ -$line_number,1 +$line_number,1 @@\n-$line1\n+$line2";
118             } else {
119 0         0 return 0;
120             }
121             }
122              
123              
124              
125             sub line {
126 2     2 1 2 my $self = shift;
127 2         14 return $self->{line};
128             }
129              
130              
131             sub updatedLine {
132 3     3 1 5 my $self = shift;
133 3         3 my $updated_line = shift;
134 3 100       8 if(defined $updated_line) {
    100          
135 1         3 $self->{updated_line} = $updated_line;
136 1         7 return 1;
137             } elsif (!defined $self->{updated_line}) {
138 1         5 return $self->line;
139             } else {
140 1         10 return $self->{updated_line};
141             }
142             }
143              
144              
145             sub qname {
146 3     3 1 683 my $self = shift;
147 3         2 my $new_qname = shift;
148 3 100       8 if(defined $new_qname) {
149 1         3 $self->{qname} = $new_qname;
150             }
151 3         9 return $self->{qname};
152             }
153              
154              
155             sub flag {
156 6     6 1 6 my $self = shift;
157 6         6 my $new_flag = shift;
158 6 100       30 if(defined $new_flag) {
159 1         2 $self->{flag} = $new_flag;
160             }
161 6         24 return $self->{flag};
162             }
163              
164              
165             sub rname {
166 7     7 1 27 my $self = shift;
167 7         7 my $new_rname = shift;
168 7 100       14 if(defined $new_rname) {
169 3         4 $self->{rname} = $new_rname;
170             }
171 7         19 return $self->{rname};
172             }
173              
174              
175             sub chr {
176 3     3 1 5 my $self = shift;
177 3         5 $self->rname(@_);
178             }
179              
180              
181             sub pos {
182 3     3 1 3 my $self = shift;
183 3         3 my $new_pos = shift;
184 3 100       7 if(defined $new_pos) {
185 1         3 $self->{pos} = $new_pos;
186             }
187 3         7 return $self->{pos};
188             }
189              
190              
191             sub mapq {
192 3     3 1 3 my $self = shift;
193 3         4 my $new_mapq = shift;
194 3 100       6 if(defined $new_mapq) {
195 1         2 $self->{mapq} = $new_mapq;
196             }
197 3         10 return $self->{mapq};
198             }
199              
200              
201             sub cigar {
202 4     4 1 6 my $self = shift;
203 4         3 my $new_cigar = shift;
204 4 100       7 if(defined $new_cigar) {
205 1         2 $self->{cigar} = $new_cigar;
206             }
207 4         15 return $self->{cigar};
208             }
209              
210              
211             sub rnext {
212 3     3 1 3 my $self = shift;
213 3         3 my $new_rnext = shift;
214 3 100       7 if(defined $new_rnext) {
215 1         3 $self->{rnext} = $new_rnext;
216             }
217 3         8 return $self->{rnext};
218             }
219              
220              
221             sub pnext {
222 3     3 1 5 my $self = shift;
223 3         3 my $new_pnext = shift;
224 3 100       6 if(defined $new_pnext) {
225 1         2 $self->{pnext} = $new_pnext;
226             }
227 3         10 return $self->{pnext};
228             }
229              
230              
231             sub tlen {
232 3     3 1 5 my $self = shift;
233 3         3 my $new_tlen = shift;
234 3 100       7 if(defined $new_tlen) {
235 1         2 $self->{tlen} = $new_tlen;
236             }
237 3         8 return $self->{tlen};
238             }
239              
240              
241             sub seq {
242 4     4 1 385 my $self = shift;
243 4         13 my $new_seq = shift;
244 4 100       11 if(defined $new_seq) {
245 1         3 $self->{seq} = $new_seq;
246             }
247 4         14 return $self->{seq};
248             }
249              
250              
251             sub qual {
252 3     3 1 2 my $self = shift;
253 3         3 my $new_qual = shift;
254 3 100       7 if(defined $new_qual) {
255 1         3 $self->{qual} = $new_qual;
256             }
257 3         7 return $self->{qual};
258             }
259              
260              
261             sub getOptionalField {
262 1     1 1 1 my $self = shift;
263 1         1 my $field = shift;
264 1 50       4 croak("Missing \$field argument to call getOptionalField") unless defined $field;
265 1         4 return $self->{extended_fields}{$field};
266             }
267              
268              
269              
270             sub getChimericAlignments {
271 1     1 1 2 my $self = shift;
272             # check the existence of the SA field in the SAM line
273 1 50       3 if (defined $self->{extended_fields}{SA}){
274 1         2 my @array_hash;
275 1         4 my (@SA_alignments) = split(/;/,$self->{extended_fields}{SA});
276 1         4 for (my $i=0 ; $i < scalar @SA_alignments ; $i++){
277 2         8 my ($chr,$pos,$strand,$cigar,$mapq,$edist) = split(/,/,$SA_alignments[$i]);
278             # strand switch from "+,-" to "1,-1"
279 2 100       6 if ($strand eq '+'){
280 1         2 $strand = 1;
281             }else{
282 1         1 $strand = -1;
283             }
284 2         9 my $hash = { chr => $chr,
285             pos => $pos,
286             strand => $strand,
287             cigar => $cigar,
288             mapq => $mapq,
289             edist => $edist};
290 2         6 push(@array_hash,$hash);
291             }
292 1         4 return \@array_hash;
293             }
294 0         0 return undef;
295             }
296              
297              
298             sub getCigarOperatorsCount {
299 1     1 1 1604 my $self = shift;
300 1         3 my @ops = $self->cigar =~ /(\d+\D)/g;
301 1         1 my %ops_occ;
302 1         3 foreach (@ops) {
303 3         9 my ($nb,$op) = $_ =~ /(\d+)(\D)/;
304 3 100       7 $ops_occ{$op} = 0 unless defined $ops_occ{$op};
305 3         6 $ops_occ{$op} += $nb;
306             }
307 1         4 return \%ops_occ;
308             }
309              
310              
311             sub pSupport {
312 1     1 1 1 my $self = shift;
313 1         3 $self->loadSamDetailed;
314 1         4 return $self->{sam_detailed}{p_support};
315             }
316              
317              
318             sub pLoc {
319 1     1 1 2 my $self = shift;
320 1         2 $self->loadSamDetailed;
321 1         3 return $self->{sam_detailed}{p_loc};
322             }
323              
324              
325             sub pairedChimera {
326 1     1 1 2 my $self = shift;
327 1         3 $self->loadPaired();
328 1 50       3 if(defined $self->{extended_fields}{XP}{chimera}) {
329 1         3 my ($crac_loc1,$crac_loc2) = split(":",$self->{extended_fields}{XP}{chimera});
330 1         2 return (expandCracLoc($crac_loc1),expandCracLoc($crac_loc2));
331             } else {
332 0         0 return undef;
333             }
334             }
335              
336              
337             sub isPairedClassified {
338 3     3 1 1414 my $self = shift;
339 3         3 my $class = shift;
340 3         5 $self->loadPaired();
341              
342 3 100 66     15 if(defined $self->{extended_fields}{XP}{loc} && ref($self->{extended_fields}{XP}{loc}) ne 'HASH') {
343 1         4 my ($uniq,$dupli,$multi) = split(":",$self->{extended_fields}{XP}{loc});
344 1         5 $self->{extended_fields}{XP}{loc} = {unique => $uniq, duplicated => $dupli, multiple => $multi};
345              
346             }
347 3         10 return $self->{extended_fields}{XP}{loc}{$class};
348             }
349              
350              
351              
352             sub genericInfo {
353 2     2 1 4 my ($self,$key,$value) = @_;
354 2 50       7 if(!defined $key) {
    100          
355 0         0 die "You need to provide a key in order to set or get a genericInfo\n";
356             } elsif(defined $value) {
357 1         3 $self->{genericInfo}{$key} = $value;
358             } else {
359 1         4 return $self->{genericInfo}{$key};
360             }
361             }
362              
363              
364             sub isClassified {
365 2     2 1 3 my $self = shift;
366 2         2 my $class = shift;
367            
368 2 50       6 croak "Missing class argument" unless defined $class;
369              
370 2 100       7 if($class eq "unique") {
    50          
    50          
    0          
    0          
371 1         3 return $self->{extended_fields}{XU};
372             } elsif($class eq "duplicated") {
373 0         0 return $self->{extended_fields}{XD};
374             } elsif($class eq "multiple") {
375 1         4 return $self->{extended_fields}{XM};
376             } elsif($class eq "normal") {
377 0 0       0 defined $self->{extended_fields}{XN} ? return $self->{extended_fields}{XN} == 1 : return undef;
378             } elsif($class eq "almostNormal") {
379 0 0       0 defined $self->{extended_fields}{XN} ? return $self->{extended_fields}{XN} == 2 : return undef;
380             } else {
381 0         0 croak "Class argument ($class) does not match any case";
382             }
383             }
384              
385              
386             sub events {
387 3     3 1 5 my $self = shift;
388 3         4 my $event_type = lc shift;
389 3         6 $self->loadEvents();#$event_type);
390 3 50       6 if(defined $self->{events}{$event_type}) {
391 3         10 return $self->{events}{$event_type};
392             } else {
393 0         0 return [];
394             }
395             }
396              
397              
398             sub loadEvents {
399 3     3 1 2 my $self = shift;
400 3         4 my $event_type_to_load = shift;
401             ## TODO avoid double loading when doing lazy loading
402             #if(defined $event_type_to_load && defined $self->{$event_type_to_load}{loaded}) {
403             # return 0;
404             #}
405 3 100 66     11 if(!defined $self->{events} && defined $self->{extended_fields}{XE}) {
406             # Init events
407 1         3 my @events = split(";",$self->{extended_fields}{XE});
408 1         3 foreach my $event (@events) {
409 1         6 my ($event_id,$event_break_id,$event_type,$event_infos) = $event =~ /([^:]+):([^:]+):([^:]+):(.*)/g;
410 1         2 $event_type = lc $event_type;
411 1 50 33     10 next if(defined $event_type_to_load && $event_type ne $event_type_to_load);
412 1 50       3 if(defined $event_id) {
413 1         1 my %event_hash;
414 1 50 0     3 if($event_type eq 'junction') {
    0          
    0          
    0          
    0          
    0          
    0          
415 1         3 my ($type,$pos_read,$loc,$gap) = split(':',$event_infos);
416 1         4 my ($chr,$pos,$strand) = expandCracLoc($loc);
417 1         11 %event_hash = ( type => $type,
418             pos => $pos_read,
419             loc => {chr => $chr, pos => $pos, strand => $strand},
420             gap => $gap,
421             );
422             } elsif($event_type eq 'ins' || $event_type eq 'del') {
423 0         0 my ($score,$pos_read,$loc,$nb) = split(':',$event_infos);
424 0         0 my ($chr,$pos,$strand) = expandCracLoc($loc);
425 0         0 %event_hash = ( score => $score,
426             pos => $pos_read,
427             loc => {chr => $chr, pos => $pos, strand => $strand},
428             nb => $nb,
429             );
430             } elsif($event_type eq 'snp') {
431 0         0 my ($score,$pos_read,$loc,$expected,$actual) = split(':',$event_infos);
432 0         0 my ($chr,$pos,$strand) = expandCracLoc($loc);
433 0         0 %event_hash = ( score => $score,
434             pos => $pos_read,
435             loc => {chr => $chr, pos => $pos, strand => $strand},
436             expected => $expected,
437             actual => $actual,
438             );
439             } elsif($event_type eq 'error') {
440 0         0 my ($type,$pos,$score,$other1,$other2) = split(':',$event_infos);
441 0         0 %event_hash = ( score => $score,
442             pos => $pos,
443             type => $type,
444             other1 => $other1,
445             other2 => $other2,
446             );
447             } elsif($event_type eq 'chimera') {
448 0         0 my ($pos_read,$loc1,$loc2,$class,$score) = split(':',$event_infos);
449 0         0 my ($chr1,$pos1,$strand1) = expandCracLoc($loc1);
450 0         0 my ($chr2,$pos2,$strand2) = expandCracLoc($loc2);
451 0         0 %event_hash = ( pos => $pos_read,
452             loc1 => {chr => $chr1, pos => $pos1, strand => $strand1},
453             loc2 => {chr => $chr2, pos => $pos2, strand => $strand2},
454             class => $class,
455             score => $score,
456             );
457             } elsif($event_type eq 'undetermined') {
458 0         0 %event_hash = ( message => $event_infos,
459             );
460             } elsif($event_type eq 'bioundetermined') {
461 0         0 my ($pos,$message) = $event_infos =~ /([^:]+):(.*)/;
462 0         0 %event_hash = ( pos => $pos,
463             message => $message,
464             );
465             }
466 1 50       5 if (keys %event_hash > 1) {
467 1         2 $event_hash{event_id} = $event_id;
468 1         2 $event_hash{break_id} = $event_break_id;
469 1         2 $event_hash{event_type} = $event_type;
470 1         3 $self->addEvent(\%event_hash);
471             }
472             }
473             }
474             ## If we have only load a specific event type
475             #if(defined $event_type_to_load) {
476             # $self->{$event_type_to_load}{loaded} = 1;
477             ## Else we have load every events.
478             #} else {
479             # $self->{events}{loaded} = 1;
480             #}
481             }
482             }
483              
484              
485             sub addEvent {
486 2     2 1 2 my $self = shift;
487 2         2 my $event = shift;
488 2         3 my $event_type = $event->{event_type};
489 2 50       4 if(defined $self->{events}{$event_type}) {
490 0         0 push(@{$self->{events}{$event_type}},$event);
  0         0  
491             } else {
492 2         12 $self->{events}{$event_type} = [$event];
493             }
494             }
495              
496              
497             sub removeEvent {
498 2     2 1 5 my $self = shift;
499 2         2 my $delete_event = shift;
500 2         3 my $type = $delete_event->{event_type};
501 2 100 66     8 if(defined $type && defined $self->{events}{$type}) {
502 1         2 my $i = 0;
503 1         1 foreach my $event (@{$self->{events}{$type}}) {
  1         27  
504 1 50       4 if($event eq $delete_event) {
505 1         1 splice @{$self->{events}{$type}}, $i, 1;
  1         3  
506 1         4 return 1;
507             }
508 0         0 $i++;
509             }
510             }
511 1         2 return 0;
512             }
513              
514              
515             sub updateEvent {
516 1     1 1 1053 my $self = shift;
517 1         1 my $event = shift;
518 1         2 my $new_event_type = shift;
519 1         2 my %new_event = @_;
520              
521             # Update new event with old break id and event id
522 1         2 $new_event{event_type} = $new_event_type;
523 1         2 $new_event{event_id} = $event->{event_id};
524 1         2 $new_event{break_id} = $event->{break_id};
525              
526 1 50       3 if($self->removeEvent($event)) {
527             # Catch warnings on string concatenation that correspond to missing
528             # field in the hash for the event to update
529 1     0   7 local $SIG{'__WARN__'} = sub {croak("Invalid event hash for event type '$new_event_type'");};
  0         0  
530 1         4 my $base_XE = 'XE:Z:'.$new_event{event_id}.':'.$new_event{break_id};
531 1         2 my $new_XE = $base_XE . ':';
532 1 50 0     2 if($new_event_type eq 'Junction') {
    0          
    0          
    0          
    0          
    0          
    0          
533 1         4 my $loc = compressCracLoc($new_event{loc}{chr},$new_event{loc}{pos},$new_event{loc}{strand});
534             $new_XE .= $new_event_type.':'.
535             $new_event{type}.':'.
536             $new_event{pos}.':'.
537             $loc.':'.
538 1         6 $new_event{gap};
539             } elsif($new_event_type eq 'Ins' || $new_event_type eq 'Del') {
540 0         0 my $loc = compressCracLoc($new_event{loc}{chr},$new_event{loc}{pos},$new_event{loc}{strand});
541             $new_XE .= $new_event_type.':'.
542             $new_event{score}.':'.
543             $new_event{pos}.':'.
544             $loc.':'.
545 0         0 $new_event{nb};
546             } elsif($new_event_type eq 'SNP') {
547 0         0 my $loc = compressCracLoc($new_event{loc}{chr},$new_event{loc}{pos},$new_event{loc}{strand});
548             $new_XE .= $new_event_type.':'.
549             $new_event{score}.':'.
550             $new_event{pos}.':'.
551             $loc.':'.
552             $new_event{expected}.':'.
553 0         0 $new_event{actual};
554             } elsif($new_event_type eq 'Error') {
555 0         0 my $loc = compressCracLoc($new_event{loc}{chr},$new_event{loc}{pos},$new_event{loc}{strand});
556             $new_XE .= $new_event_type.':'.
557             $new_event{type}.':'.
558             $new_event{pos}.':'.
559             $new_event{score}.':'.
560             $new_event{other1}.':'.
561 0         0 $new_event{other2};
562             } elsif($new_event_type eq 'chimera') {
563 0         0 my $loc1 = compressCracLoc($new_event{loc1}{chr},$new_event{loc1}{pos},$new_event{loc1}{strand});
564 0         0 my $loc2 = compressCracLoc($new_event{loc2}{chr},$new_event{loc2}{pos},$new_event{loc2}{strand});
565             $new_XE .= $new_event_type.':'.
566 0         0 $new_event{pos}.':'.
567             $loc1.':'.
568             $loc2;
569             } elsif($new_event_type eq 'Undetermined') {
570             $new_XE .= $new_event_type.':'.
571 0         0 $new_event{message};
572             } elsif($new_event_type eq 'BioUndetermined') {
573             $new_XE .= $new_event_type.':'.
574             $new_event{pos}.':'.
575 0         0 $new_event{message};
576             } else {
577 0         0 croak("Unknown type of event : $new_event_type");
578             }
579 1         2 $self->addEvent(\%new_event);
580 1         3 my $new_line = $self->updatedLine;
581 1         32 $new_line =~ s/($base_XE:[^\t]*)/$new_XE/;
582 1         4 $self->updatedLine($new_line);
583             } else {
584 0         0 croak('Event not find');
585             }
586             }
587              
588              
589             sub loadSamDetailed {
590 2     2 1 3 my $self = shift;
591 2 100       6 if(!defined $self->{sam_detailed}) {
592             #my ($detailed) = $self->line =~ /XR:Z:([^\s]+)/g;
593 1         2 my $detailed = $self->{extended_fields}{XR};
594 1         4 my @detailed_fields = split(";",$detailed);
595 1         3 foreach (@detailed_fields) {
596 2         5 my ($k,$v) = split('=',$_);
597 2 100       7 if($k eq 'p_loc') {
    50          
598 1         3 $self->{sam_detailed}{p_loc} = $v;
599             } elsif($k eq 'p_support') {
600 1         2 $self->{sam_detailed}{p_support} = $v;
601             } else {
602 0         0 carp("Unknown sam detailed field : $k");
603             }
604             }
605 1         2 $self->{sam_detailed}{loaded} = 1;
606             }
607             }
608              
609              
610             sub loadPaired {
611 4     4 1 3 my $self = shift;
612             # If XP fields exist and we haven't load it already
613 4 100 66     23 if(defined $self->{extended_fields}{XP} && ref($self->{extended_fields}{XP}) ne 'HASH') {
614 1         4 my @paired_fields = split(";",$self->{extended_fields}{XP});
615 1         4 $self->{extended_fields}{XP} = {}; # Chamge type of XP exetended field from scalar to hash ref
616 1         2 foreach (@paired_fields) {
617 2         6 my($key,$value) = split(":",$_,2);
618 2         5 $self->{extended_fields}{XP}{$key} = $value;
619             }
620             }
621             }
622              
623              
624             sub expandCracLoc {
625 3     3 1 3 my $loc = shift;
626 3         14 my($chr,$strand,$pos) = $loc =~ /(\S+)\|(\S+)?,(\S+)?/;
627 3         8 return ($chr,$pos,$strand);
628             }
629              
630              
631             sub compressCracLoc {
632 1     1 1 2 my ($chr,$pos,$strand) = @_;
633 1 50 33     10 confess("Missing argument") unless defined $chr && defined $pos && defined $strand;
      33        
634 1         3 return $chr."|".$strand.",".$pos;
635             }
636              
637             1;
638              
639             __END__