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   1085 use strict;
  4         4  
  4         88  
104 4     4   14 use integer;
  4         4  
  4         21  
105 4     4   515 use Bio::Range;
  4         4  
  4         78  
106              
107 4     4   14 use base qw(Bio::Map::Position);
  4         3  
  4         1178  
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 189 my ($self) = @_;
157 139         118 my ($chr, $r, $band, $band2, $arm, $arm2, $lc, $uc, $lcchar, $ucchar);
158              
159 139 100       242 return $r if not defined $self->{_value}; # returns undef
160             $self->{_value} =~
161             # -----1----- --------2--------- -----3----- -------4------- ---6---
162 138         791 m/([XY]|[0-9]+)(cen|qcen|pcen|[pq])?(ter|[.0-9]+)?-?([pq]?(cen|ter)?)?([.0-9]+)?/;
163 138 100       335 $self->warn("Not a valid value: ". $self->{_value}), return $r
164             if not defined $1 ; # returns undef
165              
166 137         200 $chr = uc $1;
167 137         249 $self->chr($chr);
168              
169 137 100       241 $chr = 100 if $chr eq 'X';
170 137 50       177 $chr = 101 if $chr eq 'Y';
171 137         185 $chr *= 1000000;
172              
173 137         309 $r = Bio::Range->new();
174              
175 137         158 $band = '';
176 137 100       266 if (defined $3 ) {
177 95 50       189 $2 || $self->throw("$& does not make sense: 'arm' or 'cen' missing");
178 95         102 $band = $3;
179 95         134 $band =~ tr/\.//d;
180             }
181 137 100 66     569 if (defined $6 ) {
    100          
    100          
    100          
182 57         75 $arm2 = $4;
183 57 100       112 $arm2 = $2 if $4 eq ''; # it is not necessary to repeat the arm [p|q]
184 57         53 $band2 = $6;
185 57         55 $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     548 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         14 $lc = 'start'; $lcchar = '9';
  14         14  
191 14         9 $uc = 'end'; $ucchar = '9';
  14         17  
192             }
193             elsif ($band ne 'ter' and $2 ne $arm2 and $arm2 eq 'p') {
194 17         19 $lc = 'end'; $lcchar = '9';
  17         15  
195 17         14 $uc = 'start'; $ucchar = '9';
  17         12  
196             }
197             elsif ($band eq 'ter' and $arm2 eq 'p') {
198 2         4 $uc = 'start'; $ucchar = '9';
  2         4  
199             } # $2 eq $arm2
200             elsif ($arm2 eq 'q') {
201 11 50       18 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
202 11         14 $lc = 'start'; $lcchar = '0';
  11         10  
203 11         11 $uc = 'end'; $ucchar = '9';
  11         14  
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       24 if (_pad($band, 5, '0') < _pad($band2, 5, '0')) {
211 13         16 $lc = 'end'; $lcchar = '0';
  13         11  
212 13         12 $uc = 'start'; $ucchar = '9';
  13         12  
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     264 if ( (defined $arm2 and $arm2 eq 'p') or (defined $arm2 and $arm2 eq 'p') ) {
      33        
      66        
224 32         43 $r->$uc(-(_pad($band2, 5, $ucchar)) + 100000 + $chr );
225 32 100 100     237 if (defined $3 and $3 eq 'ter') {
    100 66        
    100 66        
226 2         4 $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         21 $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     118 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       26 if ($3 eq 'ter') {
244 0         0 $r->$lc(200000 + $chr);
245             } else {
246 14         19 $r->$lc(-(_pad($band, 5, $lcchar)) + 100000 + $chr);
247             }
248             } else { #$2.==q
249 8         15 $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     277 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         8 $r->end(100000 + $chr);
261 4 100       13 if ($2 eq 'p') {
    50          
262 3 50       5 if ($3 eq 'ter') {
263 0         0 $r->start($chr);
264             } else {
265 3         6 $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     65 if ($2 eq 'p'){
    100 66        
    50          
277 6         12 $r->start(-(_pad($band, 5, '9')) + 100000 + $chr); #??? OK?
278             }
279             elsif ($2 eq 'q') {
280 3         7 $r->start(_pad($band, 5, '0') + 100000 + $chr);
281             }
282             elsif ($2 eq 'cen' || $2 eq 'qcen' || $2 eq 'pcen' ) {
283 6         9 $r->start(100000 + $chr);
284             }
285             }
286             elsif ($4 eq 'pter' ) {
287             #print STDERR "$2,$3--$4-----$&----\n";
288 13         35 $r->start( $chr);
289 13 100 33     58 if ($2 eq 'p'){
    50 33        
    50          
290 8         15 $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         13 $r->end(100000 + $chr);
297             }
298             } else { # -p or -q at the end of the range
299 1         8 $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       61 if ($2 eq 'p') {
    100          
307 13 100       25 if ($3 eq 'ter') { # e.g. 10pter
308 3         10 $r = Bio::Range->new('-start' => $chr,
309             '-end' => $chr,
310             );
311             } else { # e.g 10p22.1
312 10         22 $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       22 if ($3 eq 'ter') { # e.g. 10qter
318 6         18 $r = Bio::Range->new('-start' => 200000 + $chr,
319             '-end' => 200000 + $chr,
320             );
321             } else { # e.g 10q22.1
322 7         17 $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         6 $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       36 if ($2 eq 'p' ) {
    100          
335 6         17 $r = Bio::Range->new('-start' => $chr,
336             '-end' => 100000 + $chr
337             );
338             }
339             elsif ($2 eq 'q' ) {
340 3         11 $r = Bio::Range->new('-start' => 100000 + $chr,
341             '-end' => 200000 + $chr
342             );
343             } else { # $2 eq 'cen' || 'qcen'
344 6         18 $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         13 $r = Bio::Range->new('-start' => $chr,
354             '-end' => 200000 + $chr
355             );
356             }
357            
358 134 50       278 if ($r) {
359 134         209 $self->start($r->start);
360 134         224 $self->end($r->end);
361             }
362 134         384 return $r;
363             }
364              
365              
366             sub _pad {
367 206     206   630 my ($string, $len, $pad_char) = @_;
368 206 100       617 __PACKAGE__->throw("function _pad needs a positive integer length, not [$len]")
369             unless $len =~ /^\+?\d+$/;
370 205 100       297 __PACKAGE__->throw("function _pad needs a single character pad_char, not [$pad_char]")
371             unless length $pad_char == 1;
372 204   100     267 $string ||= '';
373 204         786 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 21 my ($self,$value) = @_;
389 16 50       32 if( defined $value) {
390 16 50       51 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       28 if( ! $value->start ) {
395 0         0 $self->throw("Start is not defined in [$value]");
396 0         0 return;
397             }
398 16 50       29 if( ! $value->end ) {
399 0         0 $self->throw("End is not defined in [$value]");
400 0         0 return;
401             }
402 16 50       28 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       30 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         32 my ($chr, $arm, $band) = $value->start =~ /(\d+)(\d)(\d{5})/;
412 16         37 my ($chr2, $arm2, $band2) = $value->end =~ /(\d+)(\d)(\d{5})/;
413              
414 16         35 my ($chrS, $armS, $bandS, $arm2S, $band2S, $sep) = ('', '', '', '', '', '' );
415             LOC: {
416             #
417             # chromosome
418             #
419 16 50       15 if ($chr == 100) {
  16 50       47  
420 0         0 $chrS = 'X';
421             }
422             elsif ($chr == 100) {
423 0         0 $chrS = 'Y';
424             } else {
425 16         21 $chrS = $chr;
426             }
427 16 100 100     83 last LOC if $arm == 0 and $arm2 == 2 and $band == 0 and $band2 == 0 ;
      100        
      66        
428             #
429             # arm
430             #
431 15 100       27 if ($arm == $arm2 ) {
432 9 100       27 if ($arm == 0) {
    100          
433 4         8 $armS = 'p';
434             #$armS = 'pter' if $band == 0 and $band2 == 0;
435 4 100       14 $bandS = 'ter' if $band == 0;
436             #$arm2S = 'p'; #?
437             }
438             elsif ($arm == 2) {
439 1         3 $armS = 'q';
440 1 50       5 $bandS = 'ter' if $band == 0;
441             } else {
442 4         7 $armS = 'q';
443             #$arm2S = 'q'; #?
444 4 100       14 $armS = 'cen', if $band == 0;# and $band2 == 0;
445             }
446             } else {
447 6 100       16 if ($arm == 0) {
448 5         8 $armS = 'p';
449 5         7 $arm2S = 'q';
450 5 100 66     20 $arm2S = '' if $band == 0 and $band2 == 0;
451             } else {
452 1         3 $armS = 'q';
453 1         2 $arm2S = 'p';
454 1 50 33     14 $arm2S = '' if $arm2 == 2 and $band == 0 and $band2 == 0;
      33        
455             }
456             }
457 15 100       31 last LOC if $band == $band2 ;
458 10         10 my $c;
459             #
460             # first band (ter is hadled with the arm)
461             #
462 10 100       24 if ($bandS ne 'ter') {
463 9 100       15 if ($armS eq 'p') {
464 6         7 $band = 100000 - $band;
465 6         8 $c = '9';
466             } else {
467 3         5 $c = '0';
468             }
469 9         133 $band =~ s/$c+$//;
470 9         14 $bandS = $band;
471 9 100       31 $bandS = substr($band, 0, 2). '.'. substr($band, 2) if length $band > 2;
472             }
473 10 50       18 last LOC unless $band2;
474             #
475             # second band
476             #
477 10 100       18 if ($arm2 == 0) {
478 3         4 $arm2S = 'p';
479 3         5 $band2 = 100000 - $band2;
480 3         5 $c = '0';
481             } else { # 1 or 2
482 7         9 $arm2S = 'q';
483 7         7 $c = '9';
484             }
485 10 100       18 if ($band2 == 0) {
486 3 100       8 if ($arm2 == 1) {
487 1         2 $arm2S = 'p';
488 1         2 $band2S = 'cen';
489             } else {
490 2         3 $band2S = 'ter';
491             }
492 3         8 last LOC;
493             }
494 7 50 33     18 last LOC if $band eq $band2 and $arm == $arm2;
495              
496 7         73 $band2 =~ s/$c+$//;
497 7         12 $band2S = $band2;
498 7 100       22 $band2S = substr($band2, 0, 2). '.'. substr($band2, 2) if length $band2 > 2;
499              
500             } # end of LOC:
501              
502 16 100 100     61 if ($armS eq 'p' and $arm2S eq 'p') {
503 4         7 my $tmp = $band2S;
504 4         4 $band2S = $bandS;
505 4         5 $bandS = $tmp;
506             }
507 16 100       33 $band2S = '' if $bandS eq $band2S ;
508 16 100       26 $armS = '' if $bandS eq 'cen';
509 16 100 100     51 $arm2S = '' if $armS eq $arm2S and $band2S ne 'ter';
510 16 100 100     52 $sep = '-' if $arm2S || $band2S;
511 16         62 $self->value( $chrS. $armS. $bandS. $sep. $arm2S. $band2S);
512             }
513 16         36 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 1129 my ($self,$value) = @_;
528 215 100       400 if( defined $value ) {
529 63         86 $self->{'_value'} = $value;
530 63         104 $self->cytorange;
531             }
532 211         493 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         5 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 1791 my ($self,$chr) = @_;
563 142 100       232 if( defined $chr ) {
564 139         223 $self->{'_chr'} = $chr;
565             }
566 142         167 return $self->{'_chr'};
567             }
568              
569              
570             1;