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.22';
7              
8 4     4   20929 use strict;
  4         9  
  4         100  
9 4     4   20 use warnings;
  4         5  
  4         91  
10 4     4   23 use Carp;
  4         5  
  4         221  
11              
12 4     4   1274 use CracTools::Utils;
  4         17  
  4         16347  
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 13 my ($line,$event_type) = @_;
33 2 50 33     14 croak("Missing argument(s)") unless defined $line && defined $event_type;
34 2         49 return $line =~ /XE:Z:\d+:\d+:$event_type/i;
35             }
36              
37              
38             sub new {
39 8     8 1 17 my $class = shift;
40 8         11 my ($line) = @_;
41 8         18 chomp $line;
42 8         50 my ($qname,$flag,$rname,$pos,$mapq,$cigar,$rnext,$pnext,$tlen,$seq,$qual,@others) = split("\t",$line);
43 8         14 my %extended_fields;
44 8         16 foreach(@others) {
45 16         28 my $key = substr($_,0,2);
46 16         31 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       64 $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         20 $rname =~ s/^chr//;
54              
55 8         86 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         33 return $self;
72             }
73              
74              
75             sub isFlagged {
76 3     3 1 10 my $self = shift;
77 3         4 my $flag = shift;
78 3         10 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 4 my $self = shift;
127 2         7 return $self->{line};
128             }
129              
130              
131             sub updatedLine {
132 3     3 1 6 my $self = shift;
133 3         6 my $updated_line = shift;
134 3 100       10 if(defined $updated_line) {
    100          
135 1         4 $self->{updated_line} = $updated_line;
136 1         9 return 1;
137             } elsif (!defined $self->{updated_line}) {
138 1         3 return $self->line;
139             } else {
140 1         14 return $self->{updated_line};
141             }
142             }
143              
144              
145             sub qname {
146 3     3 1 1086 my $self = shift;
147 3         5 my $new_qname = shift;
148 3 100       10 if(defined $new_qname) {
149 1         2 $self->{qname} = $new_qname;
150             }
151 3         12 return $self->{qname};
152             }
153              
154              
155             sub flag {
156 6     6 1 9 my $self = shift;
157 6         7 my $new_flag = shift;
158 6 100       19 if(defined $new_flag) {
159 1         3 $self->{flag} = $new_flag;
160             }
161 6         30 return $self->{flag};
162             }
163              
164              
165             sub rname {
166 7     7 1 29 my $self = shift;
167 7         12 my $new_rname = shift;
168 7 100       16 if(defined $new_rname) {
169 3         7 $self->{rname} = $new_rname;
170             }
171 7         25 return $self->{rname};
172             }
173              
174              
175             sub chr {
176 3     3 1 4 my $self = shift;
177 3         8 $self->rname(@_);
178             }
179              
180              
181             sub pos {
182 3     3 1 5 my $self = shift;
183 3         4 my $new_pos = shift;
184 3 100       9 if(defined $new_pos) {
185 1         2 $self->{pos} = $new_pos;
186             }
187 3         16 return $self->{pos};
188             }
189              
190              
191             sub mapq {
192 3     3 1 4 my $self = shift;
193 3         5 my $new_mapq = shift;
194 3 100       9 if(defined $new_mapq) {
195 1         3 $self->{mapq} = $new_mapq;
196             }
197 3         12 return $self->{mapq};
198             }
199              
200              
201             sub cigar {
202 4     4 1 6 my $self = shift;
203 4         7 my $new_cigar = shift;
204 4 100       10 if(defined $new_cigar) {
205 1         3 $self->{cigar} = $new_cigar;
206             }
207 4         18 return $self->{cigar};
208             }
209              
210              
211             sub rnext {
212 3     3 1 4 my $self = shift;
213 3         5 my $new_rnext = shift;
214 3 100       8 if(defined $new_rnext) {
215 1         3 $self->{rnext} = $new_rnext;
216             }
217 3         11 return $self->{rnext};
218             }
219              
220              
221             sub pnext {
222 3     3 1 6 my $self = shift;
223 3         4 my $new_pnext = shift;
224 3 100       8 if(defined $new_pnext) {
225 1         3 $self->{pnext} = $new_pnext;
226             }
227 3         10 return $self->{pnext};
228             }
229              
230              
231             sub tlen {
232 3     3 1 11 my $self = shift;
233 3         4 my $new_tlen = shift;
234 3 100       10 if(defined $new_tlen) {
235 1         2 $self->{tlen} = $new_tlen;
236             }
237 3         10 return $self->{tlen};
238             }
239              
240              
241             sub seq {
242 4     4 1 468 my $self = shift;
243 4         7 my $new_seq = shift;
244 4 100       11 if(defined $new_seq) {
245 1         2 $self->{seq} = $new_seq;
246             }
247 4         27 return $self->{seq};
248             }
249              
250              
251             sub qual {
252 3     3 1 5 my $self = shift;
253 3         5 my $new_qual = shift;
254 3 100       9 if(defined $new_qual) {
255 1         3 $self->{qual} = $new_qual;
256             }
257 3         11 return $self->{qual};
258             }
259              
260              
261             sub getOptionalField {
262 1     1 1 2 my $self = shift;
263 1         2 my $field = shift;
264 1 50       3 croak("Missing \$field argument to call getOptionalField") unless defined $field;
265 1         6 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       5 if (defined $self->{extended_fields}{SA}){
274 1         1 my @array_hash;
275 1         4 my (@SA_alignments) = split(/;/,$self->{extended_fields}{SA});
276 1         6 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       7 if ($strand eq '+'){
280 1         2 $strand = 1;
281             }else{
282 1         2 $strand = -1;
283             }
284 2         10 my $hash = { chr => $chr,
285             pos => $pos,
286             strand => $strand,
287             cigar => $cigar,
288             mapq => $mapq,
289             edist => $edist};
290 2         8 push(@array_hash,$hash);
291             }
292 1         5 return \@array_hash;
293             }
294 0         0 return undef;
295             }
296              
297              
298             sub getCigarOperatorsCount {
299 1     1 1 2453 my $self = shift;
300 1         4 my @ops = $self->cigar =~ /(\d+\D)/g;
301 1         2 my %ops_occ;
302 1         3 foreach (@ops) {
303 3         12 my ($nb,$op) = $_ =~ /(\d+)(\D)/;
304 3 100       11 $ops_occ{$op} = 0 unless defined $ops_occ{$op};
305 3         8 $ops_occ{$op} += $nb;
306             }
307 1         6 return \%ops_occ;
308             }
309              
310              
311             sub pSupport {
312 1     1 1 3 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 3 my $self = shift;
320 1         2 $self->loadSamDetailed;
321 1         4 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       4 if(defined $self->{extended_fields}{XP}{chimera}) {
329 1         4 my ($crac_loc1,$crac_loc2) = split(":",$self->{extended_fields}{XP}{chimera});
330 1         4 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 2093 my $self = shift;
339 3         4 my $class = shift;
340 3         7 $self->loadPaired();
341              
342 3 100 66     21 if(defined $self->{extended_fields}{XP}{loc} && ref($self->{extended_fields}{XP}{loc}) ne 'HASH') {
343 1         5 my ($uniq,$dupli,$multi) = split(":",$self->{extended_fields}{XP}{loc});
344 1         6 $self->{extended_fields}{XP}{loc} = {unique => $uniq, duplicated => $dupli, multiple => $multi};
345              
346             }
347 3         13 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       8 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         5 $self->{genericInfo}{$key} = $value;
358             } else {
359 1         6 return $self->{genericInfo}{$key};
360             }
361             }
362              
363              
364             sub isClassified {
365 2     2 1 4 my $self = shift;
366 2         3 my $class = shift;
367            
368 2 50       7 croak "Missing class argument" unless defined $class;
369              
370 2 100       17 if($class eq "unique") {
    50          
    50          
    0          
    0          
371 1         5 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         5 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 8 my $self = shift;
388 3         6 my $event_type = lc shift;
389 3         7 $self->loadEvents();#$event_type);
390 3 50       12 if(defined $self->{events}{$event_type}) {
391 3         15 return $self->{events}{$event_type};
392             } else {
393 0         0 return [];
394             }
395             }
396              
397              
398             sub loadEvents {
399 3     3 1 4 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     16 if(!defined $self->{events} && defined $self->{extended_fields}{XE}) {
406             # Init events
407 1         4 my @events = split(";",$self->{extended_fields}{XE});
408 1         3 foreach my $event (@events) {
409 1         8 my ($event_id,$event_break_id,$event_type,$event_infos) = $event =~ /([^:]+):([^:]+):([^:]+):(.*)/g;
410 1         2 $event_type = lc $event_type;
411 1 50 33     7 next if(defined $event_type_to_load && $event_type ne $event_type_to_load);
412 1 50       3 if(defined $event_id) {
413 1         2 my %event_hash;
414 1 50 0     4 if($event_type eq 'junction') {
    0          
    0          
    0          
    0          
    0          
    0          
415 1         4 my ($type,$pos_read,$loc,$gap) = split(':',$event_infos);
416 1         10 my ($chr,$pos,$strand) = expandCracLoc($loc);
417 1         8 %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       4 if (keys %event_hash > 1) {
467 1         3 $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         4 $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 4 my $self = shift;
487 2         3 my $event = shift;
488 2         4 my $event_type = $event->{event_type};
489 2 50       7 if(defined $self->{events}{$event_type}) {
490 0         0 push(@{$self->{events}{$event_type}},$event);
  0         0  
491             } else {
492 2         14 $self->{events}{$event_type} = [$event];
493             }
494             }
495              
496              
497             sub removeEvent {
498 2     2 1 7 my $self = shift;
499 2         4 my $delete_event = shift;
500 2         3 my $type = $delete_event->{event_type};
501 2 100 66     12 if(defined $type && defined $self->{events}{$type}) {
502 1         2 my $i = 0;
503 1         2 foreach my $event (@{$self->{events}{$type}}) {
  1         3  
504 1 50       4 if($event eq $delete_event) {
505 1         31 splice @{$self->{events}{$type}}, $i, 1;
  1         5  
506 1         4 return 1;
507             }
508 0         0 $i++;
509             }
510             }
511 1         3 return 0;
512             }
513              
514              
515             sub updateEvent {
516 1     1 1 1726 my $self = shift;
517 1         3 my $event = shift;
518 1         2 my $new_event_type = shift;
519 1         4 my %new_event = @_;
520              
521             # Update new event with old break id and event id
522 1         3 $new_event{event_type} = $new_event_type;
523 1         3 $new_event{event_id} = $event->{event_id};
524 1         3 $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   6 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         3 my $new_XE = $base_XE . ':';
532 1 50 0     13 if($new_event_type eq 'Junction') {
    0          
    0          
    0          
    0          
    0          
    0          
533 1         5 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         5 $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         3 $self->addEvent(\%new_event);
580 1         9 my $new_line = $self->updatedLine;
581 1         33 $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 4 my $self = shift;
591 2 100       8 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         2 foreach (@detailed_fields) {
596 2         7 my ($k,$v) = split('=',$_);
597 2 100       10 if($k eq 'p_loc') {
    50          
598 1         3 $self->{sam_detailed}{p_loc} = $v;
599             } elsif($k eq 'p_support') {
600 1         4 $self->{sam_detailed}{p_support} = $v;
601             } else {
602 0         0 carp("Unknown sam detailed field : $k");
603             }
604             }
605 1         3 $self->{sam_detailed}{loaded} = 1;
606             }
607             }
608              
609              
610             sub loadPaired {
611 4     4 1 7 my $self = shift;
612             # If XP fields exist and we haven't load it already
613 4 100 66     31 if(defined $self->{extended_fields}{XP} && ref($self->{extended_fields}{XP}) ne 'HASH') {
614 1         5 my @paired_fields = split(";",$self->{extended_fields}{XP});
615 1         2 $self->{extended_fields}{XP} = {}; # Chamge type of XP exetended field from scalar to hash ref
616 1         3 foreach (@paired_fields) {
617 2         6 my($key,$value) = split(":",$_,2);
618 2         8 $self->{extended_fields}{XP}{$key} = $value;
619             }
620             }
621             }
622              
623              
624             sub expandCracLoc {
625 3     3 1 4 my $loc = shift;
626 3         20 my($chr,$strand,$pos) = $loc =~ /(\S+)\|(\S+)?,(\S+)?/;
627 3         12 return ($chr,$pos,$strand);
628             }
629              
630              
631             sub compressCracLoc {
632 1     1 1 2 my ($chr,$pos,$strand) = @_;
633 1 50 33     16 confess("Missing argument") unless defined $chr && defined $pos && defined $strand;
      33        
634 1         4 return $chr."|".$strand.",".$pos;
635             }
636              
637             1;
638              
639             __END__