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