File Coverage

Bio/Map/CytoPosition.pm
Criterion Covered Total %
statement 182 207 87.9
branch 138 162 85.1
condition 68 95 71.5
subroutine 10 10 100.0
pod 5 5 100.0
total 403 479 84.1


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             consistant 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 separting 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   934 use strict;
  4         6  
  4         101  
104 4     4   15 use integer;
  4         6  
  4         853  
105 4     4   659 use Bio::Range;
  4         6  
  4         101  
106              
107 4     4   19 use base qw(Bio::Map::Position);
  4         6  
  4         1448  
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 137 my ($self) = @_;
157 139         112 my ($chr, $r, $band, $band2, $arm, $arm2, $lc, $uc, $lcchar, $ucchar);
158              
159 139 100       212 return $r if not defined $self->{_value}; # returns undef
160             $self->{_value} =~
161             # -----1----- --------2--------- -----3----- -------4------- ---6---
162 138         631 m/([XY]|[0-9]+)(cen|qcen|pcen|[pq])?(ter|[.0-9]+)?-?([pq]?(cen|ter)?)?([.0-9]+)?/;
163 138 100       290 $self->warn("Not a valid value: ". $self->{_value}), return $r
164             if not defined $1 ; # returns undef
165              
166 137         182 $chr = uc $1;
167 137         223 $self->chr($chr);
168              
169 137 100       198 $chr = 100 if $chr eq 'X';
170 137 50       187 $chr = 101 if $chr eq 'Y';
171 137         150 $chr *= 1000000;
172              
173 137         250 $r = Bio::Range->new();
174              
175 137         118 $band = '';
176 137 100       235 if (defined $3 ) {
177 95 50       142 $2 || $self->throw("$& does not make sense: 'arm' or 'cen' missing");
178 95         96 $band = $3;
179 95         118 $band =~ tr/\.//d;
180             }
181 137 100 66     451 if (defined $6 ) {
    100          
    100          
    100          
182 57         55 $arm2 = $4;
183 57 100       95 $arm2 = $2 if $4 eq ''; # it is not necessary to repeat the arm [p|q]
184 57         44 $band2 = $6;
185 57         54 $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     463 if ($band ne '' and (defined $arm2 and $2 ne $arm2 and $arm2 eq 'q') ) {
    100 100        
    100 66        
    100 100        
    50 100        
      66        
190 14         11 $lc = 'start'; $lcchar = '9';
  14         10  
191 14         15 $uc = 'end'; $ucchar = '9';
  14         11  
192             }
193             elsif ($band ne 'ter' and $2 ne $arm2 and $arm2 eq 'p') {
194 17         13 $lc = 'end'; $lcchar = '9';
  17         8  
195 17         17 $uc = 'start'; $ucchar = '9';
  17         14  
196             }
197             elsif ($band eq 'ter' and $arm2 eq 'p') {
198 2         4 $uc = 'start'; $ucchar = '9';
  2         3  
199             } # $2 eq $arm2
200             elsif ($arm2 eq 'q') {
201 11 50       13 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
202 11         8 $lc = 'start'; $lcchar = '0';
  11         7  
203 11         10 $uc = 'end'; $ucchar = '9';
  11         10  
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       17 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
211 13         11 $lc = 'end'; $lcchar = '0';
  13         11  
212 13         8 $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     236 if ( (defined $arm2 and $arm2 eq 'p') or (defined $arm2 and $arm2 eq 'p') ) {
      33        
      66        
224 32         45 $r->$uc(-(_pad($band2, 5, $ucchar)) + 100000 + $chr );
225 32 100 100     229 if (defined $3 and $3 eq 'ter') {
    100 66        
    100 66        
226 2         7 $r->end(200000 + $chr);
227             }
228             elsif ($2 eq 'cen' or $2 eq 'qcen' or $2 eq 'pcen'){
229 8         16 $r->$lc(100000 + $chr);
230             }
231             elsif ($2 eq 'q') {
232 9         13 $r->$lc(_pad($band, 5, $lcchar) + 100000 + $chr );
233             } else {
234 13         19 $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         32 $r->$uc(_pad($band2, 5, $ucchar) + 100000 + $chr);
239 25 100 66     98 if ($2 eq 'cen' or $2 eq 'pcen') {
    100          
240 3         7 $r->$lc(100000 + $chr);
241             }
242             elsif ($2 eq 'p') {
243 14 50       19 if ($3 eq 'ter') {
244 0         0 $r->$lc(200000 + $chr);
245             } else {
246 14         17 $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     204 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         9 $r->end(100000 + $chr);
261 4 100       12 if ($2 eq 'p') {
    50          
262 3 50       5 if ($3 eq 'ter') {
263 0         0 $r->start($chr);
264             } else {
265 3         16 $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         4 $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         27 $r->end(200000 + $chr);
276 15 100 66     51 if ($2 eq 'p'){
    100 66        
    50          
277 6         10 $r->start(-(_pad($band, 5, '9')) + 100000 + $chr); #??? OK?
278             }
279             elsif ($2 eq 'q') {
280 3         6 $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         20 $r->start( $chr);
289 13 100 33     36 if ($2 eq 'p'){
    50 33        
    50          
290 8         11 $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         5 $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       47 if ($2 eq 'p') {
    100          
307 13 100       20 if ($3 eq 'ter') { # e.g. 10pter
308 3         6 $r = Bio::Range->new('-start' => $chr,
309             '-end' => $chr,
310             );
311             } else { # e.g 10p22.1
312 10         16 $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       19 if ($3 eq 'ter') { # e.g. 10qter
318 6         22 $r = Bio::Range->new('-start' => 200000 + $chr,
319             '-end' => 200000 + $chr,
320             );
321             } else { # e.g 10q22.1
322 7         12 $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         5 $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       29 if ($2 eq 'p' ) {
    100          
335 6         11 $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         13 $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       208 if ($r) {
359 134         182 $self->start($r->start);
360 134         159 $self->end($r->end);
361             }
362 134         286 return $r;
363             }
364              
365              
366             sub _pad {
367 206     206   482 my ($string, $len, $pad_char) = @_;
368 206 100       493 __PACKAGE__->throw("function _pad needs a positive integer length, not [$len]")
369             unless $len =~ /^\+?\d+$/;
370 205 100       263 __PACKAGE__->throw("function _pad needs a single character pad_char, not [$pad_char]")
371             unless length $pad_char == 1;
372 204   100     258 $string ||= '';
373 204         641 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 16 my ($self,$value) = @_;
389 16 50       24 if( defined $value) {
390 16 50       34 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       22 if( ! $value->start ) {
395 0         0 $self->throw("Start is not defined in [$value]");
396 0         0 return;
397             }
398 16 50       22 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       20 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         23 my ($chr, $arm, $band) = $value->start =~ /(\d+)(\d)(\d{5})/;
412 16         23 my ($chr2, $arm2, $band2) = $value->end =~ /(\d+)(\d)(\d{5})/;
413              
414 16         19 my ($chrS, $armS, $bandS, $arm2S, $band2S, $sep) = ('', '', '', '', '', '' );
415             LOC: {
416             #
417             # chromosome
418             #
419 16 50       14 if ($chr == 100) {
  16 50       36  
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     50 last LOC if $arm == 0 and $arm2 == 2 and $band == 0 and $band2 == 0 ;
      100        
      66        
428             #
429             # arm
430             #
431 15 100       24 if ($arm == $arm2 ) {
432 9 100       14 if ($arm == 0) {
    100          
433 4         4 $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       3 $bandS = 'ter' if $band == 0;
441             } else {
442 4         4 $armS = 'q';
443             #$arm2S = 'q'; #?
444 4 100       7 $armS = 'cen', if $band == 0;# and $band2 == 0;
445             }
446             } else {
447 6 100       10 if ($arm == 0) {
448 5         4 $armS = 'p';
449 5         4 $arm2S = 'q';
450 5 100 66     14 $arm2S = '' if $band == 0 and $band2 == 0;
451             } else {
452 1         2 $armS = 'q';
453 1         3 $arm2S = 'p';
454 1 50 33     8 $arm2S = '' if $arm2 == 2 and $band == 0 and $band2 == 0;
      33        
455             }
456             }
457 15 100       23 last LOC if $band == $band2 ;
458 10         9 my $c;
459             #
460             # first band (ter is hadled with the arm)
461             #
462 10 100       15 if ($bandS ne 'ter') {
463 9 100       11 if ($armS eq 'p') {
464 6         5 $band = 100000 - $band;
465 6         7 $c = '9';
466             } else {
467 3         4 $c = '0';
468             }
469 9         101 $band =~ s/$c+$//;
470 9         12 $bandS = $band;
471 9 100       21 $bandS = substr($band, 0, 2). '.'. substr($band, 2) if length $band > 2;
472             }
473 10 50       15 last LOC unless $band2;
474             #
475             # second band
476             #
477 10 100       14 if ($arm2 == 0) {
478 3         4 $arm2S = 'p';
479 3         2 $band2 = 100000 - $band2;
480 3         4 $c = '0';
481             } else { # 1 or 2
482 7         6 $arm2S = 'q';
483 7         6 $c = '9';
484             }
485 10 100       14 if ($band2 == 0) {
486 3 100       7 if ($arm2 == 1) {
487 1         1 $arm2S = 'p';
488 1         2 $band2S = 'cen';
489             } else {
490 2         2 $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         43 $band2 =~ s/$c+$//;
497 7         7 $band2S = $band2;
498 7 100       18 $band2S = substr($band2, 0, 2). '.'. substr($band2, 2) if length $band2 > 2;
499              
500             } # end of LOC:
501              
502 16 100 100     44 if ($armS eq 'p' and $arm2S eq 'p') {
503 4         4 my $tmp = $band2S;
504 4         4 $band2S = $bandS;
505 4         4 $bandS = $tmp;
506             }
507 16 100       22 $band2S = '' if $bandS eq $band2S ;
508 16 100       20 $armS = '' if $bandS eq 'cen';
509 16 100 100     37 $arm2S = '' if $armS eq $arm2S and $band2S ne 'ter';
510 16 100 100     41 $sep = '-' if $arm2S || $band2S;
511 16         39 $self->value( $chrS. $armS. $bandS. $sep. $arm2S. $band2S);
512             }
513 16         20 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 postion
521             Returns : scalar, value
522             Args : none to get, OR scalar to set
523              
524             =cut
525              
526             sub value {
527 215     215 1 1024 my ($self,$value) = @_;
528 215 100       291 if( defined $value ) {
529 63         69 $self->{'_value'} = $value;
530 63         92 $self->cytorange;
531             }
532 211         394 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         18 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 1762 my ($self,$chr) = @_;
563 142 100       190 if( defined $chr ) {
564 139         157 $self->{'_chr'} = $chr;
565             }
566 142         150 return $self->{'_chr'};
567             }
568              
569              
570             1;