line
stmt
bran
cond
sub
pod
time
code
1
# BioPerl module for Bio::Draw::Pictogram
2
#
3
# Please direct questions and support issues to
4
#
5
# Cared for by Shawn Hoon
6
#
7
# Copyright Shawn Hoon
8
#
9
# You may distribute this module under the same terms as perl itself
10
11
# POD documentation - main docs before the code
12
13
=head1 NAME
14
15
Bio::Draw::Pictogram - generate SVG output of Pictogram display for consensus motifs
16
17
=head1 SYNOPSIS
18
19
use Bio::Draw::Pictogram;
20
use Bio::SeqIO;
21
22
my $sio = Bio::SeqIO->new(-file=>$ARGV[0],-format=>'fasta');
23
my @seq;
24
while(my $seq = $sio->next_seq){
25
push @seq, $seq;
26
}
27
28
my $picto = Bio::Draw::Pictogram->new(-width=>"800",
29
-height=>"500",
30
-fontsize=>"60",
31
-plot_bits=>1,
32
-background=>{
33
'A'=>0.25,
34
'C'=>0.18,
35
'T'=>0.32,
36
'G'=>0.25},
37
-color=>{'A'=>'red',
38
'G'=>'blue',
39
'C'=>'green',
40
'T'=>'magenta'});
41
42
my $svg = $picto->make_svg(\@seq);
43
44
print $svg->xmlify."\n";
45
46
#Support for Bio::Matrix::PSM::SiteMatrix now included
47
48
use Bio::Matrix::PSM::IO;
49
50
my $picto = Bio::Draw::Pictogram->new(-width=>"800",
51
-height=>"500",
52
-fontsize=>"60",
53
-plot_bits=>1,
54
-background=>{
55
'A'=>0.25,
56
'C'=>0.18,
57
'T'=>0.32,
58
'G'=>0.25},
59
-color=>{'A'=>'red',
60
'G'=>'blue',
61
'C'=>'green',
62
'T'=>'magenta'});
63
64
my $psm = $psmIO->next_psm;
65
my $svg = $picto->make_svg($psm);
66
print $svg->xmlify;
67
68
=head1 DESCRIPTION
69
70
A module for generating SVG output of Pictogram display for consensus
71
motifs. This method of representation was describe by Burge and
72
colleagues: (Burge, C.B.,Tuschl, T., Sharp, P.A. in The RNA world II,
73
525-560, CSHL press, 1999)
74
75
This is a simple module that takes in an array of sequences (assuming
76
equal lengths) and calculates relative base frequencies where the
77
height of each letter reflects the frequency of each nucleotide at a
78
given position. It can also plot the information content at each
79
position scaled by the background frequencies of each nucleotide.
80
81
It requires the SVG-2.26 or later module by Ronan Oger available at
82
http://www.cpan.org
83
84
Recommended viewing of the SVG is the plugin available at Adobe:
85
http://www.adobe.com/svg
86
87
=head1 FEEDBACK
88
89
90
=head2 Mailing Lists
91
92
User feedback is an integral part of the evolution of this and other
93
Bioperl modules. Send your comments and suggestions preferably to one
94
of the Bioperl mailing lists. Your participation is much appreciated.
95
96
bioperl-l@bioperl.org - General discussion
97
http://bioperl.org/wiki/Mailing_lists - About the mailing lists
98
99
=head2 Support
100
101
Please direct usage questions or support issues to the mailing list:
102
103
I
104
105
rather than to the module maintainer directly. Many experienced and
106
reponsive experts will be able look at the problem and quickly
107
address it. Please include a thorough description of the problem
108
with code and data examples if at all possible.
109
110
=head2 Reporting Bugs
111
112
Report bugs to the Bioperl bug tracking system to help us keep track
113
the bugs and their resolution. Bug reports can be submitted via the
114
web:
115
116
https://github.com/bioperl/bioperl-live/issues
117
118
=head1 AUTHOR - Shawn Hoon
119
120
Email shawnh@fugu-sg.org
121
122
=head1 APPENDIX
123
124
The rest of the documentation details each of the object
125
methods. Internal methods are usually preceded with a "_".
126
127
=cut
128
129
package Bio::Draw::Pictogram;
130
1
1
608
use strict;
1
1
1
46
131
1
1
5
use SVG 2.26;
1
28
1
5
132
1
1
1265
use Bio::SeqIO;
1
3
1
31
133
1
1
6
use base qw(Bio::Root::Root);
1
1
1
60
134
135
1
1
6
use constant MAXBITS => 2;
1
2
1
1365
136
137
=head2 new
138
139
Title : new
140
Usage : my $picto = Bio::Draw::Pictogram->new(-width=>"800",
141
-height=>"500",
142
-fontsize=>"60",
143
-plot_bits=>1,
144
-background=>{
145
'A'=>0.25,
146
'C'=>0.18,
147
'T'=>0.32,
148
'G'=>0.25},
149
-color=>{'A'=>'red',
150
'G'=>'blue',
151
'C'=>'green',
152
'T'=>'magenta'});
153
Function: Constructor for Pictogram Object
154
Returns : L
155
156
=cut
157
158
sub new {
159
2
2
1
41
my ($caller,@args) = @_;
160
2
12
my $self = $caller->SUPER::new(@args);
161
2
13
my ($width,$height,$fontsize,$color,$background,$bit,$normalize) = $self->_rearrange([qw(WIDTH HEIGHT FONTSIZE COLOR BACKGROUND PLOT_BITS NORMALIZE)],@args);
162
2
50
7
$width||=800;
163
2
50
7
$height||=600;
164
2
19
my $svg = SVG->new(width=>$width,height=>$height);
165
2
758
$self->svg_obj($svg);
166
2
50
4
$fontsize ||= 80;
167
2
50
9
$self->fontsize($fontsize) if $fontsize;
168
2
50
8
$color = $color || {'T'=>'black','C'=>'blue','G'=>'green','A'=>'red'};
169
2
8
$self->color($color);
170
2
50
13
$background = $background || {'T'=>0.25,'C'=>0.25,'G'=>0.25,'A'=>0.25};
171
2
8
$self->background($background);
172
2
50
8
$self->plot_bits($bit) if $bit;
173
2
100
7
$self->normalize($normalize) if $normalize;
174
175
2
10
return $self;
176
}
177
178
=head2 make_svg
179
180
Title : make_svg
181
Usage : $picto->make_svg();
182
Function: make the SVG object
183
Returns : L
184
Arguments: A fasta file or array ref of L objects or a L
185
186
=cut
187
188
sub make_svg {
189
2
2
1
570
my ($self,$input) = @_;
190
2
8
my $fontsize = $self->fontsize;
191
2
7
my $size = $fontsize * 0.75;
192
2
3
my $width= $size;
193
2
5
my $height= $size+40;
194
2
7
my $color = $self->color;
195
196
#starting x coordinate for pictogram
197
2
5
my $x = 45+$size/2;
198
2
4
my $pos_y = $size * 2;
199
2
3
my $bit_y = $pos_y+40;
200
2
3
my @pwm;
201
202
2
3
my $bp = 1;
203
204
#input can be file or array ref of sequences
205
2
100
33
22
if(ref($input) eq 'ARRAY'){
50
206
1
2
@pwm = @{$self->_make_pwm($input)};
1
4
207
}
208
elsif(ref($input) && $input->isa("Bio::Matrix::PSM::SiteMatrixI")){
209
1
4
@pwm = $self->_make_pwm_from_site_matrix($input);
210
}
211
else {
212
0
0
my $sio = Bio::SeqIO->new(-file=>$input,-format=>"fasta");
213
0
0
my @seq;
214
0
0
while (my $seq = $sio->next_seq){
215
0
0
push @seq, $seq;
216
}
217
0
0
@pwm = @{$self->_make_pwm(\@seq)};
0
0
218
}
219
220
221
2
6
my $svg = $self->svg_obj;
222
2
6
my $seq_length = scalar(@pwm + 1) * $width + $x + $x;
223
2
4
my $seq_grp;
224
225
#scale the svg if length greater than svg width
226
2
100
9
if($seq_length > $svg->{-document}->{'width'}){
227
1
3
my $ratio = $svg->{-document}->{'width'}/($seq_length);
228
1
13
$seq_grp = $svg->group(transform=>"scale($ratio,1)");
229
}
230
else {
231
1
9
$seq_grp= $svg->group();
232
}
233
234
#do the drawing, each set is a base position
235
2
140
foreach my $set(@pwm){
236
34
65
my ($A,$C,$G,$T,$bits) = @$set;
237
34
33
my @array;
238
34
55
push @array, ['a',($A)];
239
34
48
push @array, ['g',($G)];
240
34
63
push @array, ['c',($C)];
241
34
43
push @array, ['t',($T)];
242
34
91
@array = sort {$b->[1]<=>$a->[1]}@array;
158
205
243
34
39
my $count = 1;
244
34
94
my $pos_group = $seq_grp->group(id=>"bp $bp");
245
34
1855
my $prev_size;
246
my $y_trans;
247
248
#draw each letter at each position
249
34
45
foreach my $letter(@array){
250
136
149
my $scale;
251
136
100
180
if($self->normalize){
252
100
119
$scale = $letter->[1];
253
} else {
254
36
51
$scale = $letter->[1] * ($bits / MAXBITS);
255
}
256
257
136
100
195
if($count == 1){
258
34
100
54
if($self->normalize){
259
25
31
$y_trans = 0;
260
} else {
261
9
13
$y_trans = (1 - ($bits / MAXBITS)) * $size;
262
}
263
}
264
else {
265
102
116
$y_trans += $prev_size;
266
}
267
$pos_group->text('id'=> uc($letter->[0]).$bp,height=>$height,
268
'width'=>$width,x=>$x,y=>$size,
269
'transform'=>"translate(0,$y_trans),scale(1,$scale)",
270
'style'=>{"font-size"=>$fontsize,
271
'text-anchor'=>'middle',
272
'font-family'=>'Verdana',
273
136
100
1083
'fill'=>$color->{uc $letter->[0]}})->cdata(uc $letter->[0]) if $scale > 0;
274
275
136
10343
$prev_size = $scale * $size;
276
136
160
$count++;
277
}
278
#plot the bit if required
279
34
50
51
if($self->plot_bits){
280
34
112
$seq_grp->text('x'=>$x,
281
'y'=>$bit_y,
282
'style'=>{"font-size"=>'10',
283
'text-anchor'=>'middle',
284
'font-family'=>'Verdana',
285
'fill'=>'black'})->cdata($bits);
286
}
287
34
2048
$bp++;
288
34
79
$x+=$width;
289
}
290
291
#plot the tags
292
2
50
6
$seq_grp->text(x=>int($width/2),y=>$bit_y,style=>{"font-size"=>'10','text-anchor'=>'middle','font-family'=>'Verdana','fill'=>'black'})->cdata("Bits:") if $self->plot_bits;
293
294
2
142
$seq_grp->text(x=>int($width/2),y=>$pos_y,style=>{"font-size"=>'10','text-anchor'=>'middle','font-family'=>'Verdana','fill'=>'black'})->cdata("Position:");
295
296
#plot the base positions
297
2
123
$x = 45+$size/2-int($width/2);
298
2
9
foreach my $nbr(1..($bp-1)){
299
34
121
$seq_grp->text(x=>$x+int($width/2),y=>$pos_y,style=>{"font-size"=>'10','text-anchor'=>'left','font-family'=>'Verdana','fill'=>'black'})->cdata($nbr);
300
34
1917
$x+=$width;
301
}
302
303
304
# $seq_grp->transform("scale(2,2)");
305
306
2
8
return $self->svg_obj($svg);
307
}
308
309
sub _make_pwm_from_site_matrix{
310
1
1
3
my ($self,$matrix) = @_;
311
1
3
my $bgd = $self->background;
312
1
2
my @pwm;
313
1
7
my $consensus = $matrix->consensus;
314
1
4
foreach my $i(1..length($consensus)){
315
25
53
my %base = $matrix->next_pos;
316
25
34
my $bits;
317
25
41
$bits+=($base{pA} * log2($base{pA}/$bgd->{'A'}));
318
25
43
$bits+=($base{pC} * log2($base{pC}/$bgd->{'C'}));
319
25
35
$bits+=($base{pG} * log2($base{pG}/$bgd->{'G'}));
320
25
38
$bits+=($base{pT} * log2($base{pT}/$bgd->{'T'}));
321
25
145
push @pwm, [$base{pA},$base{pC},$base{pG},$base{pT},abs(sprintf("%.3f",$bits))];
322
}
323
1
5
return @pwm;
324
}
325
326
sub _make_pwm {
327
1
1
2
my ($self,$input) = @_;
328
1
1
my $count = 1;
329
1
2
my %hash;
330
1
1
my $bgd = $self->background;
331
#sum up the frequencies at each base pair
332
1
3
foreach my $seq(@$input){
333
14
49
my $string = $seq->seq;
334
14
18
$string = uc $string;
335
14
34
my @motif = split('',$string);
336
14
15
my $pos = 1;
337
14
16
foreach my $t(@motif){
338
126
131
$hash{$pos}{$t}++;
339
126
131
$pos++;
340
}
341
14
24
$count++;
342
}
343
344
#calculate relative freq
345
1
2
my @pwm;
346
347
#decrement last count
348
1
2
$count--;
349
1
9
foreach my $pos(sort{$a<=>$b} keys %hash){
20
20
350
9
10
my @array;
351
9
100
22
push @array,($hash{$pos}{'A'}||0)/$count;
352
9
100
16
push @array,($hash{$pos}{'C'}||0)/$count;
353
9
100
23
push @array,($hash{$pos}{'G'}||0)/$count;
354
9
100
18
push @array,($hash{$pos}{'T'}||0)/$count;
355
356
#calculate bits
357
# relative entropy (RelEnt) or Kullback-Liebler distance
358
# relent = sum fk * log2(fk/gk) where fk is frequency of nucleotide k and
359
# gk the background frequency of nucleotide k
360
361
9
8
my $bits;
362
9
100
34
$bits+=(($hash{$pos}{'A'}||0) / $count) * log2((($hash{$pos}{'A'}||0)/$count) / ($bgd->{'A'}));
100
363
9
100
28
$bits+=(($hash{$pos}{'C'}||0) / $count) * log2((($hash{$pos}{'C'}||0)/$count) / ($bgd->{'C'}));
100
364
9
100
31
$bits+=(($hash{$pos}{'G'}||0) / $count) * log2((($hash{$pos}{'G'}||0)/$count) / ($bgd->{'G'}));
100
365
9
100
30
$bits+=(($hash{$pos}{'T'}||0) / $count) * log2((($hash{$pos}{'T'}||0)/$count) / ($bgd->{'T'}));
100
366
9
50
push @array, abs(sprintf("%.3f",$bits));
367
368
9
16
push @pwm,\@array;
369
}
370
1
4
return $self->pwm(\@pwm);
371
}
372
373
374
###various get/sets
375
376
=head2 fontsize
377
378
Title : fontsize
379
Usage : $picto->fontsize();
380
Function: get/set for fontsize
381
Returns : int
382
Arguments: int
383
384
=cut
385
386
sub fontsize {
387
4
4
1
9
my ($self,$obj) = @_;
388
4
100
8
if($obj){
389
2
5
$self->{'_fontsize'} = $obj;
390
}
391
4
9
return $self->{'_fontsize'};
392
}
393
394
=head2 color
395
396
Title : color
397
Usage : $picto->color();
398
Function: get/set for color
399
Returns : a hash reference
400
Arguments: a hash reference
401
402
=cut
403
404
sub color {
405
4
4
1
7
my ($self,$obj) = @_;
406
4
100
8
if($obj){
407
2
4
$self->{'_color'} = $obj;
408
}
409
4
9
return $self->{'_color'};
410
}
411
412
=head2 svg_obj
413
414
Title : svg_obj
415
Usage : $picto->svg_obj();
416
Function: get/set for svg_obj
417
Returns : L
418
Arguments: L
419
420
=cut
421
422
sub svg_obj {
423
6
6
1
17
my ($self,$obj) = @_;
424
6
100
16
if($obj){
425
4
9
$self->{'_svg_obj'} = $obj;
426
}
427
6
147
return $self->{'_svg_obj'};
428
}
429
430
=head2 plot_bits
431
432
Title : plot_bits
433
Usage : $picto->plot_bits();
434
Function: get/set for plot_bits to indicate whether to plot
435
information content at each base position
436
Returns :1/0
437
Arguments: 1/0
438
439
=cut
440
441
sub plot_bits {
442
38
38
1
54
my ($self,$obj) = @_;
443
38
100
55
if($obj){
444
2
4
$self->{'_plot_bits'} = $obj;
445
}
446
38
83
return $self->{'_plot_bits'};
447
}
448
449
=head2 normalize
450
451
Title : normalize
452
Usage : $picto->normalize($newval)
453
Function: get/set to make all columns the same height.
454
default is to scale height with information
455
content.
456
Returns : value of normalize (a scalar)
457
Args : on set, new value (a scalar or undef, optional)
458
459
460
=cut
461
462
sub normalize{
463
171
171
1
159
my $self = shift;
464
465
171
100
236
return $self->{'normalize'} = shift if @_;
466
170
273
return $self->{'normalize'};
467
}
468
469
=head2 background
470
471
Title : background
472
Usage : $picto->background();
473
Function: get/set for hash reference of nucleodtide bgd frequencies
474
Returns : hash reference
475
Arguments: hash reference
476
477
=cut
478
479
sub background {
480
4
4
1
9
my ($self,$obj) = @_;
481
4
100
8
if($obj){
482
2
5
$self->{'_background'} = $obj;
483
}
484
4
6
return $self->{'_background'};
485
}
486
487
=head2 pwm
488
489
Title : pwm
490
Usage : $picto->pwm();
491
Function: get/set for pwm
492
Returns : int
493
Arguments: int
494
495
=cut
496
497
sub pwm {
498
1
1
1
3
my ($self,$pwm) = @_;
499
1
50
3
if($pwm){
500
1
3
$self->{'_pwm'} = $pwm;
501
}
502
1
7
return $self->{'_pwm'};
503
}
504
505
#utility method for returning log 2
506
sub log2 {
507
136
136
0
140
my ($val) = @_;
508
136
100
177
return 0 if $val==0;
509
121
153
return log($val)/log(2);
510
}
511
512
513
1;