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.5.1';
50              
51             #######################################################################
52             ####################### Load External modules #####################
53             #######################################################################
54 2     2   1179 use Moose::Role;
  2         4  
  2         11  
55 2     2   6642 use namespace::autoclean;
  2         3  
  2         13  
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 858 my ($self) = @_;
69            
70 14         29 return $self->_total_operator_count('M');
71             }
72              
73             sub I_count {
74 7     7 0 853 my ($self) = @_;
75            
76 7         19 return $self->_total_operator_count('I');
77             }
78              
79             sub D_count {
80 20     20 0 896 my ($self) = @_;
81            
82 20         41 return $self->_total_operator_count('D');
83             }
84              
85             sub N_count {
86 14     14 0 944 my ($self) = @_;
87            
88 14         27 return $self->_total_operator_count('N');
89             }
90              
91             sub S_count {
92 9     9 0 1095 my ($self) = @_;
93            
94 9         33 return $self->_total_operator_count('S');
95             }
96              
97             sub H_count {
98 1     1 0 883 my ($self) = @_;
99            
100 1         7 return $self->_total_operator_count('H');
101             }
102              
103             sub P_count {
104 13     13 0 16 my ($self) = @_;
105            
106 13         23 return $self->_total_operator_count('P');
107             }
108              
109             sub EQ_count {
110 14     14 0 925 my ($self) = @_;
111            
112 14         25 return $self->_total_operator_count('=');
113             }
114              
115             sub X_count {
116 14     14 0 1088 my ($self) = @_;
117            
118 14         22 return $self->_total_operator_count('X');
119             }
120              
121             sub insertion_count {
122 6     6 0 1273 my ($self) = @_;
123            
124 6         20 return $self->I_count;
125             }
126              
127             sub deletion_count {
128 6     6 0 1154 my ($self) = @_;
129            
130 6         15 return $self->D_count;
131             }
132              
133             sub deletion_positions_on_query {
134 6     6 0 1270 my ($self) = @_;
135             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
136             # - -
137             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
138            
139 6         9 my @deletion_positions;
140 6 100       20 if ($self->cigar =~ /D/) {
141 5         16 my $cigar = $self->cigar_relative_to_query;
142            
143 5         9 my $relative_position = 0;
144 5         30 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
145 44         65 my $count = $1;
146 44         49 my $identifier = $2;
147            
148 44 100 66     239 if ($identifier eq 'D') {
    100 66        
149 7         32 push @deletion_positions, $relative_position - 1;
150             }
151             elsif ($identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
152 35         138 $relative_position += $count;
153             }
154             }
155             }
156            
157 6         57 return @deletion_positions;
158             }
159              
160             sub mid_position {
161 8     8 0 782 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         339 my $mid_position = $self->start - 1;
167 8         27 my $mid_position_on_query = ($self->query_length - $self->S_count + 1) / 2;
168 8         24 my $cigar = $self->cigar;
169            
170 8         42 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
171 31         64 my ($count, $identifier) = ($1, $2);
172 31 100 100     282 if ($identifier eq 'D' or $identifier eq 'N' or $identifier eq 'P' or $identifier eq 'H') {
    100 66        
    50 66        
      100        
      100        
173 6         26 $mid_position += $count;
174             }
175             elsif ($identifier eq 'M' or $identifier eq '=' or $identifier eq 'X') {
176 21 100       50 if ($mid_position_on_query < $count) {
177 7         51 return $mid_position + $mid_position_on_query;
178             }
179 14         16 $mid_position += $count;
180 14         58 $mid_position_on_query -= $count;
181             }
182             elsif ($identifier eq 'I') {
183 4 50       12 if ($mid_position_on_query < $count) {
184 0         0 return $mid_position + 0.5;
185             }
186 4         19 $mid_position_on_query -= $count;
187             }
188             }
189            
190 1         8 return;
191             }
192              
193              
194             sub insertion_positions_on_query {
195 6     6 0 1253 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       21 if ($self->cigar =~ /I/) {
202 4         15 my $cigar = $self->cigar_relative_to_query;
203            
204 4         10 my $relative_position = 0;
205 4         27 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
206 41         63 my $count = $1;
207 41         45 my $identifier = $2;
208            
209 41 100 100     268 if ($identifier eq 'I') {
    100 66        
      66        
210 8         23 for (my $i=0; $i<$count; $i++) {
211 8         11 push @insertion_positions, $relative_position;
212 8         36 $relative_position++;
213             }
214             }
215             elsif ($identifier ne 'D' and $identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
216 25         98 $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 795 my ($self) = @_;
226             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
227             # - -
228             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
229            
230 7 100       21 if ($self->cigar =~ /X/) {
231 1         2 my @mismatch_positions;
232 1         6 my $cigar = $self->cigar_relative_to_query;
233            
234 1         3 my $relative_position = 0;
235 1         8 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
236 18         29 my $count = $1;
237 18         21 my $identifier = $2;
238            
239 18 100 100     139 if ($identifier eq 'X') {
    100 66        
      66        
240 3         11 for (my $i=0; $i<$count; $i++) {
241 3         5 push @mismatch_positions, $relative_position;
242 3         16 $relative_position++;
243             }
244             }
245             elsif ($identifier ne 'D' and $identifier ne 'N' and $identifier ne 'P' and $identifier ne 'H') {
246 12         50 $relative_position += $count;
247             }
248             }
249 1         12 return @mismatch_positions;
250             }
251             else {
252 6         22 return $self->mismatch_positions_on_query_calculated_from_mdz();
253             }
254             }
255              
256             sub deletion_positions_on_reference {
257 6     6 0 1241 my ($self) = @_;
258             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
259             # - -
260             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
261            
262 6         12 my @deletion_positions;
263 6 100       21 if ($self->cigar =~ /D/) {
264 5         14 my $cigar = $self->cigar;
265            
266 5         8 my $relative_position = 0;
267 5         34 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
268 44         73 my $count = $1;
269 44         49 my $identifier = $2;
270            
271 44 100 66     465 if ($identifier eq 'D') {
    100 66        
      33        
272 7         20 for (my $i=0; $i<$count; $i++) {
273 13         558 push @deletion_positions, $self->start + $relative_position;
274 13         63 $relative_position++;
275             }
276             }
277             elsif ($identifier ne 'I' and $identifier ne 'P' and $identifier ne 'S' and $identifier ne 'H') {
278 29         123 $relative_position += $count;
279             }
280             }
281             }
282            
283 6         53 return @deletion_positions;
284             }
285              
286             sub mismatch_positions_on_reference {
287 20     20 0 1254 my ($self) = @_;
288             #Tag: AGTGATGGGA------GGATGTCTCGTCTGTGAGTTACAGCA -> CIGAR: 2M1I7M6D26M
289             # - -
290             #Genome: AG-GCTGGTAGCTCAGGGATGTCTCGTCTGTGAGTTACAGCA -> MD:Z: 3C3T1^GCTCAG26
291            
292 20 100       48 if ($self->cigar =~ /X/) {
293 2         4 my @mismatch_positions;
294 2         7 my $cigar = $self->cigar;
295            
296 2         5 my $relative_position = 0;
297 2         16 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
298 36         47 my $count = $1;
299 36         31 my $identifier = $2;
300            
301 36 100 66     206 if ($identifier eq 'X') {
    100 66        
      33        
302 6         15 for (my $i=0; $i<$count; $i++) {
303 6         190 push @mismatch_positions, $self->start + $relative_position;
304 6         27 $relative_position++;
305             }
306             }
307             elsif ($identifier ne 'I' and $identifier ne 'P' and $identifier ne 'S' and $identifier ne 'H') {
308 24         76 $relative_position += $count;
309             }
310             }
311 2         15 return @mismatch_positions;
312             }
313             else {
314 18         55 return $self->mismatch_positions_on_reference_calculated_from_mdz();
315             }
316             }
317              
318             sub cigar_relative_to_query {
319 15     15 0 796 my ($self) = @_;
320            
321 15         41 my $cigar = $self->cigar;
322            
323             # if negative strand -> reverse the cigar string
324 15 100 100     524 if (defined $self->strand and ($self->strand == -1)) {
325 4         8 my $reverse_cigar = '';
326 4         23 while ($cigar =~ /(\d+)([MIDNSHP=X])/g) {
327 16         64 $reverse_cigar = $1.$2.$reverse_cigar;
328             }
329 4         17 return $reverse_cigar;
330             }
331             else {
332 11         36 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   122 my ($self, $operator) = @_;
371            
372 106         221 my $cigar = $self->cigar;
373            
374 106         97 my $count = 0;
375 106         996 while ($cigar =~ /(\d+)$operator/g) {
376 115         446 $count += $1;
377             }
378 106         637 return $count;
379             }
380              
381             1;