File Coverage

Bio/Map/CytoPosition.pm
Criterion Covered Total %
statement 182 207 87.9
branch 138 162 85.1
condition 69 95 72.6
subroutine 10 10 100.0
pod 5 5 100.0
total 404 479 84.3


line stmt bran cond sub pod time code
1             #
2             # BioPerl module for Bio::Map::CytoPosition
3             #
4             # Please direct questions and support issues to
5             #
6             # Cared for by Sendu Bala
7             #
8             # Copyright Heikki Lehvaslaiho
9             #
10             # You may distribute this module under the same terms as perl itself
11              
12             # POD documentation - main docs before the code
13              
14             =head1 NAME
15              
16             Bio::Map::CytoPosition - Marker class with cytogenetic band storing attributes
17              
18             =head1 SYNOPSIS
19              
20             $m1 = Bio::Map::CytoPosition->new ( '-id' => 'A1',
21             '-value' => '2q1-3'
22             );
23             $m2 = Bio::Map::CytoPosition->new ( '-id' => 'A2',
24             '-value' => '2q2'
25             );
26              
27             if ($m1->cytorange->overlaps($m2->cytorange)) {
28             print "Makers overlap\n";
29             }
30              
31              
32             =head1 DESCRIPTION
33              
34             CytoPosition is marker (Bio::Map::MarkerI compliant) with a location in a
35             cytogenetic map. See L for more information.
36              
37             Cytogenetic locations are names of bands visible in stained mitotic
38             eucaryotic chromosomes. The naming follows strict rules which are
39             consistent at least in higher vertebates, e.g. mammals. The chromosome
40             name preceds the band names.
41              
42             The shorter arm of the chromosome is called 'p' ('petit') and usually
43             drawn pointing up. The lower arm is called 'q' ('queue'). The bands
44             are named from the region separating these, a centromere (cen), towards
45             the tips or telomeric regions (ter) counting from 1 upwards. Depending
46             of the resolution used the bands are identified with one or more
47             digit. The first digit determines the major band and subsequent digits
48             sub bands: p1 band can be divided into subbands p11, p12 and 13 and
49             p11 can furter be divided into subbands p11.1 and p11.2. The dot after
50             second digit makes it easier to read the values. A region between ands
51             is given from the centromere outwards towards the telomere (e.g. 2p2-5
52             or 3p21-35) or from a band in the p arm to a band in the q arm.
53              
54             =head1 FEEDBACK
55              
56             =head2 Mailing Lists
57              
58             User feedback is an integral part of the evolution of this and other
59             Bioperl modules. Send your comments and suggestions preferably to the
60             Bioperl mailing lists Your participation is much appreciated.
61              
62             bioperl-l@bioperl.org - General discussion
63             http://bioperl.org/wiki/Mailing_lists - About the mailing lists
64              
65             =head2 Support
66              
67             Please direct usage questions or support issues to the mailing list:
68              
69             I
70              
71             rather than to the module maintainer directly. Many experienced and
72             reponsive experts will be able look at the problem and quickly
73             address it. Please include a thorough description of the problem
74             with code and data examples if at all possible.
75              
76             =head2 Reporting Bugs
77              
78             report bugs to the Bioperl bug tracking system to help us keep track
79             the bugs and their resolution. Bug reports can be submitted via the
80             web:
81              
82             https://github.com/bioperl/bioperl-live/issues
83              
84             =head1 AUTHOR - Heikki Lehvaslaiho
85              
86             Email: heikki-at-bioperl-dot-org
87              
88             =head1 CONTRIBUTORS
89              
90             Sendu Bala bix@sendu.me.uk
91              
92             =head1 APPENDIX
93              
94             The rest of the documentation details each of the object
95             methods. Internal methods are usually preceded with a _
96              
97             =cut
98              
99             # Let the code begin...
100              
101             package Bio::Map::CytoPosition;
102              
103 4     4   733 use strict;
  4         8  
  4         107  
104 4     4   19 use integer;
  4         8  
  4         29  
105 4     4   517 use Bio::Range;
  4         11  
  4         105  
106              
107 4     4   23 use base qw(Bio::Map::Position);
  4         14  
  4         1172  
108              
109             =head2 cytorange
110              
111             Title : cytorange
112             Usage : my $range = $obj->cytorange();
113             Function:
114             Converts cytogenetic location set by value method into
115             an integer range. The chromosome number determines the
116             "millions" in the values. Human X and Y chromosome
117             symbols are represented by values 100 and 101.
118              
119             The localization within chromosomes are converted into
120             values between the range of 0 and 200,000:
121              
122             pter cen qter
123             |------------------------|-------------------------|
124             0 100,000 200,000
125              
126             The values between -100,000 through 0 for centromere to
127             100,000 would have reflected the band numbering better but
128             use of positive integers was chosen since the
129             transformation is very easy. These values are not metric.
130              
131             Each band defines a range in a chromosome. A band string
132             is converted into a range by padding it with lower and and
133             higher end digits (for q arm: '0' and '9') to the length
134             of five. The arm and chromosome values are added to these:
135             e.g. 21000 & 21999 (band 21) + 100,000 (q arm) + 2,000,000
136             (chromosome 2) => 2q21 : 2,121,000 .. 2,121,999. Note that
137             this notation breaks down if there is a band or a subband
138             using digit 9 in its name! This is not the case in human
139             karyotype.
140              
141             The full algorithm used for bands:
142              
143             if arm is 'q' then
144             pad char for start is '0', for end '9'
145             range is chromosome + 100,000 + padded range start or end
146             elsif arm is 'p' then
147             pad char for start is '9', for end '0'
148             range is chromosome + 100,000 - padded range start or end
149              
150             Returns : Bio::Range object or undef
151             Args : none
152              
153             =cut
154              
155             sub cytorange {
156 139     139 1 191 my ($self) = @_;
157 139         157 my ($chr, $r, $band, $band2, $arm, $arm2, $lc, $uc, $lcchar, $ucchar);
158              
159 139 100       225 return $r if not defined $self->{_value}; # returns undef
160             $self->{_value} =~
161             # -----1----- --------2--------- -----3----- -------4------- ---6---
162 138         671 m/([XY]|[0-9]+)(cen|qcen|pcen|[pq])?(ter|[.0-9]+)?-?([pq]?(cen|ter)?)?([.0-9]+)?/;
163 138 100       303 $self->warn("Not a valid value: ". $self->{_value}), return $r
164             if not defined $1 ; # returns undef
165              
166 137         231 $chr = uc $1;
167 137         252 $self->chr($chr);
168              
169 137 100       214 $chr = 100 if $chr eq 'X';
170 137 50       179 $chr = 101 if $chr eq 'Y';
171 137         201 $chr *= 1000000;
172              
173 137         263 $r = Bio::Range->new();
174              
175 137         175 $band = '';
176 137 100       232 if (defined $3 ) {
177 95 50       160 $2 || $self->throw("$& does not make sense: 'arm' or 'cen' missing");
178 95         104 $band = $3;
179 95         147 $band =~ tr/\.//d;
180             }
181 137 100 66     402 if (defined $6 ) {
    100          
    100          
    100          
182 57         78 $arm2 = $4;
183 57 100       96 $arm2 = $2 if $4 eq ''; # it is not necessary to repeat the arm [p|q]
184 57         56 $band2 = $6;
185 57         64 $band2 =~ tr/\.//d;
186            
187             #find the correct order
188             #print STDERR "-|$&|----2|$2|-----3|$band|---4|$4|--------arm2|$arm2|-------------\n";
189 57 100 66     376 if ($band ne '' and (defined $arm2 and $2 ne $arm2 and $arm2 eq 'q') ) {
    100 100        
    100 100        
    100 100        
    50 100        
      66        
190 14         14 $lc = 'start'; $lcchar = '9';
  14         16  
191 14         13 $uc = 'end'; $ucchar = '9';
  14         14  
192             }
193             elsif ($band ne 'ter' and $2 ne $arm2 and $arm2 eq 'p') {
194 17         17 $lc = 'end'; $lcchar = '9';
  17         17  
195 17         15 $uc = 'start'; $ucchar = '9';
  17         17  
196             }
197             elsif ($band eq 'ter' and $arm2 eq 'p') {
198 2         3 $uc = 'start'; $ucchar = '9';
  2         3  
199             } # $2 eq $arm2
200             elsif ($arm2 eq 'q') {
201 11 50       16 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
202 11         12 $lc = 'start'; $lcchar = '0';
  11         10  
203 11         10 $uc = 'end'; $ucchar = '9';
  11         13  
204             } else {
205 0         0 $lc = 'end'; $lcchar = '9';
  0         0  
206 0         0 $uc = 'start'; $ucchar = '0';
  0         0  
207             }
208             }
209             elsif ($arm2 eq 'p') {
210 13 50       21 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
211 13         14 $lc = 'end'; $lcchar = '0';
  13         13  
212 13         13 $uc = 'start'; $ucchar = '9';
  13         13  
213             } else {
214 0         0 $lc = 'start'; $lcchar = '9';
  0         0  
215 0         0 $uc = 'end'; $ucchar = '0';
  0         0  
216             }
217             }
218             else {
219 0         0 $self->throw("How did you end up here? $&");
220             }
221              
222             #print STDERR "-------$arm2--------$band2---------$ucchar--------------\n";
223 57 100 66     209 if ( (defined $arm2 and $arm2 eq 'p') or (defined $arm2 and $arm2 eq 'p') ) {
      33        
      66        
224 32         54 $r->$uc(-(_pad($band2, 5, $ucchar)) + 100000 + $chr );
225 32 100 100     182 if (defined $3 and $3 eq 'ter') {
    100 66        
    100 66        
226 2         9 $r->end(200000 + $chr);
227             }
228             elsif ($2 eq 'cen' or $2 eq 'qcen' or $2 eq 'pcen'){
229 8         20 $r->$lc(100000 + $chr);
230             }
231             elsif ($2 eq 'q') {
232 9         20 $r->$lc(_pad($band, 5, $lcchar) + 100000 + $chr );
233             } else {
234 13         18 $r->$lc(-(_pad($band, 5, $lcchar)) + 100000 + $chr );
235             }
236             } else { #if:$arm2=q e.g. 9p22-q32
237             #print STDERR "-------$arm2--------$band2---------$ucchar--------------\n";
238 25         40 $r->$uc(_pad($band2, 5, $ucchar) + 100000 + $chr);
239 25 100 66     94 if ($2 eq 'cen' or $2 eq 'pcen') {
    100          
240 3         9 $r->$lc(100000 + $chr);
241             }
242             elsif ($2 eq 'p') {
243 14 50       21 if ($3 eq 'ter') {
244 0         0 $r->$lc(200000 + $chr);
245             } else {
246 14         18 $r->$lc(-(_pad($band, 5, $lcchar)) + 100000 + $chr);
247             }
248             } else { #$2.==q
249 8         10 $r->$lc(_pad($band, 5, $lcchar) + 100000 + $chr);
250             }
251             }
252             }
253             #
254             # e.g. 10p22.1-cen
255             #
256             elsif (defined $4 and $4 ne '') {
257             #print STDERR "$4-----$&----\n";
258 33 100 66     172 if ($4 eq 'cen' || $4 eq 'qcen' || $4 eq 'pcen') { # e.g. 10p22.1-cen;
    100 66        
    100 100        
259             # '10pcen-qter' does not really make sense but lets have it in anyway
260 4         10 $r->end(100000 + $chr);
261 4 100       10 if ($2 eq 'p') {
    50          
262 3 50       7 if ($3 eq 'ter') {
263 0         0 $r->start($chr);
264             } else {
265 3         7 $r->start(_pad($band, 5, '9') + $chr);
266             }
267             }
268             elsif ($2 eq 'cen') {
269 0         0 $self->throw("'cen-cen' does not make sense: $&");
270             } else {
271 1         5 $self->throw("Only order p-cen is valid: $&");
272             }
273             }
274             elsif ($4 eq 'qter' || $4 eq 'ter') { # e.g. 10p22.1-qter, 1p21-qter, 10pcen-qter, 7q34-qter
275 15         34 $r->end(200000 + $chr);
276 15 100 66     50 if ($2 eq 'p'){
    100 66        
    50          
277 6         18 $r->start(-(_pad($band, 5, '9')) + 100000 + $chr); #??? OK?
278             }
279             elsif ($2 eq 'q') {
280 3         5 $r->start(_pad($band, 5, '0') + 100000 + $chr);
281             }
282             elsif ($2 eq 'cen' || $2 eq 'qcen' || $2 eq 'pcen' ) {
283 6         11 $r->start(100000 + $chr);
284             }
285             }
286             elsif ($4 eq 'pter' ) {
287             #print STDERR "$2,$3--$4-----$&----\n";
288 13         28 $r->start( $chr);
289 13 100 33     38 if ($2 eq 'p'){
    50 33        
    50          
290 8         13 $r->end(-(_pad($band, 5, '0')) + 100000 + $chr);
291             }
292             elsif ($2 eq 'q') {
293 0         0 $r->end(_pad($band, 5, '9') + 100000 + $chr);
294             }
295             elsif ($2 eq 'cen' || $2 eq 'qcen' || $2 eq 'pcen' ) {
296 5         10 $r->end(100000 + $chr);
297             }
298             } else { # -p or -q at the end of the range
299 1         6 $self->throw("lone '$4' in $& does not make sense");
300             }
301             }
302             #
303             # e.g 10p22.1, 10pter
304             #
305             elsif (defined $3 ) {
306 27 100       51 if ($2 eq 'p') {
    100          
307 13 100       20 if ($3 eq 'ter') { # e.g. 10pter
308 3         7 $r = Bio::Range->new('-start' => $chr,
309             '-end' => $chr,
310             );
311             } else { # e.g 10p22.1
312 10         19 $r = Bio::Range->new('-start' => -(_pad($band, 5, '9')) + 100000 + $chr,
313             '-end' => -(_pad($band, 5, '0')) + 100000 + $chr,
314             );
315             }
316             } elsif ($2 eq 'q') {
317 13 100       20 if ($3 eq 'ter') { # e.g. 10qter
318 6         12 $r = Bio::Range->new('-start' => 200000 + $chr,
319             '-end' => 200000 + $chr,
320             );
321             } else { # e.g 10q22.1
322 7         14 $r = Bio::Range->new('-start' => _pad($band, 5, '0') + 100000 + $chr,
323             '-end' => _pad($band, 5, '9') + 100000 + $chr,
324             );
325             }
326             } else { # e.g. 10qcen1.1 !
327 1         4 $self->throw("'cen' in $& does not make sense");
328             }
329             }
330             #
331             # e.g. 10p
332             #
333             elsif (defined $2 ) { # e.g. 10p
334 15 100       30 if ($2 eq 'p' ) {
    100          
335 6         12 $r = Bio::Range->new('-start' => $chr,
336             '-end' => 100000 + $chr
337             );
338             }
339             elsif ($2 eq 'q' ) {
340 3         8 $r = Bio::Range->new('-start' => 100000 + $chr,
341             '-end' => 200000 + $chr
342             );
343             } else { # $2 eq 'cen' || 'qcen'
344 6         15 $r = Bio::Range->new('-start' => 100000 + $chr,
345             '-end' => 100000 + $chr
346             );
347             }
348             }
349             #
350             # chr only, e.g. X
351             #
352             else {
353 5         11 $r = Bio::Range->new('-start' => $chr,
354             '-end' => 200000 + $chr
355             );
356             }
357            
358 134 50       230 if ($r) {
359 134         211 $self->start($r->start);
360 134         208 $self->end($r->end);
361             }
362 134         322 return $r;
363             }
364              
365              
366             sub _pad {
367 206     206   520 my ($string, $len, $pad_char) = @_;
368 206 100       545 __PACKAGE__->throw("function _pad needs a positive integer length, not [$len]")
369             unless $len =~ /^\+?\d+$/;
370 205 100       300 __PACKAGE__->throw("function _pad needs a single character pad_char, not [$pad_char]")
371             unless length $pad_char == 1;
372 204   100     255 $string ||= '';
373 204         715 return $string . $pad_char x ( $len - length( $string ) );
374             }
375              
376             =head2 range2value
377              
378             Title : range2value
379             Usage : my $value = $obj->range2value($range);
380             Function: Sets and returns the value string based on start and end values of
381             the Bio::Range object passes as an argument.
382             Returns : string or false
383             Args : Bio::Range object
384              
385             =cut
386              
387             sub range2value {
388 16     16 1 43 my ($self,$value) = @_;
389 16 50       27 if( defined $value) {
390 16 50       32 if( ! $value->isa('Bio::Range') ) {
391 0         0 $self->throw("Is not a Bio::Range object but a [$value]");
392 0         0 return;
393             }
394 16 50       24 if( ! $value->start ) {
395 0         0 $self->throw("Start is not defined in [$value]");
396 0         0 return;
397             }
398 16 50       23 if( ! $value->end ) {
399 0         0 $self->throw("End is not defined in [$value]");
400 0         0 return;
401             }
402 16 50       21 if( $value->start < 100000 ) {
403 0         0 $self->throw("Start value has to be in millions, not ". $value->start);
404 0         0 return;
405             }
406 16 50       25 if( $value->end < 100000 ) {
407 0         0 $self->throw("End value has to be in millions, not ". $value->end);
408 0         0 return;
409             }
410              
411 16         22 my ($chr, $arm, $band) = $value->start =~ /(\d+)(\d)(\d{5})/;
412 16         28 my ($chr2, $arm2, $band2) = $value->end =~ /(\d+)(\d)(\d{5})/;
413              
414 16         33 my ($chrS, $armS, $bandS, $arm2S, $band2S, $sep) = ('', '', '', '', '', '' );
415             LOC: {
416             #
417             # chromosome
418             #
419 16 50       16 if ($chr == 100) {
  16 50       31  
420 0         0 $chrS = 'X';
421             }
422             elsif ($chr == 100) {
423 0         0 $chrS = 'Y';
424             } else {
425 16         17 $chrS = $chr;
426             }
427 16 100 100     47 last LOC if $arm == 0 and $arm2 == 2 and $band == 0 and $band2 == 0 ;
      100        
      66        
428             #
429             # arm
430             #
431 15 100       23 if ($arm == $arm2 ) {
432 9 100       17 if ($arm == 0) {
    100          
433 4         5 $armS = 'p';
434             #$armS = 'pter' if $band == 0 and $band2 == 0;
435 4 100       7 $bandS = 'ter' if $band == 0;
436             #$arm2S = 'p'; #?
437             }
438             elsif ($arm == 2) {
439 1         2 $armS = 'q';
440 1 50       4 $bandS = 'ter' if $band == 0;
441             } else {
442 4         6 $armS = 'q';
443             #$arm2S = 'q'; #?
444 4 100       8 $armS = 'cen', if $band == 0;# and $band2 == 0;
445             }
446             } else {
447 6 100       10 if ($arm == 0) {
448 5         6 $armS = 'p';
449 5         5 $arm2S = 'q';
450 5 100 66     13 $arm2S = '' if $band == 0 and $band2 == 0;
451             } else {
452 1         2 $armS = 'q';
453 1         1 $arm2S = 'p';
454 1 50 33     8 $arm2S = '' if $arm2 == 2 and $band == 0 and $band2 == 0;
      33        
455             }
456             }
457 15 100       26 last LOC if $band == $band2 ;
458 10         10 my $c;
459             #
460             # first band (ter is hadled with the arm)
461             #
462 10 100       15 if ($bandS ne 'ter') {
463 9 100       14 if ($armS eq 'p') {
464 6         7 $band = 100000 - $band;
465 6         8 $c = '9';
466             } else {
467 3         4 $c = '0';
468             }
469 9         98 $band =~ s/$c+$//;
470 9         15 $bandS = $band;
471 9 100       22 $bandS = substr($band, 0, 2). '.'. substr($band, 2) if length $band > 2;
472             }
473 10 50       16 last LOC unless $band2;
474             #
475             # second band
476             #
477 10 100       13 if ($arm2 == 0) {
478 3         3 $arm2S = 'p';
479 3         4 $band2 = 100000 - $band2;
480 3         4 $c = '0';
481             } else { # 1 or 2
482 7         8 $arm2S = 'q';
483 7         8 $c = '9';
484             }
485 10 100       14 if ($band2 == 0) {
486 3 100       5 if ($arm2 == 1) {
487 1         2 $arm2S = 'p';
488 1         2 $band2S = 'cen';
489             } else {
490 2         3 $band2S = 'ter';
491             }
492 3         6 last LOC;
493             }
494 7 50 33     13 last LOC if $band eq $band2 and $arm == $arm2;
495              
496 7         44 $band2 =~ s/$c+$//;
497 7         12 $band2S = $band2;
498 7 100       16 $band2S = substr($band2, 0, 2). '.'. substr($band2, 2) if length $band2 > 2;
499              
500             } # end of LOC:
501              
502 16 100 100     40 if ($armS eq 'p' and $arm2S eq 'p') {
503 4         6 my $tmp = $band2S;
504 4         3 $band2S = $bandS;
505 4         5 $bandS = $tmp;
506             }
507 16 100       31 $band2S = '' if $bandS eq $band2S ;
508 16 100       18 $armS = '' if $bandS eq 'cen';
509 16 100 100     30 $arm2S = '' if $armS eq $arm2S and $band2S ne 'ter';
510 16 100 100     36 $sep = '-' if $arm2S || $band2S;
511 16         36 $self->value( $chrS. $armS. $bandS. $sep. $arm2S. $band2S);
512             }
513 16         22 return $self->value;
514             }
515              
516             =head2 value
517              
518             Title : value
519             Usage : my $pos = $position->value;
520             Function: Get/Set the value for this position
521             Returns : scalar, value
522             Args : none to get, OR scalar to set
523              
524             =cut
525              
526             sub value {
527 215     215 1 1028 my ($self,$value) = @_;
528 215 100       309 if( defined $value ) {
529 63         85 $self->{'_value'} = $value;
530 63         98 $self->cytorange;
531             }
532 211         408 return $self->{'_value'};
533             }
534              
535             =head2 numeric
536              
537             Title : numeric
538             Usage : my $num = $position->numeric;
539             Function: Read-only method that is guarantied to return a numeric
540             representation of the start of this position.
541             Returns : int (the start of the range)
542             Args : optional Bio::RangeI object
543              
544             =cut
545              
546             sub numeric {
547 1     1 1 2 my $self = shift;
548 1         2 return $self->start(@_);
549             }
550              
551             =head2 chr
552              
553             Title : chr
554             Usage : my $mychr = $position->chr();
555             Function: Get/Set method for the chromosome string of the location.
556             Returns : chromosome value
557             Args : none to get, OR scalar to set
558              
559             =cut
560              
561             sub chr {
562 142     142 1 2671 my ($self,$chr) = @_;
563 142 100       207 if( defined $chr ) {
564 139         215 $self->{'_chr'} = $chr;
565             }
566 142         171 return $self->{'_chr'};
567             }
568              
569              
570             1;