File Coverage

blib/lib/GenOO/Data/File/SAM/Cigar.pm
Criterion Covered Total %
statement 127 140 90.7
branch 40 46 86.9
condition 44 75 58.6
subroutine 21 22 95.4
pod 0 19 0.0
total 232 302 76.8


line stmt bran cond sub pod time code
1             # POD documentation - main docs before the code
2              
3             =head1 NAME
4              
5             GenOO::Data::File::SAM::Cigar - Role that corresponds to the SAM file CIGAR string
6              
7             =head1 SYNOPSIS
8              
9             This role when consumed requires specific attributes and provides
10             methods to extract information from the CIGAR string as defined in
11             the SAM format specifications.
12              
13             =head1 DESCRIPTION
14              
15             The cigar string describes the alignment of a query on a reference sequence.
16             This role offers methods that can extract information from the CIGAR string
17             directly such as the positions on insertions, the total number of deletions, etc
18            
19             The CIGAR operations are given in the following table (set `*' if unavailable):
20             Op BAM Description
21             M 0 alignment match (can be a sequence match or mismatch)
22             I 1 insertion to the reference
23             D 2 deletion from the reference
24             N 3 skipped region from the reference
25             S 4 soft clipping (clipped sequences present in SEQ)
26             H 5 hard clipping (clipped sequences NOT present in SEQ)
27             P 6 padding (silent deletion from padded reference)
28             = 7 sequence match
29             X 8 sequence mismatch
30            
31             * H can only be present as the first and/or last operation.
32             * S may only have H operations between them and the ends of the CIGAR string.
33             * For mRNA-to-genome alignment, an N operation represents an intron. For other types of
34             alignments, the interpretation of N is not defined.
35             * Sum of lengths of the M/I/S/=/X operations shall equal the length of SEQ.
36              
37             =head1 EXAMPLES
38              
39             # Get the location information on the reference sequence
40             $obj_with_role->deletion_positions_on_query; # (10, 19, ...)
41             $obj_with_role->insertion_count; # 3
42             $obj_with_role->deletion_positions_on_reference; # (43534511, 43534522, ...)
43              
44             =cut
45              
46             # Let the code begin...
47              
48             package GenOO::Data::File::SAM::Cigar;
49             $GenOO::Data::File::SAM::Cigar::VERSION = '1.4.6';
50              
51             #######################################################################
52             ####################### Load External modules #####################
53             #######################################################################
54 2     2   1192 use Moose::Role;
  2         3  
  2         15  
55 2     2   8326 use namespace::autoclean;
  2         2  
  2         22  
56              
57              
58             #######################################################################
59             ######################## Required attributes ######################
60             #######################################################################
61             requires qw(cigar strand start);
62              
63              
64             #######################################################################
65             ######################## Interface Methods ########################
66             #######################################################################
67             sub M_count {
68 14     14 0 814 my ($self) = @_;
69            
70 14         37 return $self->_total_operator_count('M');
71             }
72              
73             sub I_count {
74 7     7 0 612 my ($self) = @_;
75            
76 7         20 return $self->_total_operator_count('I');
77             }
78              
79             sub D_count {
80 20     20 0 724 my ($self) = @_;
81            
82 20         38 return $self->_total_operator_count('D');
83             }
84              
85             sub N_count {
86 14     14 0 708 my ($self) = @_;
87            
88 14         22 return $self->_total_operator_count('N');
89             }
90              
91             sub S_count {
92 9     9 0 694 my ($self) = @_;
93            
94 9         21 return $self->_total_operator_count('S');
95             }
96              
97             sub H_count {
98 1     1 0 667 my ($self) = @_;
99            
100 1         4 return $self->_total_operator_count('H');
101             }
102              
103             sub P_count {
104 13     13 0 13 my ($self) = @_;
105            
106 13         20 return $self->_total_operator_count('P');
107             }
108              
109             sub EQ_count {
110 14     14 0 623 my ($self) = @_;
111            
112 14         26 return $self->_total_operator_count('=');
113             }
114              
115             sub X_count {
116 14     14 0 618 my ($self) = @_;
117            
118 14         27 return $self->_total_operator_count('X');
119             }
120              
121             sub insertion_count {
122 6     6 0 1000 my ($self) = @_;
123            
124 6         17 return $self->I_count;
125             }
126              
127             sub deletion_count {
128 6     6 0 745 my ($self) = @_;
129            
130 6         13 return $self->D_count;
131             }
132              
133             sub deletion_positions_on_query {
134 6     6 0 641 my ($self) = @_;
135             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
136             # - -
137             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
138            
139 6         6 my @deletion_positions;
140 6 100       15 if ($self->cigar =~ /D/) {
141 5         11 my $cigar = $self->cigar_relative_to_query;
142            
143 5         7 my $relative_position = 0;
144 5         21 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
145 44         43 my $count = $1;
146 44         35 my $identifier = $2;
147            
148 44 100 66     186 if ($identifier eq 'D') {
    100 66        
149 7         19 push @deletion_positions, $relative_position - 1;
150             }
151             elsif ($identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
152 35         75 $relative_position += $count;
153             }
154             }
155             }
156            
157 6         33 return @deletion_positions;
158             }
159              
160             sub mid_position {
161 8     8 0 635 my ($self) = @_;
162             # Read: AGTGAT____GGA---GTGACTCA-C -> CIGAR: 2M1I3M4N3M3D1M1I3M1I2M1D1M / 2=1I1=1X1=4N1=1X1=3D1=1I2=1X1I2=1D1=
163             # - - -
164             # Genome: AG-GCTNNNNGTAGAGG-GAG-CAGC -> MD:Z: 3C1^NNNN1T1^GAG3G2^G1
165            
166 8         271 my $mid_position = $self->start - 1;
167 8         20 my $mid_position_on_query = ($self->query_length - $self->S_count + 1) / 2;
168 8         20 my $cigar = $self->cigar;
169            
170 8         30 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
171 31         42 my ($count, $identifier) = ($1, $2);
172 31 100 100     235 if ($identifier eq 'D' or $identifier eq 'N' or $identifier eq 'P' or $identifier eq 'H') {
    100 66        
    50 66        
      100        
      100        
173 6         14 $mid_position += $count;
174             }
175             elsif ($identifier eq 'M' or $identifier eq '=' or $identifier eq 'X') {
176 21 100       35 if ($mid_position_on_query < $count) {
177 7         36 return $mid_position + $mid_position_on_query;
178             }
179 14         14 $mid_position += $count;
180 14         35 $mid_position_on_query -= $count;
181             }
182             elsif ($identifier eq 'I') {
183 4 50       9 if ($mid_position_on_query < $count) {
184 0         0 return $mid_position + 0.5;
185             }
186 4         10 $mid_position_on_query -= $count;
187             }
188             }
189            
190 1         5 return;
191             }
192              
193              
194             sub insertion_positions_on_query {
195 6     6 0 992 my ($self) = @_;
196             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
197             # - -
198             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
199            
200 6         8 my @insertion_positions;
201 6 100       19 if ($self->cigar =~ /I/) {
202 4         15 my $cigar = $self->cigar_relative_to_query;
203            
204 4         6 my $relative_position = 0;
205 4         28 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
206 41         55 my $count = $1;
207 41         56 my $identifier = $2;
208            
209 41 100 100     262 if ($identifier eq 'I') {
    100 66        
      66        
210 8         23 for (my $i=0; $i<$count; $i++) {
211 8         9 push @insertion_positions, $relative_position;
212 8         35 $relative_position++;
213             }
214             }
215             elsif ($identifier ne 'D' and $identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
216 25         95 $relative_position += $count;
217             }
218             }
219             }
220            
221 6         45 return @insertion_positions;
222             }
223              
224             sub mismatch_positions_on_query {
225 7     7 0 646 my ($self) = @_;
226             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
227             # - -
228             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
229            
230 7 100       19 if ($self->cigar =~ /X/) {
231 1         1 my @mismatch_positions;
232 1         4 my $cigar = $self->cigar_relative_to_query;
233            
234 1         2 my $relative_position = 0;
235 1         9 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
236 18         20 my $count = $1;
237 18         15 my $identifier = $2;
238            
239 18 100 100     99 if ($identifier eq 'X') {
    100 66        
      66        
240 3         8 for (my $i=0; $i<$count; $i++) {
241 3         4 push @mismatch_positions, $relative_position;
242 3         10 $relative_position++;
243             }
244             }
245             elsif ($identifier ne 'D' and $identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
246 12         39 $relative_position += $count;
247             }
248             }
249 1         10 return @mismatch_positions;
250             }
251             else {
252 6         21 return $self->mismatch_positions_on_query_calculated_from_mdz();
253             }
254             }
255              
256             sub deletion_positions_on_reference {
257 6     6 0 705 my ($self) = @_;
258             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
259             # - -
260             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
261            
262 6         9 my @deletion_positions;
263 6 100       16 if ($self->cigar =~ /D/) {
264 5         14 my $cigar = $self->cigar;
265            
266 5         7 my $relative_position = 0;
267 5         20 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
268 44         44 my $count = $1;
269 44         30 my $identifier = $2;
270            
271 44 100 66     232 if ($identifier eq 'D') {
    100 66        
      33        
272 7         15 for (my $i=0; $i<$count; $i++) {
273 13         364 push @deletion_positions, $self->start + $relative_position;
274 13         36 $relative_position++;
275             }
276             }
277             elsif ($identifier ne 'I' and $identifier ne 'P' and $identifier ne 'S' and $identifier ne 'H') {
278 29         69 $relative_position += $count;
279             }
280             }
281             }
282            
283 6         38 return @deletion_positions;
284             }
285              
286             sub mismatch_positions_on_reference {
287 20     20 0 673 my ($self) = @_;
288             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
289             # - -
290             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
291            
292 20 100       43 if ($self->cigar =~ /X/) {
293 2         2 my @mismatch_positions;
294 2         7 my $cigar = $self->cigar;
295            
296 2         4 my $relative_position = 0;
297 2         14 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
298 36         39 my $count = $1;
299 36         54 my $identifier = $2;
300            
301 36 100 66     231 if ($identifier eq 'X') {
    100 66        
      33        
302 6         20 for (my $i=0; $i<$count; $i++) {
303 6         338 push @mismatch_positions, $self->start + $relative_position;
304 6         30 $relative_position++;
305             }
306             }
307             elsif ($identifier ne 'I' and $identifier ne 'P' and $identifier ne 'S' and $identifier ne 'H') {
308 24         60 $relative_position += $count;
309             }
310             }
311 2         14 return @mismatch_positions;
312             }
313             else {
314 18         51 return $self->mismatch_positions_on_reference_calculated_from_mdz();
315             }
316             }
317              
318             sub cigar_relative_to_query {
319 15     15 0 824 my ($self) = @_;
320            
321 15         42 my $cigar = $self->cigar;
322            
323             # if negative strand -> reverse the cigar string
324 15 100 100     490 if (defined $self->strand and ($self->strand == -1)) {
325 4         9 my $reverse_cigar = '';
326 4         22 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
327 16         55 $reverse_cigar = $1.$2.$reverse_cigar;
328             }
329 4         16 return $reverse_cigar;
330             }
331             else {
332 11         28 return $cigar;
333             }
334             }
335              
336             sub aligned_areas_starts_and_stops {
337 0     0 0 0 my ($self) = @_;
338            
339             # returns the start and stop positions on the reference
340             # for all parts of the alignment that bind.
341            
342             # Read: AGTGAT____GGA---GTGACTCA-C -> CIGAR: 2M1I3M4N3M3D1M1I3M1I2M1D1M / 2=1I1=1X1=4N1=1X1=3D1=1I2=1X1I2=1D1=
343             # - - -
344             # Genome: AG-GCTNNNNGTAGAGG-GAG-CAGC -> MD:Z: 3C1^NNNN1T1^GAG3G2^G1
345            
346 0         0 my @exon_starts;
347             my @exon_stops;
348            
349 0         0 my $pos = $self->start;
350 0         0 my $cigar = $self->cigar;
351            
352 0         0 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
353 0         0 my ($count, $identifier) = ($1, $2);
354 0 0 0     0 if ($identifier eq 'D' or $identifier eq 'N' or $identifier eq 'P' or $identifier eq 'H') {
    0 0        
      0        
      0        
      0        
355 0         0 $pos += $count;
356             }
357             elsif ($identifier eq 'M' or $identifier eq '=' or $identifier eq 'X') {
358 0         0 push @exon_starts, $pos;
359 0         0 push @exon_stops, $pos+$count-1;
360 0         0 $pos += $count;
361             }
362             }
363 0         0 return (\@exon_starts, \@exon_stops);
364             }
365              
366             #######################################################################
367             ######################### Private methods ##########################
368             #######################################################################
369             sub _total_operator_count {
370 106     106   118 my ($self, $operator) = @_;
371            
372 106         218 my $cigar = $self->cigar;
373            
374 106         117 my $count = 0;
375 106         984 while ($cigar =~ /(\d+)$operator/g) {
376 115         403 $count += $1;
377             }
378 106         640 return $count;
379             }
380              
381             1;