| 0 | 1 =head1 LICENSE | 
|  | 2 | 
|  | 3 Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute | 
|  | 4 Copyright [2016-2018] EMBL-European Bioinformatics Institute | 
|  | 5 | 
|  | 6 Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 7 you may not use this file except in compliance with the License. | 
|  | 8 You may obtain a copy of the License at | 
|  | 9 | 
|  | 10      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 11 | 
|  | 12 Unless required by applicable law or agreed to in writing, software | 
|  | 13 distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 15 See the License for the specific language governing permissions and | 
|  | 16 limitations under the License. | 
|  | 17 | 
|  | 18 =head1 CONTACT | 
|  | 19 | 
|  | 20  Ensembl <http://www.ensembl.org/info/about/contact/index.html> | 
|  | 21 | 
|  | 22 =cut | 
|  | 23 | 
|  | 24 =head1 NAME | 
|  | 25 | 
|  | 26  Draw | 
|  | 27 | 
|  | 28 =head1 SYNOPSIS | 
|  | 29 | 
|  | 30  mv Draw.pm ~/.vep/Plugins | 
|  | 31  ./vep -i variations.vcf --plugin Draw | 
|  | 32 | 
|  | 33 =head1 DESCRIPTION | 
|  | 34 | 
|  | 35  A VEP plugin that draws pictures of the transcript model showing the | 
|  | 36  variant location. Can take five optional paramters: | 
|  | 37 | 
|  | 38  1) File name stem for images | 
|  | 39  2) Image width in pixels (default: 1000px) | 
|  | 40  3) Image height in pixels (default: 100px) | 
|  | 41  4) Transcript ID - only draw images for this transcript | 
|  | 42  5) Variant ID - only draw images for this variant | 
|  | 43 | 
|  | 44  e.g. | 
|  | 45 | 
|  | 46  ./vep -i variations.vcf --plugin Draw,myimg,2000,100 | 
|  | 47 | 
|  | 48  Images are written to [file_stem]_[transcript_id]_[variant_id].png | 
|  | 49 | 
|  | 50  Requires GD library installed to run. | 
|  | 51 | 
|  | 52 =cut | 
|  | 53 | 
|  | 54 package Draw; | 
|  | 55 | 
|  | 56 use strict; | 
|  | 57 use warnings; | 
|  | 58 | 
|  | 59 use Bio::EnsEMBL::Variation::Utils::BaseVepPlugin; | 
|  | 60 | 
|  | 61 # GD libraries for image creating | 
|  | 62 use GD; | 
|  | 63 use GD::Polygon; | 
|  | 64 | 
|  | 65 use Bio::EnsEMBL::Variation::Utils::VariationEffect qw(MAX_DISTANCE_FROM_TRANSCRIPT); | 
|  | 66 | 
|  | 67 use base qw(Bio::EnsEMBL::Variation::Utils::BaseVepPlugin); | 
|  | 68 | 
|  | 69 | 
|  | 70 sub new { | 
|  | 71     my $class = shift; | 
|  | 72 | 
|  | 73     my $self = $class->SUPER::new(@_); | 
|  | 74 | 
|  | 75     # configure | 
|  | 76     my @params = @{$self->params}; | 
|  | 77 | 
|  | 78     $self->{prefix}     = $params[0] || $self->{config}->{output_file}; | 
|  | 79     $self->{width}      = $params[1] || 1000; | 
|  | 80     $self->{height}     = $params[2] || 100; | 
|  | 81     $self->{transcript} = $params[3] || undef; | 
|  | 82     $self->{variant}    = $params[4] || undef; | 
|  | 83 | 
|  | 84     return $self; | 
|  | 85 } | 
|  | 86 | 
|  | 87 sub version { | 
|  | 88     return '2.4'; | 
|  | 89 } | 
|  | 90 | 
|  | 91 sub feature_types { | 
|  | 92     return ['Transcript']; | 
|  | 93 } | 
|  | 94 | 
|  | 95 sub variant_feature_types { | 
|  | 96     return ['BaseVariationFeature']; | 
|  | 97 } | 
|  | 98 | 
|  | 99 sub get_header_info { | 
|  | 100     return {}; | 
|  | 101 } | 
|  | 102 | 
|  | 103 sub run { | 
|  | 104     my ($self, $tva) = @_; | 
|  | 105 | 
|  | 106     my $main_tr = $tva->feature; | 
|  | 107     my $vf = $tva->base_variation_feature; | 
|  | 108 | 
|  | 109     return {} if defined($self->{transcript}) && $main_tr->stable_id ne $self->{transcript}; | 
|  | 110     return {} if defined($self->{variant}) && $vf->variation_name ne $self->{variant}; | 
|  | 111 | 
|  | 112     # if we're showing a gene fusion, get | 
|  | 113     my $second_tr = $tva->{_fusion_transcripts}->[0] if grep {$_->SO_term =~ /gene_fusion/} @{$tva->get_all_OverlapConsequences}; | 
|  | 114 | 
|  | 115     # set up scales etc | 
|  | 116     my $width    = $main_tr->feature_Slice->length + (2 * MAX_DISTANCE_FROM_TRANSCRIPT); | 
|  | 117     my $tr_start = $main_tr->start - MAX_DISTANCE_FROM_TRANSCRIPT; | 
|  | 118     my $tr_end   = $main_tr->end + MAX_DISTANCE_FROM_TRANSCRIPT; | 
|  | 119 | 
|  | 120     if(defined($second_tr)) { | 
|  | 121         $width    = (max(($main_tr->end, $second_tr->end)) - min(($main_tr->start, $second_tr->start))) + 1 + (2 * MAX_DISTANCE_FROM_TRANSCRIPT); | 
|  | 122         $tr_start = min(($main_tr->start, $second_tr->start)) - MAX_DISTANCE_FROM_TRANSCRIPT; | 
|  | 123         $tr_end   = max(($main_tr->end, $second_tr->end)) + MAX_DISTANCE_FROM_TRANSCRIPT; | 
|  | 124     } | 
|  | 125 | 
|  | 126     my $x_scale  = ($self->{width} - 20)  / $width; | 
|  | 127     my $y_scale  = ($self->{height} - 30) / 100; | 
|  | 128     my $x_off    = 10; | 
|  | 129     my $y_off    = 20; | 
|  | 130 | 
|  | 131     # create GD image object | 
|  | 132     my $img = GD::Image->new($self->{width}, $self->{height}); | 
|  | 133 | 
|  | 134     # set up some colours | 
|  | 135     my %colours = ( | 
|  | 136         white      => $img->colorAllocate(255,255,255), | 
|  | 137         black      => $img->colorAllocate(0,0,0), | 
|  | 138         grey       => $img->colorAllocate(200,200,200), | 
|  | 139         darkgrey   => $img->colorAllocate(150,150,150), | 
|  | 140         vlightgrey => $img->colorAllocate(235,235,235), | 
|  | 141         blue       => $img->colorAllocate(0,0,200), | 
|  | 142         lightblue  => $img->colorAllocate(200,200,255), | 
|  | 143         red        => $img->colorAllocate(200,0,0), | 
|  | 144         lightred   => $img->colorAllocate(255,200,200), | 
|  | 145         green      => $img->colorAllocate(0,200,0), | 
|  | 146         lightgreen => $img->colorAllocate(220,255,220), | 
|  | 147         yellow     => $img->colorAllocate(236,164,26), | 
|  | 148         purple     => $img->colorAllocate(195,50,212), | 
|  | 149     ); | 
|  | 150 | 
|  | 151     # scale bar | 
|  | 152     my $zero_string = '0' x (length(int(100 / $x_scale)) - 1); | 
|  | 153     my $bases_per_bar = '1'.$zero_string; | 
|  | 154 | 
|  | 155     my $start = int($tr_start / $bases_per_bar) * $bases_per_bar; | 
|  | 156     my $end = (int($tr_start / $bases_per_bar) + 1) * $bases_per_bar; | 
|  | 157     my $colour = 0; | 
|  | 158 | 
|  | 159     while($start < $tr_end) { | 
|  | 160         my $method = $colour ? 'rectangle' : 'filledRectangle'; | 
|  | 161 | 
|  | 162         $img->$method( | 
|  | 163             $x_off + (($start - $tr_start) * $x_scale), | 
|  | 164             $self->{height} - 15, | 
|  | 165             $x_off + (($end - $tr_start) * $x_scale), | 
|  | 166             $self->{height} - 10, | 
|  | 167             $colours{grey}, | 
|  | 168         ); | 
|  | 169 | 
|  | 170         # tick and label | 
|  | 171         if($start =~ /(5|0)$zero_string$/) { | 
|  | 172             my $string = $start; | 
|  | 173             1 while $string =~ s/^(-?\d+)(\d\d\d)/$1,$2/; | 
|  | 174 | 
|  | 175             $img->string( | 
|  | 176                 gdTinyFont, | 
|  | 177                 $x_off + (($start - $tr_start) * $x_scale) + 2, | 
|  | 178                 $self->{height} - 8, | 
|  | 179                 $string, | 
|  | 180                 $colours{black} | 
|  | 181             ); | 
|  | 182 | 
|  | 183             $img->line( | 
|  | 184                 $x_off + (($start - $tr_start) * $x_scale), | 
|  | 185                 $self->{height} - 15, | 
|  | 186                 $x_off + (($start - $tr_start) * $x_scale), | 
|  | 187                 $self->{height}, | 
|  | 188                 $start =~ /5$zero_string$/ ? $colours{grey} : $colours{black} | 
|  | 189             ); | 
|  | 190 | 
|  | 191             $img->dashedLine( | 
|  | 192                 $x_off + (($start - $tr_start) * $x_scale), | 
|  | 193                 0, | 
|  | 194                 $x_off + (($start - $tr_start) * $x_scale), | 
|  | 195                 $self->{height} - 15, | 
|  | 196                 $start =~ /5$zero_string$/ ? $colours{vlightgrey} : $colours{grey} | 
|  | 197             ) | 
|  | 198         } | 
|  | 199 | 
|  | 200         $colour = 1 - $colour; | 
|  | 201         $start += $bases_per_bar; | 
|  | 202         $end += $bases_per_bar; | 
|  | 203     } | 
|  | 204 | 
|  | 205     # render transcripts | 
|  | 206     foreach my $tr($main_tr, $second_tr) { | 
|  | 207         next unless defined($tr); | 
|  | 208 | 
|  | 209         # render introns | 
|  | 210         foreach my $intron(@{$tr->get_all_Introns}) { | 
|  | 211             $img->line( | 
|  | 212                 $x_off + (($intron->start - $tr_start) * $x_scale), | 
|  | 213                 $y_off + (20 * $y_scale), | 
|  | 214                 $x_off + (((($intron->start + $intron->end) / 2) - $tr_start) * $x_scale), | 
|  | 215                 $y_off + (10 * $y_scale), | 
|  | 216                 $colours{lightblue} | 
|  | 217             ); | 
|  | 218 | 
|  | 219             $img->line( | 
|  | 220                 $x_off + (((($intron->start + $intron->end) / 2) - $tr_start) * $x_scale), | 
|  | 221                 $y_off + (10 * $y_scale), | 
|  | 222                 $x_off + (($intron->end - $tr_start) * $x_scale), | 
|  | 223                 $y_off + (20 * $y_scale), | 
|  | 224                 $colours{lightblue} | 
|  | 225             ); | 
|  | 226         } | 
|  | 227 | 
|  | 228         # render exons | 
|  | 229         foreach my $exon(@{$tr->get_all_Exons}) { | 
|  | 230 | 
|  | 231             # non-coding part | 
|  | 232             $img->rectangle( | 
|  | 233                 $x_off + (($exon->start - $tr_start) * $x_scale), | 
|  | 234                 $y_off + (10 * $y_scale), | 
|  | 235                 $x_off + (($exon->end - $tr_start) * $x_scale), | 
|  | 236                 $y_off + (30 * $y_scale), | 
|  | 237                 $colours{lightblue} | 
|  | 238             ); | 
|  | 239 | 
|  | 240             # coding part | 
|  | 241             $img->filledRectangle( | 
|  | 242                 $x_off + (($exon->coding_region_start($tr) - $tr_start) * $x_scale), | 
|  | 243                 $y_off + (0 * $y_scale), | 
|  | 244                 $x_off + (($exon->coding_region_end($tr) - $tr_start) * $x_scale), | 
|  | 245                 $y_off + (40 * $y_scale), | 
|  | 246                 $colours{blue} | 
|  | 247             ) if defined $exon->coding_region_start($tr) && defined $exon->coding_region_end($tr); | 
|  | 248         } | 
|  | 249 | 
|  | 250         # add transcript direction indicator | 
|  | 251         if($tr->strand == 1) { | 
|  | 252 | 
|  | 253             # vertical line | 
|  | 254             $img->line( | 
|  | 255                 $x_off + (($tr->start - $tr_start) * $x_scale), | 
|  | 256                 $y_off + (-5 * $y_scale), | 
|  | 257                 $x_off + (($tr->start - $tr_start) * $x_scale), | 
|  | 258                 $y_off + (20 * $y_scale), | 
|  | 259                 $colours{lightblue}, | 
|  | 260             ); | 
|  | 261 | 
|  | 262             # horizontal line | 
|  | 263             $img->line( | 
|  | 264                 $x_off + (($tr->start - $tr_start) * $x_scale), | 
|  | 265                 $y_off + (-5 * $y_scale), | 
|  | 266                 $x_off + (($tr->start - $tr_start) * $x_scale) + 20, | 
|  | 267                 $y_off + (-5 * $y_scale), | 
|  | 268                 $colours{lightblue}, | 
|  | 269             ); | 
|  | 270 | 
|  | 271             # top arrow part | 
|  | 272             $img->line( | 
|  | 273                 $x_off + (($tr->start - $tr_start) * $x_scale) + 17, | 
|  | 274                 $y_off + (-8 * $y_scale), | 
|  | 275                 $x_off + (($tr->start - $tr_start) * $x_scale) + 20, | 
|  | 276                 $y_off + (-5 * $y_scale), | 
|  | 277                 $colours{lightblue}, | 
|  | 278             ); | 
|  | 279 | 
|  | 280             # bottom arrow part | 
|  | 281             $img->line( | 
|  | 282                 $x_off + (($tr->start - $tr_start) * $x_scale) + 17, | 
|  | 283                 $y_off + (-1 * $y_scale), | 
|  | 284                 $x_off + (($tr->start - $tr_start) * $x_scale) + 20, | 
|  | 285                 $y_off + (-5 * $y_scale), | 
|  | 286                 $colours{lightblue}, | 
|  | 287             ); | 
|  | 288 | 
|  | 289             # label | 
|  | 290             $img->string(gdTinyFont, $x_off + (($tr->start - $tr_start) * $x_scale) + 25, $y_off + (-12 * $y_scale), $tr->stable_id, $colours{blue}); | 
|  | 291         } | 
|  | 292 | 
|  | 293         else { | 
|  | 294 | 
|  | 295             # vertical line | 
|  | 296             $img->line( | 
|  | 297                 $x_off + (($tr->end - $tr_start) * $x_scale), | 
|  | 298                 $y_off + (20 * $y_scale), | 
|  | 299                 $x_off + (($tr->end - $tr_start) * $x_scale), | 
|  | 300                 $y_off + (47 * $y_scale), | 
|  | 301                 $colours{lightblue}, | 
|  | 302             ); | 
|  | 303 | 
|  | 304             # horizontal line | 
|  | 305             $img->line( | 
|  | 306                 $x_off + (($tr->end - $tr_start) * $x_scale), | 
|  | 307                 $y_off + (47 * $y_scale), | 
|  | 308                 $x_off + (($tr->end - $tr_start) * $x_scale) - 20, | 
|  | 309                 $y_off + (47 * $y_scale), | 
|  | 310                 $colours{lightblue}, | 
|  | 311             ); | 
|  | 312 | 
|  | 313             # top arrow part | 
|  | 314             $img->line( | 
|  | 315                 $x_off + (($tr->end - $tr_start) * $x_scale) - 17, | 
|  | 316                 $y_off + (50 * $y_scale), | 
|  | 317                 $x_off + (($tr->end - $tr_start) * $x_scale) - 20, | 
|  | 318                 $y_off + (47 * $y_scale), | 
|  | 319                 $colours{lightblue}, | 
|  | 320             ); | 
|  | 321 | 
|  | 322             # bottom arrow part | 
|  | 323             $img->line( | 
|  | 324                 $x_off + (($tr->end - $tr_start) * $x_scale) - 17, | 
|  | 325                 $y_off + (43 * $y_scale), | 
|  | 326                 $x_off + (($tr->end - $tr_start) * $x_scale) - 20, | 
|  | 327                 $y_off + (47 * $y_scale), | 
|  | 328                 $colours{lightblue}, | 
|  | 329             ); | 
|  | 330 | 
|  | 331             # label | 
|  | 332             $img->string(gdTinyFont, $x_off + (($tr->end - $tr_start) * $x_scale) - 100, $y_off + (43 * $y_scale), $tr->stable_id, $colours{blue}); | 
|  | 333         } | 
|  | 334     } | 
|  | 335 | 
|  | 336     # render variant | 
|  | 337     my $var_colour = 'green'; | 
|  | 338 | 
|  | 339     if($vf->class_SO_term =~ /deletion/) { | 
|  | 340         $var_colour = 'red'; | 
|  | 341     } | 
|  | 342 | 
|  | 343     $img->filledRectangle( | 
|  | 344         $x_off + (($vf->start - $tr_start) * $x_scale), | 
|  | 345         $y_off + (60 * $y_scale), | 
|  | 346         $x_off + (($vf->end - $tr_start) * $x_scale), | 
|  | 347         $y_off + (70 * $y_scale), | 
|  | 348         $colours{$var_colour}, | 
|  | 349     ); | 
|  | 350 | 
|  | 351     # variant label | 
|  | 352     $img->string( | 
|  | 353         gdTinyFont, | 
|  | 354         $x_off + (($vf->start - $tr_start) * $x_scale), | 
|  | 355         $y_off + (75 * $y_scale), | 
|  | 356         $vf->variation_name, | 
|  | 357         $colours{$var_colour} | 
|  | 358     ); | 
|  | 359 | 
|  | 360     my $vname = $vf->variation_name; | 
|  | 361     $vname =~ s/\//\_/g; | 
|  | 362     my $file = $self->{prefix}."_".$main_tr->stable_id."_".(defined($second_tr) ? $second_tr->stable_id."_" : "").$vname."\.png"; | 
|  | 363 | 
|  | 364     # check we're allowed to write to it | 
|  | 365     if(!defined($self->{config}->{force_overwrite}) && -e $file) { | 
|  | 366         die "ERROR: Image file $file already exists - choose a different file name stem or use --force_overwrite\n"; | 
|  | 367         return; | 
|  | 368     } | 
|  | 369 | 
|  | 370     open IM, ">$file" or die "ERROR: Could not write to file $file\n"; | 
|  | 371     binmode IM; | 
|  | 372     print IM $img->png; | 
|  | 373     close IM; | 
|  | 374 | 
|  | 375     return {}; | 
|  | 376 } | 
|  | 377 | 
|  | 378 sub max { | 
|  | 379     return (sort {$a <=> $b} @_)[-1]; | 
|  | 380 } | 
|  | 381 | 
|  | 382 sub min { | 
|  | 383     return (sort {$a <=> $b} @_)[0]; | 
|  | 384 } | 
|  | 385 | 
|  | 386 1; | 
|  | 387 |