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