• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Ensembl / ensembl-variation / #640264871

03 Aug 2026 10:22AM UTC coverage: 83.125%. First build
#640264871

Pull #1217

travis-ci

Pull Request #1217: Fix hgvsp notation for extTer

2 of 4 new or added lines in 1 file covered. (50.0%)

22709 of 27319 relevant lines covered (83.13%)

1423.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.29
/modules/Bio/EnsEMBL/Variation/TranscriptVariationAllele.pm
1
=head1 LICENSE
2

3
Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
4
Copyright [2016-2026] 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
=cut
19

20

21
=head1 CONTACT
22

23
 Please email comments or questions to the public Ensembl
24
 developers list at <http://lists.ensembl.org/mailman/listinfo/dev>.
25

26
 Questions may also be sent to the Ensembl help desk at
27
 <http://www.ensembl.org/Help/Contact>.
28

29
=cut
30

31
=head1 NAME
32

33
Bio::EnsEMBL::Variation::TranscriptVariationAllele
34

35
=head1 SYNOPSIS
36

37
    use Bio::EnsEMBL::Variation::TranscriptVariationAllele;
38
    
39
    my $tva = Bio::EnsEMBL::Variation::TranscriptVariationAllele->new(
40
        -transcript_variation   => $tv,
41
        -variation_feature_seq  => 'A',
42
        -is_reference           => 0,
43
    );
44

45
    print "sequence with respect to the transcript: ", $tva->feature_seq, "\n";
46
    print "sequence with respect to the variation feature: ", $tva->variation_feature_seq, "\n";
47
    print "consequence SO terms: ", (join ",", map { $_->SO_term } @{ $tva->get_all_OverlapConsequences }), "\n";
48
    print "amino acid change: ", $tva->pep_allele_string, "\n";
49
    print "resulting codon: ", $tva->codon, "\n";
50
    print "reference codon: ", $tva->transcript_variation->get_reference_TranscriptVariationAllele->codon, "\n";
51
    print "PolyPhen prediction: ", $tva->polyphen_prediction, "\n";
52
    print "SIFT prediction: ", $tva->sift_prediction, "\n";
53

54
=head1 DESCRIPTION
55

56
A TranscriptVariationAllele object represents a single allele of a TranscriptVariation.
57
It provides methods that are specific to the sequence of the allele, such as codon,
58
peptide etc. Methods that depend only on position (e.g. CDS start) will be found in 
59
the associated TranscriptVariation. Ordinarily you will not create these objects 
60
yourself, but instead you would create a TranscriptVariation object which will then 
61
construct TranscriptVariationAlleles based on the allele string of the associated
62
VariationFeature. 
63

64
Note that any methods that are not specific to Transcripts will be found in the 
65
VariationFeatureOverlapAllele superclass.
66

67
=cut
68

69
package Bio::EnsEMBL::Variation::TranscriptVariationAllele;
70

71
use strict;
31✔
72
use warnings;
31✔
73

74
use Bio::EnsEMBL::Variation::ProteinFunctionPredictionMatrix qw($AA_LOOKUP);
31✔
75
use Bio::EnsEMBL::Utils::Exception qw(throw warning);
31✔
76
use Bio::EnsEMBL::Variation::Utils::Sequence qw(hgvs_variant_notation format_hgvs_string get_3prime_seq_offset);
31✔
77
use Bio::EnsEMBL::Utils::Sequence qw(reverse_comp);
31✔
78
use Bio::EnsEMBL::Variation::Utils::VariationEffect qw(overlap within_cds within_intron stop_lost start_lost frameshift stop_retained);
31✔
79

80
use base qw(Bio::EnsEMBL::Variation::VariationFeatureOverlapAllele Bio::EnsEMBL::Variation::BaseTranscriptVariationAllele);
31✔
81

82

83
our $DEBUG = 0;
84
our $NO_TRANSFER = 0;
85

86
sub new_fast {
87
    my ($class, $hashref, $strong) = @_;
103✔
88
    
89
    # swap a transcript_variation argument for a variation_feature_overlap one
90
    if ($hashref->{transcript_variation}) {
103✔
91
        $hashref->{variation_feature_overlap} = delete $hashref->{transcript_variation};
86✔
92
    }
93

94
    # and call the superclass
95

96
    return $class->SUPER::new_fast($hashref, $strong);
103✔
97
}
98

99
=head2 _return_3prime
100

101
  Description: Shifts an insertion/deletion as far as possible in the 3' direction, relative
102
               to the transcript.
103
               Called in TranscriptVariation.pm for original mapping and in 
104
               TranscriptVariationAllele.pm if required for HGVS
105
  Exceptions : none
106

107
=cut
108

109
sub _return_3prime {
110
    
111
  ## Will create a "shift_hash", containing info on precisely how the variant should be shifted when required
112
  my $self = shift;
453✔
113
  my $hgvs_only = shift;
453✔
114
  
115
  ## Return if we have already calculated a shifting hash for this allele
116
  return if defined($self->{shift_hash}) || ($self->is_reference && !$hgvs_only);
453✔
117
  
118
  my $tv = $self->transcript_variation;
449✔
119
  my $vf = $tv->base_variation_feature;
449✔
120
  my $var_class = $vf->var_class;
449✔
121
  
122
  ## Don't even attempt shifting if it's not an insertion or deletion
123
  return unless ($var_class eq 'insertion' || $var_class eq 'deletion' );
449✔
124

125
  ## If the associated database adaptor has switched HGVS shifting off, don't shift anything
126
  return if (defined($vf->adaptor) && defined($vf->adaptor->db)) && ($vf->adaptor->db->shift_hgvs_variants_3prime() == 0);
207✔
127
  
128
  my $vf_allele_string = $vf->allele_string;
196✔
129
  return if $vf_allele_string =~ /INS|DEL|HGMD|COSM|CNV/i;
196✔
130

131
  my $tr = $tv->transcript; 
195✔
132
  $self->initialise_unshifted_values;
195✔
133
  
134
  ## Checks to see if a simliar shift hash already exists for this variation feature.
135
  ## If not, a genomic shift is performed
136
  my $hash = $self->check_tva_shifting_hashes;
195✔
137
  $self->{shift_hash} = $hash if(defined($hash));
195✔
138
  
139
  ## Performs a shift in either the 5' or 3' direction depending on the strand of the transcript
140
  if (!defined($self->{shift_hash}))
195✔
141
  {
142
    $self->_genomic_shift($tr->strand);
142✔
143
    $hash = $self->check_tva_shifting_hashes;
142✔
144
    $self->{shift_hash} = $hash if(defined($hash))
142✔
145
  }
146

147
  return unless (defined($tv->cdna_start) && defined($tv->cdna_end) && defined($tv->cds_start) && defined($tv->cds_end)) 
195✔
148
  || (defined($tv->cdna_start_unshifted) && defined($tv->cdna_end_unshifted) && defined($tv->cds_start_unshifted) && defined($tv->cds_end_unshifted));
149
  
150
  return if ($tr->stable_id =~ /ENS/);
169✔
151
  my @attribs = @{$tr->get_all_Attributes()};
×
152
  
153
  ## Checks to see if the underlying sequence has been edited
154
  my @edit_attrs = grep {$_->code =~ /^_rna_edit/ && !$self->is_polyA($_)} @attribs;
×
155
  
156
  ## Return unless the RefSeq transcript has edited sequence - we need to check flanking sequences
157
  return unless scalar(@edit_attrs);
×
158
  
159
  my $hgvs_notation;
×
160

161
  ## Get all shift hashes generated for the associated variation feature
162
  ## If the flanking sequences are the same for (shift_length + 1) bases, we can just copy
163
  ## that shift hash rather than calculating another one
164
  my @preshifted_hashes = @{$self->base_variation_feature->{tva_shift_hashes}};
×
165
  if(scalar(@preshifted_hashes))
×
166
  {
167
    my ($slice_start2, $slice_end2, $slice ) = $self->_var2transcript_slice_coords($tr, $tv, $vf);
×
168

169
    if(defined($slice))
×
170
    {
171
      ## Sort through each shift hash with a corresponding transcript slice 
172
      ## Get the flanking sequences from the slice object and compare to previous hashes
173
      foreach my $shifted_obj (@preshifted_hashes)
×
174
      { 
175
        my $start = $var_class eq 'insertion' ? $tv->cdna_end_unshifted : $tv->cdna_start_unshifted;
×
176
        my $end = $var_class eq 'insertion' ? $tv->cdna_start_unshifted : $tv->cdna_end_unshifted;
×
177

178
        my $seq_to_search = defined($tr->{_variation_effect_feature_cache}->{spliced_seq}) ? $tr->{_variation_effect_feature_cache}->{spliced_seq} : $tr->seq->seq;
×
179
        my $whole_seq = substr($seq_to_search, $start - $shifted_obj->{shift_length} - 2, $end - $start + 1 + (2 * ($shifted_obj->{shift_length} + 1)));
×
180
        reverse_comp(\$whole_seq) if $tr->strand != 1;
×
181
        
182
        if(($shifted_obj->{type} eq 'ins' && (length($whole_seq) != ((2 * ($shifted_obj->{shift_length} + 1))))) || 
×
183
          ($shifted_obj->{type} eq 'del' && (length($whole_seq) != ((2 * ($shifted_obj->{shift_length} + 1)) + length($shifted_obj->{allele_string})))) )
184
        {
185
          ## This happens when an insertion/deletion gets too close to the transcript boundary and shifting in either direction at transcript level becomes tricky.
186
          $self->{shift_hash} = $shifted_obj;
×
187
          return;
×
188
        }
189
        
190
        my $pre_substr = substr($whole_seq, 0 , 1 + $shifted_obj->{shift_length});
×
191
        my $post_substr = substr($whole_seq, , -1 - $shifted_obj->{shift_length});
×
192
        if ($pre_substr eq $shifted_obj->{five_prime_flanking_seq} && $post_substr eq $shifted_obj->{three_prime_flanking_seq})
×
193
        {
194
          ## If both flanking sequences match, just copy over the shift hash
195
          $self->{shift_hash} = $shifted_obj;
×
196
          return;
×
197
        }
198
        
199
      }
200
    }
201
    else { ## This vf does not lie within that feature
202
      $self->{shift_hash} = $vf->{shift_hash} if (defined($vf->{shift_hash}) && $tr->strand == 1);
×
203
      $self->{shift_hash} = $vf->{shift_hash_reverse} if (defined($vf->{shift_hash_reverse}) && $tr->strand == -1);
×
204

205
      return;
×
206
    }
207
  }
208
  
209
  ## If we have no matching shift hashes already calculated, then we'll have to generate it
210
  
211
  my $edited_transcript_seq = defined($tr->{_variation_effect_feature_cache}->{spliced_seq}) ? $tr->{_variation_effect_feature_cache}->{spliced_seq} : $tr->seq->seq;
×
212
  my $start = $var_class eq 'insertion' ? $tv->cdna_end_unshifted : $tv->cdna_start_unshifted;
×
213
  my $end = $var_class eq 'insertion' ? $tv->cdna_start_unshifted : $tv->cdna_end_unshifted;
×
214

215
  ## To prevent having to work with too large a sequence, we get transcript sequence, get sequence +/- 1000bp from variant location, and check how far it can shift.
216
  ## Could be tidied up into a 'Shift 50 bases and if it's longer than that then take a larger slice'
217
  my $area_to_search = 1000;
×
218
  
219
  ## Gets flanking sequences around the variant to test if shifting is possible
220
  my $search_start = ($start - $area_to_search - 1) < 0 ? 0 : ($start - $area_to_search - 1);
×
221
  my $search_end = ($end + $area_to_search > length($edited_transcript_seq)) ? length($edited_transcript_seq) : ($end + $area_to_search);
×
222
  my $seqs = substr($edited_transcript_seq, $search_start, $search_end - $search_start);
×
223
  my $pre_seq = substr($seqs, 0, $start - $search_start - 1); 
×
224
  my $post_seq = substr($seqs, 0 - ($search_end - $end));
×
225
  
226
  my $unshifted_allele_string = $self->allele_string;
×
227
  my @allele_string = split('/', $unshifted_allele_string);
×
228
  my $hgvs_output_string = $allele_string[1];
×
229
  
230
  
231
  ## isolate correct sequence to attempt to shift
232
  my $seq_to_check;
×
233
  my $type;
234
  if ($var_class eq 'deletion')
×
235
  {
236
    $seq_to_check = $allele_string[0];
×
237
    $type = 'del';
×
238
  }
239
  elsif ($var_class eq 'insertion') 
240
  {
241
    $seq_to_check = $allele_string[1];
×
242
    $type = 'ins';
×
243
  }
244
  
245
  my $shift_length;
×
246
  my $strand = $tr->strand;
×
247

248
  # vf can be on different strand than transcript
249
  my $vf_strand = $self->variation_feature->strand;
×
250
  reverse_comp(\$seq_to_check) if $vf_strand != $strand;
×
251
  
252
  ## Actually performs the shift, and provides raw data in order to create shifting hash
253
  ($shift_length, $seq_to_check, $hgvs_output_string, $start, $end) = $self->perform_shift($seq_to_check, $post_seq, $pre_seq, $start, $end, $hgvs_output_string, (-1 * ($strand -1))/2, $strand); 
×
254
  
255
  ## Creates shift_hash to attach to VF and TVA objects for 
256
  $self->create_shift_hash($seq_to_check, $post_seq, $pre_seq, $start, $end, $hgvs_output_string, $type, $shift_length, $strand, 0);
×
257

258
  return;
×
259
}
260

261
=head2 check_tva_shifting_hashes
262

263
  Description: Checks to see if a matching shifting hash already exists for another
264
               tva object attached to the same variation feature
265
  Returntype : hash
266
  Status     : At Risk
267

268
=cut
269

270
sub check_tva_shifting_hashes {
271
  my $self = shift;
337✔
272
  my $preshifted_hashs = $self->base_variation_feature->{tva_shift_hashes};  
337✔
273
  my @shift_hashes = grep {$_->{unshifted_allele_string} eq $self->allele_string  && $self->transcript->strand eq $_->{strand}} @{$preshifted_hashs} if defined($preshifted_hashs);
337✔
274
  if (scalar(@shift_hashes) == 1)
337✔
275
  {
276
    return $shift_hashes[0];
195✔
277
  }
278
  return undef;
142✔
279
}
280

281
=head2 perform_shift
282

283
  Description: Calculates the maximum possible shift in the 3' direction, provides the 
284
               shift length, shifted allele string, and new start/end coordinates
285
               Requires flanking sequences, allele string, strand and feature location
286
  Returntype : 5 scalars - shift length, shifted allele string, hgvs allele string, start position, end position
287
  Status     : At Risk
288

289
=cut
290

291
sub perform_shift {
292
  ## Performs the shifting calculation
293
  my ($self, $seq_to_check, $post_seq, $pre_seq, $var_start, $var_end, $hgvs_output_string, $reverse, $seq_strand) = @_;
142✔
294
  ## get length of pattern to check 
295
  my $indel_length = (length $seq_to_check);
142✔
296
  my $shift_length = 0;
142✔
297

298
  # hgvs_output_string can be on different strand than seq_to_check
299
  my $vf_strand = $self->variation_feature->strand;
142✔
300
  my $hgvs_reverse = ($vf_strand != $seq_strand);
142✔
301
  
302
  ## Sets up the for loop, ensuring that the correct bases are compared depending on the strand
303
  my $loop_limiter = $reverse ? (length($pre_seq) - $indel_length) + 1 : (length($post_seq) - $indel_length);
142✔
304
  $loop_limiter = length($post_seq) if $loop_limiter < 0;
142✔
305
  
306
  for (my $n = $reverse; $n <= $loop_limiter; $n++ ){
142✔
307
    ## check each position in deletion/ following seq for match
308
    my $check_next_del;
235✔
309
    my $check_next_pre;
310
    my $hgvs_next_del;
235✔
311
    
312
    if ($reverse)
235✔
313
    {
314
      $check_next_del  = substr($seq_to_check, length($seq_to_check) -1, 1);
73✔
315
      $check_next_pre = substr($pre_seq, length($pre_seq) - $n, 1);
73✔
316
      $hgvs_next_del  = $hgvs_reverse ? substr($hgvs_output_string, 0, 1) : substr($hgvs_output_string, length($hgvs_output_string) -1, 1);
73✔
317
    }
318
    else{
319
      $check_next_del  = substr($seq_to_check, 0, 1);
162✔
320
      $check_next_pre = substr($post_seq, $n, 1);
162✔
321
      $hgvs_next_del  = $hgvs_reverse ? substr($hgvs_output_string, length($hgvs_output_string) -1, 1) : substr($hgvs_output_string, 0, 1); 
162✔
322
    }
323
    if($check_next_del eq $check_next_pre){
235✔
324
      
325
      ## move position of deletion along
326
      $shift_length++;
93✔
327
      
328
      ## Reforms the sequences to check and the HGVS output strings for the next iteration
329
      if($reverse)
93✔
330
      {
331
        $seq_to_check = substr($seq_to_check, 0, length($seq_to_check) -1);
28✔
332
        $hgvs_output_string = $hgvs_reverse ? substr($hgvs_output_string,1) : substr($hgvs_output_string, 0, length($hgvs_output_string) -1);
28✔
333
        $seq_to_check = $check_next_del . $seq_to_check;  
28✔
334
        $hgvs_output_string = $hgvs_reverse ? $hgvs_output_string . $hgvs_next_del : $hgvs_next_del . $hgvs_output_string;
28✔
335
      }
336
      else
337
      {
338
        $seq_to_check = substr($seq_to_check,1);
65✔
339
        $hgvs_output_string = $hgvs_reverse ? substr($hgvs_output_string, 0, length($hgvs_output_string) -1) : substr($hgvs_output_string,1);
65✔
340
        $seq_to_check .= $check_next_del;
65✔
341
        $hgvs_output_string = $hgvs_reverse ? $hgvs_next_del . $hgvs_output_string : $hgvs_output_string . $hgvs_next_del;
65✔
342
        $var_start++;
65✔
343
        $var_end++;
65✔
344
      }
345
    }
346
    else{
347
      last;            
142✔
348
    }
349
  }
350
  
351
  return $shift_length, $seq_to_check, $hgvs_output_string, $var_start, $var_end;
142✔
352
}
353

354
=head2 create_shift_hash
355

356
  Description: Generates a hash to attach to the TVA object to store shifting info.
357
               Can contain shifting info for both directions for genes with 
358
               transcripts on both strands
359
  Returntype : hash
360
  Status     : Stable
361

362
=cut
363

364

365
sub create_shift_hash
366
{
367
  my ($self, $seq_to_check, $post_seq, $pre_seq, $var_start, $var_end, $hgvs_output_string, $type, $shift_length, $strand, $genomic) = @_;
142✔
368
  my $vf = $self->variation_feature;
142✔
369
  my $five_prime_flanking_seq = substr($pre_seq, -1 - $shift_length);
142✔
370
  my $three_prime_flanking_seq = substr($post_seq, 0, $shift_length + 1);
142✔
371
  
372
  my @allele_string = split('/', $self->allele_string);
142✔
373
  reverse_comp(\$seq_to_check) if $vf->strand <0; 
142✔
374
  my %shift_hash = (
142✔
375
    "genomic" => $genomic,
376
    "strand" => $strand,
377
    "shifted_allele_string"  => $seq_to_check,
378
    "unshifted_allele_string" => $self->allele_string, 
379
    "shift_length"  => $shift_length,
380
    "start" => $var_start,
381
    "end" => $var_end,
382
    "type" => $type,
383
    "unshifted_start" => $vf->seq_region_start,
384
    "unshifted_end" => $vf->seq_region_end,
385
    "hgvs_allele_string" => $hgvs_output_string,
386
    "ref_orig_allele_string" => $allele_string[0],
387
    "alt_orig_allele_string" => $allele_string[1],
388
    "_hgvs_offset" => $shift_length,
389
    "five_prime_flanking_seq" => $five_prime_flanking_seq,
390
    "three_prime_flanking_seq" => $three_prime_flanking_seq,
391
    "allele_string" => $self->allele_string, 
392
  );
393
    $self->{shift_hash} = \%shift_hash unless $genomic;
142✔
394
    $vf->{shift_hash} = \%shift_hash if ($strand == 1 && $genomic);
142✔
395
    $vf->{shift_hash_reverse} = \%shift_hash if ($strand == -1 && $genomic);
142✔
396
    $vf->{tva_shift_hashes} = [] unless defined($vf->{tva_shift_hashes});
142✔
397
    push @{$vf->{tva_shift_hashes}}, \%shift_hash;
142✔
398
    
399
    
400
    return %shift_hash;
142✔
401
}
402

403
=head2 _genomic_shift
404

405
  Description: Performs the shifting at genomic level - all that's required for EnsEMBL 
406
               transcripts. Shifts 3' on either strand
407
  Status     : Stable
408

409
=cut
410

411
sub _genomic_shift
412
{
413
  ## Does the initial shift at the genomic level, can be on either strand
414
  my $self = shift;
142✔
415
  my $strand = shift;
142✔
416
  my $tv = $self->transcript_variation;
142✔
417
  my $vf = $tv->variation_feature;
142✔
418
  my $var_class = $vf->var_class;
142✔
419

420
  return unless ($var_class eq 'insertion' || $var_class eq 'deletion' );
142✔
421
  
422
  my $slice_to_shrink = $vf->slice;
142✔
423
  my ($slice_start, $slice_end, $var_start, $var_end) = ($slice_to_shrink->start, $slice_to_shrink->end, $vf->seq_region_start, $vf->seq_region_end );
142✔
424
  
425
  ## Gets chr slice, gets sequence +/- 1000bp from variant location, and checks how far it can shift.
426
  ## Could be tidied up into a 'Shift 50 bases and if it's longer than that then take a larger slice'
427
  my $area_to_search = 1000;
142✔
428
  my $orig_start = $var_start;
142✔
429
  my $orig_end = $var_end;
142✔
430
  
431
  my $new_slice = $slice_to_shrink->expand(0 - ($var_start - $slice_start - $area_to_search), 0 - ($slice_end - $var_end - $area_to_search));
142✔
432
  $new_slice = $new_slice->constrain_to_seq_region();
142✔
433
  
434
  ## Gets flanking sequences around the variant to test if shifting is possible
435
  my $seqs = $new_slice->seq;
142✔
436
  my $pre_seq = substr($seqs, 0, $area_to_search); 
142✔
437
  my $post_seq = substr($seqs, 0 - $area_to_search);
142✔
438
  
439
  my $unshifted_allele_string = $self->allele_string;
142✔
440
  my @allele_string = split('/', $unshifted_allele_string);
142✔
441
  $allele_string[1] = '-' if (($self->is_reference) && (scalar(@allele_string) == 1));
142✔
442
  my $hgvs_output_string = $allele_string[1];
142✔
443
  
444
  ## isolate correct sequence to attempt to shift
445
  my $seq_to_check;
142✔
446
  my $type;
447
  if ($var_class eq 'deletion')
142✔
448
  {
449
    $seq_to_check = $allele_string[0];
56✔
450
    $type = 'del';
56✔
451
  }
452
  elsif ($var_class eq 'insertion') 
453
  {
454
    $seq_to_check = $allele_string[1];
86✔
455
    $type = 'ins';
86✔
456
  }
457
  
458
  my $shift_length;
142✔
459
  reverse_comp(\$seq_to_check) if $self->variation_feature->strand <0; 
142✔
460
  
461
  ## Actually performs the shift, and provides raw data in order to create shifting hash
462
  ($shift_length, $seq_to_check, $hgvs_output_string, $var_start, $var_end) = $self->perform_shift($seq_to_check, $post_seq, $pre_seq, $var_start, $var_end, $hgvs_output_string, (-1 * ($strand -1))/2, 1); 
142✔
463
  
464
  ## Creates shift_hash to attach to VF and TVA objects for 
465
  $self->create_shift_hash($seq_to_check, $post_seq, $pre_seq, $var_start, $var_end, $hgvs_output_string, $type, $shift_length, $strand, 1);
142✔
466
}
467

468
=head2 look_for_slice_start
469

470
  Description: Finds slice_start value if it can't be found in the _return_3prime method
471
  Status     : At Risk
472

473
=cut
474

475
sub look_for_slice_start {
476
  my $self = shift;
216✔
477
  
478
  my $tv = $self->transcript_variation;
216✔
479
  my $vf = $tv->base_variation_feature;
216✔
480
  my $tr = $tv->transcript;
216✔
481
  
482
  my ($slice_start, $slice_end, $slice ) = $self->_var2transcript_slice_coords($tr, $tv, $vf);
216✔
483
  ## set new HGVS string
484
  if($slice_start)
216✔
485
  {
486
    ## add cache of seq/ pos required by c, n and p 
487
    $self->{_slice_start} = $slice_start;
216✔
488
    $self->{_slice_end}   = $slice_end;
216✔
489
    $self->{_slice}       = $slice;
216✔
490
  }
491
  return; 
216✔
492
}
493

494
=head2 clear_shifting_variables
495

496
  Description: Clears shifting variables to allow for the correct values to be 
497
               used by the OutputFactory and future analysis (i.e. Plugins)
498
  Status     : At Risk
499

500
=cut
501

502
sub clear_shifting_variables {
503
  
504
  my $self = shift;
2✔
505
  
506
  my $tv = $self->transcript_variation;
2✔
507
  my $tr ||= $tv->transcript;
2✔
508

509
  delete($tv->{cds_end});
2✔
510
  delete($tv->{cds_start});
2✔
511
  delete($tv->{_cds_coords});
2✔
512
  delete($tv->{translation_start});
2✔
513
  delete($tv->{translation_end});
2✔
514
  delete($tv->{_translation_coords});
2✔
515
  delete($tv->{cdna_start});
2✔
516
  delete($tv->{cdna_end});
2✔
517
  delete($tv->{_cdna_coords});
2✔
518
  
519
  if(defined($self->{shift_hash}))
2✔
520
  {
521
    $tv->cds_start(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
522
    $tv->cds_end(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
523
    $tv->cdna_start(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
524
    $tv->cdna_end(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
525
    $tv->translation_start(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
526
    $tv->translation_end(undef, $tr->strand * $self->{shift_hash}->{shift_length});
1✔
527
  }
528
}
529

530

531
=head2 initialise_unshifted_values
532

533
  Description: Populates the unshifted values at the beginning before any shifting
534
               is done
535
  Status     : Stable
536

537
=cut
538

539
sub initialise_unshifted_values {
540
  
541
  my $self = shift;
195✔
542
  
543
  my $tv = $self->transcript_variation;
195✔
544
  
545
  unless(defined($tv->{cds_end_unshifted}))
195✔
546
  {  
547
    $tv->cds_start_unshifted();
93✔
548
    $tv->cds_end_unshifted();
93✔
549
  }
550
  unless(defined($tv->{cdna_end_unshifted}))
195✔
551
  {  
552
    $tv->cdna_start_unshifted();
92✔
553
    $tv->cdna_end_unshifted();
92✔
554
  }
555
  unless(defined($tv->{translation_end_unshifted}))
195✔
556
  {  
557
    $tv->translation_start_unshifted();
93✔
558
    $tv->translation_end_unshifted();
93✔
559
  }
560
}
561

562

563
=head2 transcript_variation
564

565
  Description: Get/set the associated TranscriptVariation
566
  Returntype : Bio::EnsEMBL::Variation::TranscriptVariation
567
  Exceptions : throws if the argument is the wrong type
568
  Status     : Stable
569

570
=cut
571

572
sub transcript_variation {
573
    my ($self, $tv) = @_;
5,987✔
574
    assert_ref($tv, 'Bio::EnsEMBL::Variation::TranscriptVariation') if $Bio::EnsEMBL::Utils::Scalar::ASSERTIONS && $tv;
5,987✔
575
    return $self->variation_feature_overlap($tv);
5,987✔
576
}
577

578
=head2 variation_feature
579

580
  Description: Get the associated VariationFeature
581
  Returntype : Bio::EnsEMBL::Variation::VariationFeature
582
  Exceptions : none
583
  Status     : Stable
584

585
=cut
586

587
sub variation_feature {
588
    my $self = shift;
1,351✔
589
    return $self->transcript_variation->variation_feature;
1,351✔
590
}
591

592
=head2 affects_peptide
593

594
  Description: Check if this changes the resultant peptide sequence
595
  Returntype : boolean
596
  Exceptions : None
597
  Caller     : general
598
  Status     : At Risk
599

600
=cut
601

602
sub affects_peptide {
603
  my $self = shift;
10✔
604
  return scalar grep { $_->SO_term =~ /stop|missense|frameshift|inframe|initiator/ } @{$self->get_all_OverlapConsequences}; 
10✔
605
}
606

607
=head2 pep_allele_string
608

609
  Description: Return a '/' delimited string of the reference peptide and the 
610
               peptide resulting from this allele, or a single peptide if this
611
               allele does not change the peptide (e.g. because it is synonymous)
612
  Returntype : string or undef if this allele is not in the CDS
613
  Exceptions : none
614
  Status     : Stable
615

616
=cut
617

618
sub pep_allele_string {
619
    my ($self) = @_;
168✔
620

621
    my $pep = $self->peptide;
168✔
622
    
623
    return undef unless $pep;
168✔
624
    
625
    my $ref_pep = $self->transcript_variation->get_reference_TranscriptVariationAllele->peptide;
168✔
626

627
    return undef unless $ref_pep;
168✔
628
    
629
    return $ref_pep ne $pep ? $ref_pep.'/'.$pep : $pep;
168✔
630
}
631

632
=head2 codon_allele_string
633

634
  Description: Return a '/' delimited string of the reference codon and the 
635
               codon resulting from this allele 
636
  Returntype : string or undef if this allele is not in the CDS
637
  Exceptions : none
638
  Status     : Stable
639

640
=cut
641

642
sub codon_allele_string {
643
    my ($self) = @_;
1✔
644
    
645
    my $codon = $self->codon;
1✔
646
    
647
    return undef unless $codon;
1✔
648
    
649
    my $ref_codon = $self->transcript_variation->get_reference_TranscriptVariationAllele->codon;
1✔
650
    
651
    return $ref_codon.'/'.$codon;
1✔
652
}
653

654
=head2 display_codon_allele_string
655

656
  Description: Return a '/' delimited string of the reference display_codon and the 
657
               display_codon resulting from this allele. The display_codon identifies
658
               the nucleotides affected by this variant in UPPER CASE and other 
659
               nucleotides in lower case
660
  Returntype : string or undef if this allele is not in the CDS
661
  Exceptions : none
662
  Status     : At Risk
663

664
=cut
665

666
sub display_codon_allele_string {
667
    my ($self) = @_;
129✔
668
    
669
    my $display_codon = $self->display_codon;
129✔
670
    
671
    return undef unless $display_codon;
129✔
672
        
673
    my $ref_tva = $self->transcript_variation->get_reference_TranscriptVariationAllele;
129✔
674
    $ref_tva->{shift_hash} = $self->{shift_hash};
129✔
675
    
676
    my $ref_display_codon = $ref_tva->display_codon;
129✔
677

678
    return undef unless $ref_display_codon;
129✔
679
    
680
    return $ref_display_codon.'/'.$display_codon;
129✔
681
}
682

683
=head2 peptide
684

685
  Description: Return the amino acid sequence that this allele is predicted to result in
686
  Returntype : string or undef if this allele is not in the CDS or is a frameshift
687
  Exceptions : none
688
  Status     : Stable
689

690
=cut
691

692
sub peptide {
693
  my ($self, $peptide) = @_;
2,966✔
694

695
  $self->{peptide} = $peptide if $peptide;
2,966✔
696

697
  unless(exists($self->{peptide})) {
2,966✔
698

699
    $self->{peptide} = undef;
1,018✔
700

701
    return $self->{peptide} unless $self->seq_is_unambiguous_dna;
1,018✔
702

703
    if(my $codon = $self->codon) {
1,007✔
704

705
      # the codon method can set the peptide in some circumstances 
706
      # so check here before we try an (expensive) translation
707
      return $self->{peptide} if $self->{peptide};
793✔
708

709
      my $tv = $self->base_variation_feature_overlap;
775✔
710

711
      # for mithocondrial dna we need to to use a different codon table
712
      my $codon_table = $tv->_codon_table;
775✔
713

714
      # check the cache
715
      my $pep_cache = $main::_VEP_CACHE->{pep}->{$codon_table} ||= {};
775✔
716
      if(!($self->{is_reference} && scalar @{$tv->_seq_edits}) && ($self->{peptide} = $pep_cache->{$codon})) {
775✔
717
        return $self->{peptide};
551✔
718
      }
719
      
720
      # translate the codon sequence to establish the peptide allele
721
      
722
      # allow for partial codons - split the sequence into whole and partial
723
      # e.g. AAAGG split into AAA and GG            
724
      my $whole_codon   = substr($codon, 0, int(length($codon) / 3) * 3);
224✔
725
      my $partial_codon = substr($codon, int(length($codon) / 3) * 3);
224✔
726

727
      my $pep = '';
224✔
728
      
729
      if($whole_codon) {          
224✔
730
          my $codon_seq = Bio::Seq->new(
211✔
731
            -seq        => $whole_codon,
732
            -moltype    => 'dna',
733
            -alphabet   => 'dna',
734
            );
735
          
736
          $pep .= $codon_seq->translate(undef, undef, undef, $codon_table)->seq;
211✔
737
        }
738

739
      # apply any seq edits?
740
      my $have_edits = 0;
224✔
741

742
      if($self->{is_reference}) {
224✔
743
        my $seq_edits = $tv->_seq_edits;
90✔
744
        
745
        if(scalar @$seq_edits) {
90✔
746

747
          # get TV coords, switch if necessary
748
          my ($tv_start, $tv_end) = ($tv->translation_start, $tv->translation_end);
13✔
749
          ($tv_start, $tv_end) = ($tv_end, $tv_start) if $tv_start > $tv_end;
13✔
750
          
751
          # get all overlapping seqEdits
752
          SE: foreach my $se(grep {overlap($tv_start, $tv_end, $_->start, $_->end)} @$seq_edits) {
13✔
753
            my ($se_start, $se_end, $alt) = ($se->start, $se->end, $se->alt_seq);
3✔
754
            my $se_alt_seq_length = length($alt);
3✔
755
            $have_edits = 1;
3✔
756
            
757
            # loop over each overlapping pos
758
            foreach my $tv_pos(grep {overlap($_, $_, $se_start, $se_end)} ($tv_start..$tv_end)) {
3✔
759

760
              # in some cases, the sequence edit can shorten the protein
761
              # this means our TV can fall outside the range of the edited protein
762
              # therefore for safety jump out
763
              if($tv_pos - $se_start >= $se_alt_seq_length) {
3✔
764
                return $self->{peptide} = undef;
×
765
              }
766

767
              # apply edit, adjusting for string position
768
              substr($pep, $tv_pos - $tv_start, 1) = substr($alt, $tv_pos - $se_start, 1);
3✔
769
            }
770
          }
771
        }
772
      }
773
      
774
      if($partial_codon && $pep ne '*') {
224✔
775
        $pep .= 'X';
27✔
776
      }
777

778
      $pep ||= '-';
224✔
779

780
      $pep_cache->{$codon} = $pep if length($codon) <= 3 && !$have_edits;
224✔
781

782
      $self->{peptide} = $pep;
224✔
783
    }
784
  }
785

786
  return $self->{peptide};
2,386✔
787
}
788

789
=head2 codon
790

791
  Description: Return the codon sequence that this allele is predicted to result in
792
  Returntype : string or undef if this allele is not in the CDS or is a frameshift
793
  Exceptions : none
794
  Status     : Stable
795

796
=cut
797

798
sub codon {
799
  my ($self, $codon) = @_;
2,094✔
800
  $self->{codon} = $codon if defined $codon;
2,094✔
801

802
  unless(exists($self->{codon})) {
2,094✔
803

804
    $self->{codon} = undef;
1,026✔
805
  
806
    my $tv = $self->base_variation_feature_overlap;
1,026✔
807
    
808
    my $shifting_offset = 0;
1,026✔
809
    my $tr = $tv->transcript;
1,026✔
810
  
811
    $shifting_offset = (defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length})) ? $self->{shift_hash}->{shift_length} : 0;
1,026✔
812

813
    my ($tv_tr_start, $tv_tr_end) = ($tv->translation_start, $tv->translation_end);
1,026✔
814

815
    unless($tv_tr_start && $tv_tr_end && $self->seq_is_dna) {
1,026✔
816
      return $self->{codon};
219✔
817
    }
818

819
    # try to calculate the codon sequence
820
    $self->shift_feature_seqs unless $shifting_offset == 0;
807✔
821
    my $seq = $self->feature_seq;
807✔
822
    $seq = '' if $seq eq '-';
807✔
823

824
    my $ref = $tv->get_reference_TranscriptVariationAllele;
807✔
825

826
    # Mismatches between refseq transcripts and the reference genome are tracked in transcript attributes
827
    my @edit_attrs = grep {$_->code =~ /^_rna_edit/} @{$tr->get_all_Attributes()};
807✔
828

829
    # check if the refseq ref allele is the same as the alt allele
830
    # this is an invalid variant - throw warning
831
    if(!$self->{is_reference} && scalar @edit_attrs > 0) {
807✔
832
      my $refseq_ref = $edit_attrs[0]->value;
×
833
      $refseq_ref =~ s/\d+\s\d+\s//;
×
834
      if ($refseq_ref eq $seq) {
×
835
        $self->{invalid_alleles} = 1;
×
836
      }
837
    }
838

839
    # calculate necessary coords and lengths
840
    
841
    my $codon_cds_start = $tv_tr_start * 3 - 2;
807✔
842
    my $codon_cds_end   = $tv_tr_end * 3;
807✔
843
    my $codon_len       = $codon_cds_end - $codon_cds_start + 1;
807✔
844
    unless($shifting_offset == 0)
807✔
845
    {
846
      delete($tv->{cds_end});
20✔
847
      delete($tv->{cds_start});
20✔
848
      delete($tv->{_cds_coords});
20✔
849
    }
850
    return $self->{codon} if !defined($tv->cds_end(undef, $tr->strand * $shifting_offset)) || !defined($tv->cds_start(undef, $tr->strand * $shifting_offset));
807✔
851
    my $vf_nt_len       = $tv->cds_end(undef, $tr->strand * $shifting_offset) - $tv->cds_start(undef, $tr->strand * $shifting_offset) + 1;
805✔
852
    my $allele_len      = $self->seq_length;
805✔
853
    unless($shifting_offset == 0)
805✔
854
    {
855
      delete($tv->{cds_end});
18✔
856
      delete($tv->{cds_start});
18✔
857
      delete($tv->{_cds_coords});
18✔
858
    }
859
    my $cds;
805✔
860

861
    # if (abs($allele_len - $vf_nt_len) % 3)
862
    # This is frameshift variation
863
    # The resulting codon/peptide changed needs to be calculated
864
   
865
    ## Bioperl Seq object
866
    my $cds_obj = $self->_get_alternate_cds();
805✔
867
    return undef unless defined($cds_obj);
805✔
868
    $cds = ( $self->{is_reference} ? $tv->_translateable_seq() : $cds_obj->seq() );
805✔
869

870
    # modifies the $cds at the specific positions
871
    # this is necessary for RefSeq transcripts that have edited alleles saved in @edit_attrs
872
    if($self->{is_reference} && scalar @edit_attrs > 0) {
805✔
873
      substr($cds, $tv->cds_start(undef, $tr->strand * $shifting_offset) -1, $vf_nt_len) = $seq;
×
874
    }
875

876
    # and extract the codon sequence
877
    my $codon = ( $self->{is_reference} ? substr($cds, $codon_cds_start-1, $codon_len ) : substr($cds, $codon_cds_start-1, $codon_len + ($allele_len - $vf_nt_len)));
805✔
878
    if (length($codon) < 1) {
805✔
879
      $self->{codon}   = '-';
26✔
880
      $self->{peptide} = '-';
26✔
881
    }
882
    else {
883
       $self->{codon} = $codon;
779✔
884
    }
885
  }
886

887
  return $self->{codon};
1,873✔
888
}
889

890
=head2 display_codon
891

892
  Description: Return the codon sequence that this allele is predicted to result in
893
               with the affected nucleotides identified in UPPER CASE and other 
894
               nucleotides in lower case
895
  Returntype : string or undef if this allele is not in the CDS or is a frameshift
896
  Exceptions : none
897
  Status     : Stable
898

899
=cut
900

901
sub display_codon {
902
  my $self = shift;
637✔
903

904
  unless(exists($self->{_display_codon})) {
637✔
905

906
    # initialise so it doesn't get called again
907
    $self->{_display_codon} = undef;
573✔
908

909
    if(my $codon = $self->codon) {
573✔
910

911
      my $display_codon = lc $self->codon;
353✔
912

913
      if(my $codon_pos = $self->transcript_variation->codon_position) {
353✔
914

915
        # if this allele is an indel then just return all lowercase
916
        if ($self->feature_seq ne '-') {
353✔
917
            
918
          # codon_position is 1-based, while substr assumes the string starts at 0          
919
          my $pos = $codon_pos - 1;
302✔
920

921
          my $len = length $self->feature_seq;
302✔
922

923
          substr($display_codon, $pos, $len) = uc substr($display_codon, $pos, $len);
302✔
924
        }
925
      }
926

927
      $self->{_display_codon} = $display_codon;
353✔
928
    }
929
  }
930

931
  return $self->{_display_codon};
637✔
932
}
933

934
=head2 polyphen_prediction
935

936
  Description: Return the qualitative PolyPhen-2 prediction for the effect of this allele.
937
               (Note that we currently only have PolyPhen predictions for variants that 
938
               result in single amino acid substitutions in human)
939
  Returntype : string (one of 'probably damaging', 'possibly damaging', 'benign', 'unknown')
940
               if this is a missense change and a prediction is available, undef
941
               otherwise
942
  Exceptions : none
943
  Status     : At Risk
944

945
=cut
946

947
sub polyphen_prediction {
948
    my ($self, $classifier, $polyphen_prediction) = @_;
19✔
949
    
950
    $classifier ||= 'humvar';
19✔
951
    
952
    my $analysis = "polyphen_${classifier}";
19✔
953
    
954
    $self->{$analysis}->{prediction} = $polyphen_prediction if $polyphen_prediction;
19✔
955
    
956
    unless (defined $self->{$analysis}->{prediction}) {
19✔
957
        my ($prediction, $score) = $self->_protein_function_prediction($analysis);
7✔
958
        $self->{$analysis}->{score} = $score;
7✔
959
        $self->{$analysis}->{prediction} = $prediction;
7✔
960
    }
961
    
962
    return $self->{$analysis}->{prediction};
19✔
963
}
964

965
=head2 polyphen_score
966

967
  Description: Return the PolyPhen-2 probability that this allele is deleterious (Note that we 
968
               currently only have PolyPhen predictions for variants that result in single 
969
               amino acid substitutions in human)
970
  Returntype : float between 0 and 1 if this is a missense change and a prediction is 
971
               available, undef otherwise
972
  Exceptions : none
973
  Status     : At Risk
974

975
=cut
976

977
sub polyphen_score {
978
    my ($self, $classifier, $polyphen_score) = @_;
7✔
979
    
980
    $classifier ||= 'humvar';
7✔
981

982
    my $analysis = "polyphen_${classifier}";
7✔
983
    
984
    $self->{$analysis}->{score} = $polyphen_score if defined $polyphen_score;
7✔
985

986
    unless (defined $self->{$analysis}->{score}) {
7✔
987
        my ($prediction, $score) = $self->_protein_function_prediction($analysis);
×
988
        $self->{$analysis}->{score} = $score;
×
989
        $self->{$analysis}->{prediction} = $prediction;
×
990
    }
991

992
    return $self->{$analysis}->{score};
7✔
993
}
994

995
=head2 sift_prediction
996

997
  Description: Return the qualitative SIFT prediction for the effect of this allele.
998
               (Note that we currently only have SIFT predictions for variants that 
999
               result in single amino acid substitutions in human)
1000
  Returntype : string (one of 'tolerated', 'deleterious') if this is a missense 
1001
               change and a prediction is available, undef otherwise
1002
  Exceptions : none
1003
  Status     : At Risk
1004

1005
=cut
1006

1007
sub sift_prediction {
1008
    my ($self, $sift_prediction) = @_;
23✔
1009
    
1010
    $self->{sift_prediction} = $sift_prediction if $sift_prediction;
23✔
1011
    
1012
    unless (defined $self->{sift_prediction}) {
23✔
1013
        my ($prediction, $score) = $self->_protein_function_prediction('sift');
8✔
1014
        $self->{sift_score} = $score;
8✔
1015
        $self->{sift_prediction} = $prediction unless $self->{sift_prediction};
8✔
1016
    }
1017
    
1018
    return $self->{sift_prediction};
23✔
1019
}
1020

1021
=head2 sift_score
1022

1023
  Description: Return the SIFT score for this allele (Note that we currently only have SIFT 
1024
               predictions for variants that result in single amino acid substitutions in human)
1025
  Returntype : float between 0 and 1 if this is a missense change and a prediction is 
1026
               available, undef otherwise
1027
  Exceptions : none
1028
  Status     : At Risk
1029

1030
=cut
1031

1032
sub sift_score {
1033
    my ($self, $sift_score) = @_;
8✔
1034

1035
    $self->{sift_score} = $sift_score if defined $sift_score;
8✔
1036

1037
    unless (defined $self->{sift_score}) {
8✔
1038
        my ($prediction, $score) = $self->_protein_function_prediction('sift');
×
1039
        $self->{sift_score} = $score;
×
1040
        $self->{sift_prediction} = $prediction;
×
1041
    }
1042

1043
    return $self->{sift_score};
8✔
1044
}
1045

1046
=head2 cadd_prediction
1047

1048
  Description: Return the qualitative CADD prediction for the effect of this allele.
1049
               (Note that we currently only have predictions for variants that 
1050
               result in single amino acid substitutions in human)
1051
  Returntype : string (one of 'likely benign', 'likely deleterious') if this is a missense 
1052
               change and a prediction is available, undef otherwise. Predictions
1053
               are assigned based on CADD PHRED scores. CADD PHRED scores greater or
1054
               equal to 15 are considered likely deleterious.  
1055
  Exceptions : none
1056
  Status     : At Risk
1057

1058
=cut
1059

1060
sub cadd_prediction {
1061
  my ($self, $cadd_prediction) = @_;
1✔
1062
  return $self->_prediction('cadd_prediction', $cadd_prediction);
1✔
1063
}
1064

1065
=head2 dbnsfp_revel_prediction
1066

1067
  Description: Return the qualitative REVEL prediction for the effect of this allele.
1068
               (Note that we currently only have predictions for variants that 
1069
               result in single amino acid substitutions in human)
1070
  Returntype : string (one of 'likely_disease_causing', 'likely_not_disease_causing')
1071
               if this is a missense change and a prediction is available, undef otherwise.
1072
               We chose 0.5 as the threshold to assign the predictions. From the REVEL paper:
1073
               For example, 75.4% of disease mutations but only 10.9% of neutral variants
1074
               have a REVEL score above 0.5, corresponding to a sensitivity of 0.754 and
1075
               specificity of 0.891. 
1076
  Exceptions : none
1077
  Status     : At Risk
1078

1079
=cut
1080

1081
sub dbnsfp_revel_prediction {
1082
  my ($self, $dbnsfp_revel_prediction) = @_;
1✔
1083
  return $self->_prediction('dbnsfp_revel_prediction', $dbnsfp_revel_prediction);
1✔
1084
}
1085

1086
=head2 dbnsfp_alphamissense_prediction
1087

1088
  Description: Return the qualitative AlphaMissense prediction for the effect of this allele.
1089
               (Note that we currently only have predictions for variants that 
1090
               result in single amino acid substitutions in human)
1091
  Returntype : string (one of 'likely benign', 'benign', 'ambiguous', 'likely pathogenic', 'pathogenic').
1092
  Exceptions : none
1093
  Status     : At Risk
1094

1095
=cut
1096

1097
sub dbnsfp_alphamissense_prediction {
1098
  my ($self, $dbnsfp_alphamissense_prediction) = @_;
×
1099
  return $self->_prediction('dbnsfp_alphamissense_prediction', $dbnsfp_alphamissense_prediction);
×
1100
}
1101

1102
=head2 dbnsfp_esm1b_prediction
1103

1104
  Description: Return the qualitative ESM1b prediction for the effect of this allele.
1105
               (Note that we currently only have predictions for variants that 
1106
               result in single amino acid substitutions in human)
1107
  Returntype : string (one of 'tolerated' or 'deleterious').
1108
  Exceptions : none
1109
  Status     : At Risk
1110

1111
=cut
1112

1113
sub dbnsfp_esm1b_prediction {
1114
  my ($self, $dbnsfp_esm1b_prediction) = @_;
×
1115
  return $self->_prediction('dbnsfp_esm1b_prediction', $dbnsfp_esm1b_prediction);
×
1116
}
1117

1118
=head2 dbnsfp_meta_lr_prediction
1119

1120
  Description: Return the qualitative MetaLR prediction for the effect of this allele.
1121
               (Note that we currently only have predictions for variants that 
1122
               result in single amino acid substitutions in human)
1123
  Returntype : string (one of 'tolerated', 'damaging').
1124
               The score cutoff between "D" and "T" is 0.5.
1125
  Exceptions : none
1126
  Status     : At Risk
1127

1128
=cut
1129

1130
sub dbnsfp_meta_lr_prediction {
1131
  my ($self, $dbnsfp_meta_lr_prediction) = @_;
1✔
1132
  return $self->_prediction('dbnsfp_meta_lr_prediction', $dbnsfp_meta_lr_prediction);
1✔
1133
}
1134

1135
=head2 dbnsfp_mutation_assessor_prediction
1136

1137
  Description: Return the qualitative MutationAssessor prediction for the effect of this allele.
1138
               (Note that we currently only have predictions for variants that 
1139
               result in single amino acid substitutions in human)
1140
  Returntype : string (one of 'high', 'medium', 'low', 'neutral').
1141
  Exceptions : none
1142
  Status     : At Risk
1143

1144
=cut
1145

1146
sub dbnsfp_mutation_assessor_prediction {
1147
  my ($self, $dbnsfp_mutation_assessor_prediction) = @_;
1✔
1148
  return $self->_prediction('dbnsfp_mutation_assessor_prediction', $dbnsfp_mutation_assessor_prediction);
1✔
1149
}
1150

1151
=head2 _prediction
1152

1153
  Description: Return prediction for specified score type.
1154
  Returntype : float 
1155
  Exceptions : none
1156
  Status     : At Risk
1157

1158
=cut
1159

1160
sub _prediction {
1161
  my ($self, $prediction_type, $prediction) = @_;
4✔
1162
  $self->{$prediction_type} = $prediction if $prediction;
4✔
1163

1164
  unless (defined $self->{$prediction_type}) {
4✔
1165
    my $analysis = $prediction_type;
4✔
1166
    $analysis =~ s/_prediction//;
4✔
1167
    my ($prediction, $score) = $self->_protein_function_prediction($analysis);
4✔
1168
    my $score_type = $prediction_type;
4✔
1169
    $score_type =~ s/_prediction/_score/;
4✔
1170
    $self->{$score_type} = $score;
4✔
1171
    $self->{$prediction_type} = $prediction;
4✔
1172
  }
1173
  return $self->{$prediction_type};
4✔
1174
}
1175

1176
=head2 cadd_score
1177

1178
  Description: Return the CADD PHRED score for this allele.
1179
  Returntype : float if this is a missense change and a prediction is available, undef otherwise
1180
  Exceptions : none
1181
  Status     : At Risk
1182

1183
=cut
1184

1185
sub cadd_score {
1186
  my ($self, $cadd_score) = @_;
1✔
1187
  return $self->_score('cadd_score');
1✔
1188
}
1189

1190
=head2 dbnsfp_revel_score
1191

1192
  Description: Return the REVEL score for this allele. The score is retrieved from dbNSFP. (We only
1193
               have predictions for variants that result in single amino acid substitutions in human)
1194
  Returntype : float between 0 and 1 if this is a missense change and a prediction is 
1195
               available, undef otherwise
1196
  Exceptions : none
1197
  Status     : At Risk
1198

1199
=cut
1200

1201
sub dbnsfp_revel_score {
1202
  my ($self, $dbnsfp_revel_score) = @_;
1✔
1203
  return $self->_score('dbnsfp_revel_score');
1✔
1204
}
1205

1206
=head2 dbnsfp_alphamissense_score
1207

1208
  Description: Return the AlphaMissense score for this allele. The score is retrieved from dbNSFP. (We only
1209
               have predictions for variants that result in single amino acid substitutions in human)
1210
  Returntype : float if this is a missense change and a prediction is 
1211
               available, undef otherwise
1212
  Exceptions : none
1213
  Status     : At Risk
1214

1215
=cut
1216

1217
sub dbnsfp_alphamissense_score {
1218
  my ($self, $dbnsfp_alphamissense_score) = @_;
×
1219
  return $self->_score('dbnsfp_alphamissense_score', $dbnsfp_alphamissense_score);
×
1220
}
1221

1222
=head2 dbnsfp_esm1b_score
1223

1224
  Description: Return the ESM1b score for this allele. The score is retrieved from dbNSFP. (We only
1225
               have predictions for variants that result in single amino acid substitutions in human)
1226
  Returntype : float if this is a missense change and a prediction is 
1227
               available, undef otherwise
1228
  Exceptions : none
1229
  Status     : At Risk
1230

1231
=cut
1232

1233
sub dbnsfp_esm1b_score {
1234
  my ($self, $dbnsfp_esm1b_score) = @_;
×
1235
  return $self->_score('dbnsfp_esm1b_score', $dbnsfp_esm1b_score);
×
1236
}
1237

1238
=head2 dbnsfp_meta_lr_score
1239

1240
  Description: Return the MetaLR score for this allele. The score is retrieved from dbNSFP. (We only
1241
               have predictions for variants that result in single amino acid substitutions in human)
1242
  Returntype : float if this is a missense change and a prediction is 
1243
               available, undef otherwise
1244
  Exceptions : none
1245
  Status     : At Risk
1246

1247
=cut
1248

1249
sub dbnsfp_meta_lr_score {
1250
  my ($self, $dbnsfp_meta_lr_score) = @_;
1✔
1251
  return $self->_score('dbnsfp_meta_lr_score', $dbnsfp_meta_lr_score);
1✔
1252
}
1253

1254
=head2 dbnsfp_mutation_assessor_score
1255

1256
  Description: Return the MutationAssessor score for this allele. The score is retrieved from dbNSFP. (We only
1257
               have predictions for variants that result in single amino acid substitutions in human)
1258
  Returntype : float if this is a missense change and a prediction is 
1259
               available, undef otherwise
1260
  Exceptions : none
1261
  Status     : At Risk
1262

1263
=cut
1264

1265
sub dbnsfp_mutation_assessor_score {
1266
  my ($self, $dbnsfp_mutation_assessor_score) = @_;
1✔
1267
  return $self->_score('dbnsfp_mutation_assessor_score', $dbnsfp_mutation_assessor_score);
1✔
1268
}
1269

1270

1271
=head2 _score
1272

1273
  Description: Return score for specified score type.
1274
  Returntype : float 
1275
  Exceptions : none
1276
  Status     : At Risk
1277

1278
=cut
1279

1280
sub _score {
1281
  my ($self, $score_type, $score) = @_; 
4✔
1282
  $self->{$score_type} = $score if defined $score;
4✔
1283

1284
  unless (defined $self->{$score_type}) {
4✔
1285
      my $analysis = $score_type;
×
1286
      $analysis =~ s/_score//;
×
1287
      my ($prediction, $score) = $self->_protein_function_prediction($analysis);
×
1288
      my $prediction_type = $score_type;
×
1289
      $prediction_type =~ s/_score/_prediction/;
×
1290
      $self->{$score_type} = $score;
×
1291
      $self->{$prediction_type} = $prediction;
×
1292
  }
1293
  return $self->{$score_type};
4✔
1294
}
1295

1296
sub _protein_function_prediction {
1297
    my ($self, $analysis) = @_;
19✔
1298

1299
    # we can only get results for variants that cause a single amino acid substitution, 
1300
    # so check the peptide allele string first
1301

1302
    if ($self->pep_allele_string && $self->pep_allele_string =~ /^[A-Z]\/[A-Z]$/ && defined $AA_LOOKUP->{$self->peptide}) {
19✔
1303
        if (my $matrix = $self->transcript_variation->_protein_function_predictions($analysis)) {
19✔
1304
          
1305
            # temporary fix - check $matrix is not an empty hashref
1306
            if(ref($matrix) && ref($matrix) eq 'Bio::EnsEMBL::Variation::ProteinFunctionPredictionMatrix') {
19✔
1307
            
1308
                my ($prediction, $score) = $matrix->get_prediction(
19✔
1309
                    $self->transcript_variation->translation_start,
1310
                    $self->peptide,
1311
                );
1312

1313
                return wantarray ? ($prediction, $score) : $prediction;
19✔
1314
            }
1315
        }
1316
    }
1317
    
1318
    return undef;
×
1319
}
1320

1321
=head2 shift_feature_seqs
1322

1323
  Description: Updates the feature_seq and variation_feature_seq values associated with this TVA to be shifted
1324
  Returntype : string 
1325
  Exceptions : none
1326
  Status     : At Risk
1327

1328
=cut
1329

1330
sub shift_feature_seqs {
1331
    my $self = shift;
33✔
1332
    if(defined($self->{shift_hash}) && !$self->{shifted_feature_seqs})
33✔
1333
    {
1334
      my $vf_seq = $self->{variation_feature_seq};
15✔
1335
      my $f_seq = $self->{feature_seq};
15✔
1336
      
1337
      my $shift_length = $self->{shift_hash}->{shift_length} ||= 0;
15✔
1338
      $shift_length = $self->seq_length - $shift_length if $self->transcript->strand == -1;
15✔
1339
      for (my $n = 0; $n < $shift_length; $n++ ){
15✔
1340
        ## check each position in deletion/ following seq for match
1341
        $vf_seq = substr($vf_seq, 1) . substr($vf_seq, 0, 1) if defined($vf_seq);
10✔
1342
        $f_seq = substr($f_seq, 1) . substr($f_seq, 0, 1) if defined($f_seq);
10✔
1343
      } 
1344
      $self->variation_feature_seq($vf_seq);
15✔
1345
      $self->feature_seq($f_seq);
15✔
1346
      $self->{shifted_feature_seqs} = 1;
15✔
1347
    }   
1348
}
1349

1350

1351
=head2 hgvs_genomic
1352

1353
  Description: Return a string representing the genomic-level effect of this allele in HGVS format
1354
  Returntype : string 
1355
  Exceptions : none
1356
  Status     : At Risk
1357

1358
=cut
1359

1360
sub hgvs_genomic {
1361
    return _hgvs_generic(@_,'genomic');
6✔
1362
}
1363

1364

1365
=head2 hgvs_transcript
1366

1367
  Description: Return a string representing the CDS-level effect of this allele in HGVS format
1368
  Returntype : string or undef if this allele is not in the CDS
1369
  Exceptions : none
1370
  Status     : At Risk
1371

1372
=cut
1373
    
1374
    
1375
sub hgvs_transcript {
1376
  my $self = shift;
337✔
1377
  my $notation = shift;
337✔
1378
  my $no_shift = shift;
337✔
1379
  
1380
  ##### set if string supplied
1381
  $self->{hgvs_transcript} = $notation   if defined $notation;
337✔
1382

1383
  ##### return if held 
1384
  return $self->{hgvs_transcript}        if defined $self->{hgvs_transcript};
337✔
1385

1386
  ## This is cached to allow long form HGVS to be created
1387
  ## Set as undefined here to avoid re-calling if hgvs_transcript annotation not possible
1388
  $self->{hgvs_t_ref} = undef;
216✔
1389

1390
  my $tv = $self->base_variation_feature_overlap;
216✔
1391

1392
  ### don't attempt to recalculate if field is NULL from DB
1393
  return $self->{hgvs_transcript}        if exists $self->{hgvs_transcript} && defined($tv->dbID);
216✔
1394

1395

1396
  ### don't try to handle odd characters
1397
  return undef if $self->variation_feature_seq() =~ m/[^ACGT\-]/ig;
216✔
1398

1399
  ### no result for reference allele
1400
  return undef if $self->is_reference == 1;
216✔
1401

1402
  ### else evaluate
1403
  my $tr = $tv->transcript;
216✔
1404
  my $tr_stable_id = $tr->stable_id;
216✔
1405
  my $vf = $tv->base_variation_feature;
216✔
1406
    
1407
  ### get reference sequence strand
1408
  my $refseq_strand = $tr->strand();
216✔
1409

1410
  my $var_name = $vf->variation_name();
216✔
1411

1412
  if($DEBUG ==1){    
216✔
1413
          print "\nHGVS transcript: Checking ";
×
1414
          print " var_name $var_name " if defined $var_name ;
×
1415
          print " refseq strand => $refseq_strand"  if defined $refseq_strand;
×
1416
          print " seq name : " . $tr_stable_id  
×
1417
              if defined $tr_stable_id ;
1418
          print " var strand " . $vf->strand()  
×
1419
              if defined $vf->strand();
1420
          print " vf st " . $vf->strand()   
×
1421
                  if defined $vf->strand()  ;
1422
          print " seqname: " . $vf->seqname()  ." seq: " . $self->variation_feature_seq  
×
1423
              if defined  $vf->seqname();
1424
          print "\n";
×
1425
  }
1426

1427
  my $hgvs_notation ; ### store components of HGVS string in hash
216✔
1428

1429
  my $variation_feature_sequence;
1430
  my $adaptor_shifting_flag = 1;
216✔
1431
  ## Check previous shift_hgvs_variants_3prime flag and act accordingly
1432
  if (defined($vf->adaptor) && defined($vf->adaptor->db)) {
216✔
1433
    $adaptor_shifting_flag = $vf->adaptor->db->shift_hgvs_variants_3prime();
206✔
1434
  }
1435
  elsif(defined($Bio::EnsEMBL::Variation::DBSQL::DBAdaptor::DEFAULT_SHIFT_HGVS_VARIANTS_3PRIME)){
1436
    $adaptor_shifting_flag = $Bio::EnsEMBL::Variation::DBSQL::DBAdaptor::DEFAULT_SHIFT_HGVS_VARIANTS_3PRIME;
1✔
1437
  }
1438
  
1439
  my $hash_already_defined = defined($self->{shift_hash}); 
216✔
1440
  ## Perform HGVS shift even if no_shift is on
1441
  $self->_return_3prime(1) unless ($adaptor_shifting_flag == 0);
216✔
1442

1443
  $variation_feature_sequence = $self->variation_feature_seq();
216✔
1444
  $variation_feature_sequence = $self->{shift_hash}->{hgvs_allele_string} if defined($self->{shift_hash}) && $vf->var_class() eq 'insertion' && ($adaptor_shifting_flag != 0);
216✔
1445
 
1446
  my $offset_to_add = defined($self->{shift_hash}) ? $self->{shift_hash}->{_hgvs_offset} : 0;# + ($no_shift ? 0 : (0 - $self->{_hgvs_offset}) );
216✔
1447
  $self->{_hgvs_offset} = $offset_to_add;
216✔
1448
  ## delete the shifting hash if we generated it for HGVS calculations
1449
  delete($self->{shift_hash}) unless $hash_already_defined;
216✔
1450
 
1451
  ## return if a new transcript_variation_allele is not available - variation outside transcript
1452
  return undef unless defined $self->base_variation_feature_overlap;
216✔
1453
  $self->look_for_slice_start unless (defined  $self->{_slice_start});
216✔
1454
  
1455
  unless (defined  $self->{_slice_start} ){
216✔
1456
          print "Exiting hgvs_transcript: no slice start position for $var_name in trans" . $tr_stable_id . "\n " if $DEBUG == 1 ;
×
1457
          return undef;
×
1458
  }
1459
  ## this may be different to the input one for insertions/deletions
1460
    print "vfs: $variation_feature_sequence &  $self->{_slice_start} -> $self->{_slice_end}\n" if $DEBUG ==1;
216✔
1461

1462
  my $lookup_order = 1;
216✔
1463
  if($variation_feature_sequence && $vf->strand != $refseq_strand) {
216✔
1464
    reverse_comp(\$variation_feature_sequence);
56✔
1465
    $lookup_order = -1 if $adaptor_shifting_flag == 0;
56✔
1466
  };
1467
  ## delete consequences if we have an offset. This is only in here for when we want HGVS to shift but not consequences.
1468
  ## TODO add no_shift flag test
1469
  delete($self->{_predicate_cache}) if $self->transcript_variation->{shifted} && $offset_to_add != 0; 
216✔
1470
  print "sending alt: $variation_feature_sequence &  $self->{_slice_start} -> $self->{_slice_end} for formatting\n" if $DEBUG ==1;
216✔
1471

1472
  return undef if (($self->{_slice}->end - $self->{_slice}->start + 1) < ($self->{_slice_end} + $offset_to_add));
216✔
1473
  #return undef if (length($self->{_slice}->seq()) < ($self->{_slice_end} + $offset_to_add));
1474
  $hgvs_notation = hgvs_variant_notation(
1475
    $variation_feature_sequence,    ### alt_allele,
1476
    $self->{_slice}->seq(),                             ### using this to extract ref allele
1477
    $self->{_slice_start} + $offset_to_add,
1478
    $self->{_slice_end} + $offset_to_add,
216✔
1479
    "",
1480
    "",
1481
    $var_name,
1482
    $lookup_order
1483
  );
1484

1485
  ### This should not happen
1486
  unless($hgvs_notation->{'type'}){
216✔
1487
    #warn "Error - not continuing; no HGVS annotation\n";
1488
    return undef;
×
1489
  } 
1490

1491
  ## check for the same bases in ref and alt strings before or after the variant
1492
  $hgvs_notation = _clip_alleles($hgvs_notation) unless $hgvs_notation->{'type'} eq 'dup';
216✔
1493

1494
  print "hgvs transcript type : " . $hgvs_notation->{'type'} . "\n" if $DEBUG == 1;    
216✔
1495
  print "Got type: " . $hgvs_notation->{'type'} ." $hgvs_notation->{'ref'} -> $hgvs_notation->{'alt'}\n" if $DEBUG == 1;
216✔
1496

1497
  ### create reference name - transcript name & seq version
1498
  my $stable_id = $tr_stable_id;
216✔
1499
  $stable_id .= "." . $tr->version() 
216✔
1500
     unless (!defined $tr->version() || $stable_id =~ /\.\d+$/ || $stable_id =~ /LRG/); ## no version required for LRG's
1501
  $hgvs_notation->{'ref_name'} = $stable_id;
216✔
1502

1503
  ### get position relative to transcript features [use HGVS coords not variation feature coords due to dups]
1504
  # avoid doing this twice if start and end are the same
1505
  my $same_pos = $hgvs_notation->{start} == $hgvs_notation->{end};
216✔
1506
  
1507
  ## Mismatches between refseq transcripts and the reference genome are tracked in transcript attributes
1508
  my @edit_attrs = grep {$_->code =~ /^_rna_edit/} @{$tr->get_all_Attributes()};
216✔
1509

1510
  if(scalar @edit_attrs > 0) {
216✔
1511
    my $ref = $tv->get_reference_TranscriptVariationAllele;
1✔
1512
    $hgvs_notation->{ref} = $ref->feature_seq; # ref allele relative to the feature (transcript)
1✔
1513
    $hgvs_notation->{alt} = $self->feature_seq; # alt allele relative to the feature (transcript)
1✔
1514
  }
1515

1516
  my $misalignment_offset = 0;
216✔
1517
  $misalignment_offset = $self->get_misalignment_offset(\@edit_attrs) if (scalar(@edit_attrs) && (substr($tr->stable_id, 0,3) eq 'NM_' || substr($tr->stable_id, 0,3) eq 'XM_'));
216✔
1518
  
1519
  if ($vf->var_class eq 'SNP' && defined($self->{pre_consequence_predicates}) && $self->{pre_consequence_predicates}->{exon} && defined($tv->cds_start) && defined($tv->cds_end)) {
216✔
1520
    $hgvs_notation->{start} = $tv->cds_start;
13✔
1521
    $hgvs_notation->{end}   = $hgvs_notation->{start};
13✔
1522
  }
1523
  else{
1524
    $hgvs_notation->{start} = $self->_get_cDNA_position( $hgvs_notation->{start} + $misalignment_offset);
203✔
1525
    $hgvs_notation->{end}   = $same_pos ? $hgvs_notation->{start} : $self->_get_cDNA_position( $hgvs_notation->{end} + $misalignment_offset );
203✔
1526
  }
1527
  return undef unless defined  $hgvs_notation->{start}  && defined  $hgvs_notation->{end} ;
216✔
1528

1529
  # Make sure that start is always less than end
1530
  my ($exon_start_coord, $intron_start_offset) = $hgvs_notation->{start} =~ m/(\-?[0-9]+)\+?(\-?[0-9]+)?/;
216✔
1531
  my ($exon_end_coord,   $intron_end_offset)   = $same_pos ? ($exon_start_coord, $intron_start_offset) : $hgvs_notation->{end} =~ m/(\-?[0-9]+)\+?(\-?[0-9]+)?/;
216✔
1532
  $intron_start_offset ||= 0;
216✔
1533
  $intron_end_offset   ||= 0;
216✔
1534
  print "pre pos sort : $hgvs_notation->{start},$hgvs_notation->{end}\n" if $DEBUG ==1;
216✔
1535
  ($hgvs_notation->{start},$hgvs_notation->{end}) = ($hgvs_notation->{end},$hgvs_notation->{start}) if (
1536
    (($exon_start_coord  > $exon_end_coord) || ($exon_start_coord  == $exon_end_coord && $intron_start_offset > $intron_end_offset) ) &&
1537
    $hgvs_notation->{end} !~/\*/
216✔
1538
  );
1539
  print "post pos sort: $hgvs_notation->{start},$hgvs_notation->{end}\n" if $DEBUG ==1;
216✔
1540

1541
  # save these to be able to report exon coordinates and intron distances
1542
  $self->{_hgvs_exon_start_coordinate} = $exon_start_coord;
216✔
1543
  $self->{_hgvs_intron_start_offset} = $intron_start_offset;
216✔
1544
  $self->{_hgvs_exon_end_coordinate} = $exon_end_coord;
216✔
1545
  $self->{_hgvs_intron_end_offset} = $intron_end_offset;
216✔
1546

1547

1548
  if($tr->cdna_coding_start()){
216✔
1549
    $hgvs_notation->{'numbering'} = "c";  ### set 'c' if transcript is coding 
192✔
1550
  }    
1551
  else{
1552
    $hgvs_notation->{'numbering'} = "n";  ### set 'n' if transcript non-coding 
24✔
1553
  }
1554

1555
  ### generic formatting 
1556
  print "pre-format $hgvs_notation->{alt}\n" if $DEBUG ==1;
216✔
1557

1558
  $self->{hgvs_transcript} = format_hgvs_string( $hgvs_notation);
216✔
1559
  if($DEBUG ==1){ print "HGVS notation: " . $self->{hgvs_transcript} . " \n"; }
216✔
1560

1561
  ## save the HGVS style reference sequence in case the other long form of HGVS is required
1562
  $self->{hgvs_t_ref} = ($hgvs_notation->{type} eq 'dup') ? $hgvs_notation->{alt} :  $hgvs_notation->{ref};
216✔
1563

1564
  return $self->{hgvs_transcript}; 
216✔
1565
}
1566

1567
=head2 is_polyA
1568

1569
  Description: Tests RefSeq transcript attribute to see if it's a poly-A tail
1570
  Returntype : boolean value
1571
  Status     : Stable
1572

1573
=cut
1574

1575
sub is_polyA {
1576
  my $self = shift;
×
1577
  my $attr = shift;
×
1578
  my @spl_value = split(/ /, $attr->{value});
×
1579
  if((scalar(@spl_value) eq 3) && (length($self->transcript->seq->seq) <= 1 + (length($spl_value[2]) + $spl_value[0])))
×
1580
  {
1581
      return 1;
×
1582
  }
1583
  return 0;
×
1584
}
1585

1586
=head2 get_misalignment_offset
1587

1588
  Description: Calculate total offset created by RefSeq bam alignment 
1589
  Returntype : scalar value
1590
  Status     : Stable
1591

1592
=cut
1593

1594
sub get_misalignment_offset {
1595
  my $self = shift;
2✔
1596
  my $attrs = shift;
2✔
1597
  my $mlength = 0;
2✔
1598
  
1599
  ## For each refseq edit transcript attribute given, isolate the insertions and deletions found 
1600
  ## before the given variant and sum their lengths
1601
  foreach my $attr (@{$attrs})
2✔
1602
  {
1603
    next if defined($attr->{description}) && $attr->description =~ /op=X/;
2✔
1604
    ## Find out whether insertion or deletion, and get length  
1605
    my @split_val = split(/ /,$attr->{value});
2✔
1606
    next if (scalar(@split_val) eq 3) && ($split_val[1] - $split_val[0] + 1) eq length($split_val[2]);
2✔
1607
    my $type = (scalar(@split_val) eq 3) ? 'ins' : 'del';
2✔
1608
    my $var_location_start = $self->transcript_variation->cdna_start;
2✔
1609
    $var_location_start = 0 unless defined($var_location_start);
2✔
1610
    next if $var_location_start < $split_val[0];
2✔
1611

1612
    if ($type eq 'del')
2✔
1613
    {
1614
      $mlength += -1 - ($split_val[1] - $split_val[0]);
×
1615
    }
1616
    else{
1617
      $mlength += length($split_val[2]);
2✔
1618
    }
1619
  }
1620
   
1621
  $self->{refseq_misalignment_offset} = $mlength;
2✔
1622
  return $mlength;
2✔
1623
  
1624
}
1625

1626

1627
=head2 hgvs_transcript_reference
1628

1629
  Description: Return a string representing the reference sequence as could be used in HGVS notation
1630
               Useful for deletions where the recommended format formerly used the reference sequence
1631
               but no longer does.
1632
  Returntype : string or undef if no reference allele is available.
1633
  Exceptions : none
1634
  Status     : At Risk
1635

1636
=cut
1637

1638
sub hgvs_transcript_reference {
1639

1640
  my $self = shift;
46✔
1641

1642
  $self->hgvs_transcript() unless exists $self->{hgvs_t_ref};
46✔
1643
  return $self->{hgvs_t_ref};
46✔
1644

1645
}
1646

1647

1648
=head2 hgvs_protein
1649

1650
  Description: Return a string representing the protein-level effect of this allele in HGVS format
1651
  Returntype : string or undef if this allele is not in the CDS 
1652
  Exceptions : none
1653
  Status     : At Risk
1654
  
1655
=cut
1656

1657
sub hgvs_protein {
1658
  my $self     = shift;
136✔
1659
  my $notation = shift;
136✔
1660
  my $prediction_format = shift;
136✔
1661
  my $convert_to_three_letter = shift;
136✔
1662
  my $hgvs_notation; 
136✔
1663

1664
  if($DEBUG == 1){
136✔
1665
    print "\nStarting hgvs_protein with "; 
×
1666
    print " var: " . $self->transcript_variation->variation_feature->variation_name() 
×
1667
      if defined $self->transcript_variation->variation_feature->variation_name() ;
1668
    print   " trans: " . $self->transcript_variation->transcript->display_id() 
×
1669
      if defined $self->transcript_variation->transcript->display_id() ;
1670
    print "\n";
×
1671
  }
1672

1673
  ### set if string supplied
1674
  $self->{hgvs_protein} = $notation  if defined $notation;
136✔
1675

1676

1677
  ### return if set
1678
  return $self->{hgvs_protein}       if defined $self->{hgvs_protein} ;
136✔
1679
  
1680
  ### don't attempt to recalculate if field is NULL from DB
1681
  return $self->{hgvs_protein}       if exists $self->{hgvs_protein} && defined($self->transcript_variation->dbID);
135✔
1682
  
1683
  ### don't try to handle odd characters
1684
  return undef if $self->variation_feature_seq() =~ m/[^ACGT\-]/ig;
135✔
1685

1686
  ### no HGVS annotation for reference allele
1687
  return undef if $self->is_reference();
135✔
1688
  print "checking pos with hgvs prot\n" if $DEBUG ==1;
135✔
1689

1690
  ## HGVS requires the variant be located in as 3' position as possible
1691

1692
  ## return if a new transcript_variation_allele is not available - variation outside transcript
1693
  my $tv = $self->base_variation_feature_overlap;
135✔
1694
  return undef unless defined $tv;
135✔
1695

1696
  my $vf = $tv->base_variation_feature;
135✔
1697
  my $tr          = $tv->transcript;
135✔
1698

1699
  my $adaptor_shifting_flag = 1;
135✔
1700
  ## Check previous shift_hgvs_variants_3prime flag and act accordingly
1701
  if (defined($vf->adaptor) && defined($vf->adaptor->db)) {
135✔
1702
    $adaptor_shifting_flag = $vf->adaptor->db->shift_hgvs_variants_3prime();
126✔
1703
  }
1704
  elsif(defined($Bio::EnsEMBL::Variation::DBSQL::DBAdaptor::DEFAULT_SHIFT_HGVS_VARIANTS_3PRIME)){
1705
    $adaptor_shifting_flag = $Bio::EnsEMBL::Variation::DBSQL::DBAdaptor::DEFAULT_SHIFT_HGVS_VARIANTS_3PRIME;
×
1706
  }
1707
  
1708
  ## Check to see if the shift_hash is already defined, allowing us to remove it from associated $tva objects when we only want to shift HGVS
1709
  my $hash_already_defined = defined($self->{shift_hash});
135✔
1710
  ## Perform HGVS shift even if no_shift is on - only prevent shifting if shift_hgvs_variants_3prime() has been switched off.
1711
  $self->_return_3prime(1) unless ($adaptor_shifting_flag == 0);
135✔
1712

1713

1714
  my $pre         = $self->_pre_consequence_predicates;
135✔
1715
  my $shifting_offset = 0;
135✔
1716
  if($tr->strand() > 0) {
135✔
1717
    $shifting_offset = (defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length})) ? $self->{shift_hash}->{shift_length} : 0;
70✔
1718
  }
1719
  elsif($tr->strand < 0) {
1720
    $shifting_offset = (defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length})) ? 0 - $self->{shift_hash}->{shift_length} : 0;
65✔
1721
  }
1722

1723
  ### no HGVS protein annotation for variants outside translated region 
1724
  if(defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length}) && $self->{shift_hash}->{shift_length} != 0)
135✔
1725
  {
1726
    delete($tv->{translation_start});
14✔
1727
    delete($tv->{translation_end});
14✔
1728
    delete($tv->{_translation_coords});
14✔
1729
  }
1730
  unless (
135✔
1731
    ($pre->{coding}) &&
1732
    $tv->translation_start(undef, $shifting_offset) && 
1733
    $tv->translation_end(undef, $shifting_offset)
1734
  ){
1735
    print "Exiting hgvs_protein - variant " . $vf->variation_name() . "not within translation\n"  if $DEBUG == 1;
2✔
1736
    delete($self->{shift_hash}) unless $hash_already_defined;
2✔
1737
    return undef;
2✔
1738
  }
1739
       
1740
  print "proceeding with hgvs prot\n" if $DEBUG == 1;
133✔
1741
  print "Checking translation start: " . $tv->translation_start() ."\n" if $DEBUG == 1;
133✔
1742

1743
  ## checks complete - start building term
1744

1745
  ### get reference sequence
1746
  ### this is a temporary fix
1747
  if($tr->stable_id =~ /^ENS|^LRG/ || !defined($tr->analysis) || (defined($tr->analysis) && !defined($tr->analysis->db()))){
133✔
1748
    $hgvs_notation->{ref_name} = $tr->translation->display_id();
133✔
1749
  }
1750
  else{
1751
    ### get RefSeq identifiers
1752
    my @entries = grep {$_->{dbname} eq 'GenBank'} @{$tr->translation->get_all_DBEntries};
×
1753
    if(scalar @entries == 1){
×
1754
      $hgvs_notation->{ref_name} = $entries[0]->{primary_id};
×
1755
    }
1756
  }
1757

1758
  # Add seq version unless LRG 
1759
  $hgvs_notation->{ref_name} .= "." . $tr->translation->version() 
1760
    unless (!defined $tr->translation->version() || $hgvs_notation->{ref_name}=~ /\.\d+$/ || $hgvs_notation->{ref_name} =~ /LRG/ || $self->{remove_hgvsp_version});
133✔
1761

1762
  $hgvs_notation->{'numbering'} = 'p';
133✔
1763

1764
  ### get default reference location [changed later in some cases eg. duplication]
1765
  $hgvs_notation->{start}   = $tv->translation_start();
133✔
1766
  $hgvs_notation->{end}     = $tv->translation_end();  
133✔
1767

1768
  my $ref = $tv->get_reference_TranscriptVariationAllele;
133✔
1769

1770
  ## Incase the user wants shifted HGVS but not shifted consequences, we run the shifting method  
1771
  my $ref_hash_already_defined = defined($ref->{shift_hash});
133✔
1772
  $ref->_return_3prime(1) unless $ref_hash_already_defined;
133✔
1773
  ## get default reference & alt peptides  [changed later to hgvs format]
1774
  if(defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length})  && $self->{shift_hash}->{shift_length} != 0) {
133✔
1775
    delete($self->{peptide});
13✔
1776
    delete($self->{codon});
13✔
1777
    delete($self->{feature_seq});
13✔
1778
    $self->shift_feature_seqs();
13✔
1779

1780
    if(defined($ref->{shift_hash})) {
13✔
1781
      delete($ref->{peptide});
13✔
1782
      delete($ref->{codon});
13✔
1783
      delete($ref->{feature_seq});
13✔
1784

1785
      $ref->{variation_feature_seq} = $self->{shift_hash}->{ref_orig_allele_string};
13✔
1786
      $ref->{variation_feature_seq} = $self->{shift_hash}->{shifted_allele_string} if $vf->var_class eq 'deletion';
13✔
1787
      $ref->{shifted_feature_seqs} = 1;
13✔
1788
    }
1789
  }
1790
  
1791
  $hgvs_notation->{alt} = $self->peptide;
133✔
1792

1793
  $hgvs_notation->{ref} = $ref->peptide; 
133✔
1794
  
1795
  ## delete the shifting hash if we generated it for HGVS calculations
1796
  delete($self->{shift_hash}) unless $hash_already_defined;
133✔
1797
  delete($ref->{shift_hash}) unless $ref_hash_already_defined;
133✔
1798

1799
  return undef unless $hgvs_notation->{ref};   
133✔
1800
  print "Got protein peps: $hgvs_notation->{ref} =>  $hgvs_notation->{alt} (" . $self->codon() .")\n" if $DEBUG ==1;
133✔
1801

1802
  if(defined $hgvs_notation->{alt} && defined $hgvs_notation->{ref} &&
133✔
1803
    ($hgvs_notation->{alt} ne  $hgvs_notation->{ref})){
1804
    $hgvs_notation = _clip_alleles( $hgvs_notation);
112✔
1805
  }
1806

1807
  #### define type - types are different for protein numbering
1808
  $hgvs_notation  = $self->_get_hgvs_protein_type($hgvs_notation);
133✔
1809
  return undef unless defined $hgvs_notation->{type}; 
133✔
1810

1811
  $convert_to_three_letter = 1 unless defined $convert_to_three_letter;
133✔
1812
  ##### Convert ref & alt peptides taking into account HGVS rules
1813
  $hgvs_notation = $self->_get_hgvs_peptides($hgvs_notation, $convert_to_three_letter);
133✔
1814

1815
  unless($hgvs_notation) {
133✔
1816
    $self->{hgvs_protein} = undef;
×
1817
    return undef;
×
1818
  }
1819

1820
  ##### String formatting
1821
  return $self->_get_hgvs_protein_format($hgvs_notation, $prediction_format, $convert_to_three_letter);
133✔
1822
}
1823

1824

1825
=head2 hgvs_offset
1826

1827
  Description: Return the number of bases the variant was shifted 3'
1828
               to defined the HGVS transcript annotation 
1829
  Returntype : int or undef if HGVS has not been calculated or shift not applied
1830
  Exceptions : none
1831
  Status     : At risk
1832

1833
=cut
1834

1835
sub hgvs_offset {
1836
  my $self = shift;
17✔
1837
  #_hgvs_offset can usually be taken directly from the shift hash, however in situations where we remove the shift_hash from the $tva object after calculating HGVS then we can access it from $self->{_hgvs_offset}
1838
  return defined($self->{shift_hash}) ? $self->{shift_hash}->{_hgvs_offset} : $self->{_hgvs_offset};
17✔
1839
}
1840

1841
=head2 hgvs_exon_start_coordinate
1842

1843
  Description: Return the HGVS exon start coordinate
1844
  Returntype : int or undef if HGVS has not been calculated
1845
  Exceptions : none
1846
  Status     : At risk
1847

1848
=cut
1849

1850
sub hgvs_exon_start_coordinate {
1851
  my $self = shift;
1✔
1852
  return $self->{_hgvs_exon_start_coordinate};
1✔
1853
}
1854

1855
=head2 hgvs_intron_start_offset
1856

1857
  Description: Return the HGVS intron start offset
1858
  Returntype : int or undef if HGVS has not been calculated
1859
  Exceptions : none
1860
  Status     : At risk
1861

1862
=cut
1863

1864
sub hgvs_intron_start_offset {
1865
  my $self = shift;
1✔
1866
  return $self->{_hgvs_intron_start_offset};
1✔
1867
}
1868

1869
=head2 hgvs_exon_end_coordinate
1870

1871
  Description: Return the HGVS exon end coordinate
1872
  Returntype : int or undef if HGVS has not been calculated
1873
  Exceptions : none
1874
  Status     : At risk
1875

1876
=cut
1877

1878
sub hgvs_exon_end_coordinate {
1879
  my $self = shift;
1✔
1880
  return $self->{_hgvs_exon_end_coordinate};
1✔
1881
}
1882

1883
=head2 hgvs_intron_end_offset
1884

1885
  Description: Return the HGVS intron end offset
1886
  Returntype : int or undef if HGVS has not been calculated
1887
  Exceptions : none
1888
  Status     : At risk
1889

1890
=cut
1891

1892
sub hgvs_intron_end_offset {
1893
  my $self = shift;
1✔
1894
  return $self->{_hgvs_intron_end_offset};
1✔
1895
}
1896

1897
### HGVS: format protein string
1898
sub _get_hgvs_protein_format {
1899
  my $self          = shift;
133✔
1900
  my $hgvs_notation = shift;
133✔
1901
  my $prediction_format = shift;
133✔
1902
  my $convert_to_three_letter = shift;
133✔
1903

1904
  ### all start with refseq name & numbering type
1905
  $hgvs_notation->{'hgvs'} = $hgvs_notation->{'ref_name'} . ":" . $hgvs_notation->{'numbering'} . ".";
133✔
1906

1907
  ### add paranthesis if asked to report in predicted format
1908
  $hgvs_notation->{'hgvs'} .= "(" if $prediction_format;
133✔
1909

1910
  ### New (v 15.11) way to describe synonymous changes
1911
  if( $hgvs_notation->{ref} eq $hgvs_notation->{alt} && $hgvs_notation->{type} ne "fs" && $hgvs_notation->{type} ne "ins"){
133✔
1912
    $hgvs_notation->{'hgvs'} .= $hgvs_notation->{ref} . $hgvs_notation->{start} . "=";
21✔
1913
  }
1914

1915
  ### handle stop_lost seperately regardless of cause by del/delins => p.TerposAA1extnum_AA_to_stop
1916
  elsif(stop_lost($self) && ($hgvs_notation->{type} eq "del" || $hgvs_notation->{type} eq ">" )) {
1917
    ### if deletion of stop add extTer and number of new aa to alt
1918

1919
    $hgvs_notation->{alt} = $convert_to_three_letter ? substr($hgvs_notation->{alt}, 0, 3) : substr($hgvs_notation->{alt}, 0, 1);
8✔
1920
    print "stop loss check req for $hgvs_notation->{type}\n" if $DEBUG ==1;
8✔
1921

1922
    my $aa_til_stop =  $self->_stop_loss_extra_AA($hgvs_notation->{start}-1 );
8✔
1923
    ### use ? to show new stop not predicted
1924
    $aa_til_stop = "?" unless defined $aa_til_stop ;
8✔
1925

1926
    $hgvs_notation->{alt} .=  "extTer" . $aa_til_stop;
8✔
1927

1928
    # 2020-07-14
1929
    if($convert_to_three_letter && length($hgvs_notation->{ref}) >3 && $hgvs_notation->{type} eq "del" ) {
8✔
1930
      my $ref_pep_first = $convert_to_three_letter ? substr($hgvs_notation->{ref}, 0, 3) : substr($hgvs_notation->{ref}, 0, 1);
×
1931
      my $ref_pep_last  = $convert_to_three_letter ? substr($hgvs_notation->{ref}, -3, 3) : substr($hgvs_notation->{ref}, -1, 1);
×
1932
      $hgvs_notation->{'hgvs'} .=  $ref_pep_first . $hgvs_notation->{start} .  "_" .  $ref_pep_last . $hgvs_notation->{end} .$hgvs_notation->{alt} ;
×
1933
    }
1934
    else{
1935
      $hgvs_notation->{'hgvs'} .=  $hgvs_notation->{ref} . $hgvs_notation->{start} .  $hgvs_notation->{alt} ;
8✔
1936
    }
1937
  }
1938

1939
  elsif( $hgvs_notation->{type} eq "dup"){
1940
    if($hgvs_notation->{start} < $hgvs_notation->{end}){
×
1941
      ### list only first and last peptides in long duplicated string
1942
      my $ref_pep_first = $convert_to_three_letter ? substr($hgvs_notation->{alt}, 0, 3) : substr($hgvs_notation->{ref}, 0, 1);
×
1943
      my $ref_pep_last  = $convert_to_three_letter ? substr($hgvs_notation->{alt}, -3, 3) : substr($hgvs_notation->{ref}, -1, 1);
×
1944
      $hgvs_notation->{'hgvs'} .=  $ref_pep_first . $hgvs_notation->{start} .  "_" .  $ref_pep_last . $hgvs_notation->{end} ."dup";
×
1945
    }
1946
    else{
1947
      $hgvs_notation->{'hgvs'} .=  $hgvs_notation->{alt} . $hgvs_notation->{start} .  "dup" ;
×
1948
    }
1949

1950
    print "formating a dup  $hgvs_notation->{hgvs} \n" if $DEBUG==1;
×
1951
  }
1952

1953
  elsif($hgvs_notation->{type} eq ">"){
1954
    #### substitution
1955
    $hgvs_notation->{'hgvs'}  .=   $hgvs_notation->{ref}. $hgvs_notation->{start} .  $hgvs_notation->{alt};
34✔
1956
  }    
1957

1958
  elsif( $hgvs_notation->{type} eq "delins" || $hgvs_notation->{type} eq "ins" ){
1959

1960
    ## don't report other peptides after a stop is found
1961
    $hgvs_notation->{alt} =~ s/Ter\w+/Ter/ ;
26✔
1962

1963
    #### list first and last aa in reference only
1964
    my $ref_pep_first = $convert_to_three_letter ? substr($hgvs_notation->{ref}, 0, 3) : substr($hgvs_notation->{ref}, 0, 1);
26✔
1965
    my $ref_pep_last;
26✔
1966
    if(substr($hgvs_notation->{ref}, -1, 1) eq "X"){
26✔
1967
      $ref_pep_last ="Ter";
×
1968
    }
1969
    else{
1970
      $ref_pep_last  = $convert_to_three_letter ? substr($hgvs_notation->{ref}, -3, 3) : substr($hgvs_notation->{ref}, -1, 1);
26✔
1971
    }
1972

1973
    if($hgvs_notation->{ref} =~ /X$/) {
26✔
1974
      ### For stops & add extX & distance to next stop to alt pep
1975
      my $aa_til_stop =  $self->_stop_loss_extra_AA( $hgvs_notation->{start}-1, "loss");
×
1976
      if(defined  $aa_til_stop){  
×
1977
         $hgvs_notation->{alt} .="extTer" . $aa_til_stop;
×
1978
      }
1979
    }
1980

1981

1982
    if($hgvs_notation->{start} == $hgvs_notation->{end} && $hgvs_notation->{type} eq "delins"){       
26✔
1983
       $hgvs_notation->{'hgvs'} .= $ref_pep_first . $hgvs_notation->{start}  . $hgvs_notation->{type} . $hgvs_notation->{alt} ;
4✔
1984
    }
1985
    else{
1986
      ### correct ordering if needed
1987
      if($hgvs_notation->{start} > $hgvs_notation->{end}){
22✔
1988
        ( $hgvs_notation->{start}, $hgvs_notation->{end}) = ($hgvs_notation->{end}, $hgvs_notation->{start} );
16✔
1989
      }
1990

1991
      $hgvs_notation->{'hgvs'} .= $ref_pep_first . $hgvs_notation->{start}  . "_"  .  $ref_pep_last . $hgvs_notation->{end} . $hgvs_notation->{type} . $hgvs_notation->{alt} ;
22✔
1992
    }
1993
  }     
1994
  
1995
  elsif($hgvs_notation->{type} eq "fs"){
1996

1997
    if(defined $hgvs_notation->{alt} && $hgvs_notation->{alt} eq "Ter"){ ## stop gained
28✔
1998
      ## describe as substitution if stop occurs immediately
1999
      $hgvs_notation->{'hgvs'} .= $hgvs_notation->{ref} . $hgvs_notation->{start}  .  $hgvs_notation->{alt} ;
8✔
2000
    }
2001
    else{ 
2002
      ## not immediate stop - count aa until next stop
2003
      my $aa_til_stop =  $self->_stop_loss_extra_AA( $hgvs_notation->{start}-1, "fs");
20✔
2004

2005
      ### use ? to show new stop not predicted
2006
      $aa_til_stop = "?" unless defined $aa_til_stop; 
20✔
2007
        if (defined $hgvs_notation->{ref} && $hgvs_notation->{ref} eq "Ter"){ ## extTer
20✔
2008
          # extension length does not include the stop codon itself, so subtract 1 from the count
NEW
2009
          $aa_til_stop = $aa_til_stop eq '?' ? $aa_til_stop : $aa_til_stop - 1;
×
NEW
2010
          $hgvs_notation->{'hgvs'} .= $hgvs_notation->{ref} . $hgvs_notation->{start}  .  $hgvs_notation->{alt}."extTer$aa_til_stop" ;
×
2011
        }
2012
        else{ ## fsTer
2013
          $hgvs_notation->{'hgvs'} .= $hgvs_notation->{ref} . $hgvs_notation->{start}  .  $hgvs_notation->{alt}."fsTer$aa_til_stop" ;
20✔
2014
        } 
2015
    }
2016
  }
2017

2018
  elsif( $hgvs_notation->{type} eq "del"){
2019
    $hgvs_notation->{alt} =  "del"; 
12✔
2020
    if( length($hgvs_notation->{ref}) >3 ){
12✔
2021
      my $ref_pep_first = $convert_to_three_letter ? substr($hgvs_notation->{ref}, 0, 3) : substr($hgvs_notation->{ref}, 0, 1);
1✔
2022
      my $ref_pep_last  = $convert_to_three_letter ? substr($hgvs_notation->{ref}, -3, 3) : substr($hgvs_notation->{ref}, -1, 1);
1✔
2023
      $hgvs_notation->{'hgvs'} .=  $ref_pep_first . $hgvs_notation->{start} .  "_" .  $ref_pep_last . $hgvs_notation->{end} .$hgvs_notation->{alt} ;
1✔
2024
    }
2025
    else{
2026
      $hgvs_notation->{'hgvs'} .=  $hgvs_notation->{ref} . $hgvs_notation->{start} .  $hgvs_notation->{alt} ;
11✔
2027
    }       
2028
  }
2029

2030
  elsif($hgvs_notation->{start} ne $hgvs_notation->{end} ){
2031
    $hgvs_notation->{'hgvs'}  .=  $hgvs_notation->{ref} . $hgvs_notation->{start}  . "_" .  $hgvs_notation->{alt} . $hgvs_notation->{end} ;
×
2032
  }
2033

2034
  else{
2035
  #### default to substitution    
2036
    $hgvs_notation->{'hgvs'}  .=   $hgvs_notation->{ref}. $hgvs_notation->{start} .  $hgvs_notation->{alt};
4✔
2037
  }
2038

2039
  ### add paranthesis if asked to report in predicted format
2040
  $hgvs_notation->{'hgvs'} .= ")" if $prediction_format;
133✔
2041
  
2042
  if($DEBUG==1){ print "Returning protein format: $hgvs_notation->{'hgvs'}\n";}
133✔
2043
  return $hgvs_notation->{'hgvs'};
133✔
2044
}
2045

2046
### HGVS: get type of variation event in protein terms
2047
sub _get_hgvs_protein_type {
2048
  my $self = shift;
133✔
2049
  my $hgvs_notation = shift;
133✔
2050

2051
  if($DEBUG==1){ print "starting get_hgvs_protein_type \n";}
133✔
2052

2053
  if( frameshift($self) ){
133✔
2054
    $hgvs_notation->{type} = "fs";
28✔
2055
    return $hgvs_notation;
28✔
2056
  }
2057

2058
  if( defined $hgvs_notation->{ref} && defined $hgvs_notation->{alt} ){
105✔
2059
  ### Run type checks on peptides if available
2060
    $hgvs_notation->{ref} =~ s/\*/X/;
105✔
2061
    $hgvs_notation->{alt} =~ s/\*/X/;
105✔
2062

2063
    if($hgvs_notation->{ref} eq "-" || $hgvs_notation->{ref} eq "") {
105✔
2064
      $hgvs_notation->{type} = "ins";
16✔
2065
    }
2066
    elsif($hgvs_notation->{alt} eq "" || $hgvs_notation->{alt} eq "-") {
2067
      $hgvs_notation->{type} = "del";
14✔
2068
    }
2069
    elsif( length($hgvs_notation->{ref}) ==1 && length($hgvs_notation->{alt}) ==1 ) {
2070
      $hgvs_notation->{type} = ">";
65✔
2071
    }
2072
    elsif(
2073
      ((length($hgvs_notation->{alt}) >0 && length($hgvs_notation->{ref}) >0) &&
2074
      (length($hgvs_notation->{alt}) ne length($hgvs_notation->{ref})) )  ||
2075
      (length($hgvs_notation->{alt}) >1 && length($hgvs_notation->{ref}) >1)     ## not a substitution if >1 aa switched
2076
    ) {
2077
      $hgvs_notation->{type} = "delins";
10✔
2078
    }
2079
    else{
2080
      $hgvs_notation->{type} = ">";
×
2081
    }
2082
  }
2083
  else {
2084
    ### Cannot define type from peptides - check at DNA level
2085
    ### get allele length from dna seq & cds length
2086
    my ($ref_length, $alt_length ) = $self->_get_allele_length(); 
×
2087

2088
    if($alt_length >1  ){
×
2089
      if($hgvs_notation->{start} == ($hgvs_notation->{end} + 1) ){
×
2090
        ### convention for insertions - end one less than start
2091
        $hgvs_notation->{type} = "ins";
×
2092
      }
2093
      elsif( $hgvs_notation->{start} != $hgvs_notation->{end}  ){
2094
        $hgvs_notation->{type} = "delins";
×
2095
      }
2096
      else{
2097
        $hgvs_notation->{type} = ">";
×
2098
      } 
2099
    }
2100
    elsif($ref_length >1  ){
2101
      $hgvs_notation->{type} = "del"; 
×
2102
    }
2103
 
2104
    else{
2105
      #print STDERR "DEBUG ".$self->variation_feature->start."\n";
2106
      #warn "Cannot define protein variant type [$ref_length  - $alt_length]\n";
2107
    }
2108
  }
2109

2110
  return $hgvs_notation;
105✔
2111
}
2112

2113
### HGVS: get reference & alternative peptide 
2114
sub _get_hgvs_peptides {
2115
  my $self          = shift;
133✔
2116
  my $hgvs_notation = shift;
133✔
2117
  my $convert_to_three_letter = shift;
133✔
2118

2119
  if($hgvs_notation->{type} eq "fs"){
133✔
2120
    ### ensembl alt/ref peptides not the same as HGVS alt/ref - look up seperately
2121
    $hgvs_notation = $self->_get_fs_peptides($hgvs_notation); 
28✔
2122
    return undef unless defined $hgvs_notation->{type};   
28✔
2123
  }
2124
  elsif($hgvs_notation->{type} eq "ins" ){
2125

2126
    ### Check if bases directly after insertion match inserted sequence
2127
    $hgvs_notation = $self->_check_peptides_post_var($hgvs_notation);
16✔
2128

2129
    ### Check that inserted bases do not duplicate 3' reference sequence [set to type = dup and return if so]
2130
    $hgvs_notation = $self->_check_for_peptide_duplication($hgvs_notation) unless $hgvs_notation->{alt} =~/\*/;
16✔
2131
    return ($hgvs_notation) if $hgvs_notation->{type} eq "dup";              
16✔
2132

2133
    ### HGVS ref are peptides flanking insertion
2134
    my $min;
16✔
2135
    if($hgvs_notation->{start} < $hgvs_notation->{end}){
16✔
2136
        $min = $hgvs_notation->{start};
×
2137
    }
2138
    else{ $min = $hgvs_notation->{end};}
16✔
2139

2140
    $hgvs_notation->{ref} = $self->_get_surrounding_peptides(
2141
      $min, 
2142
      $hgvs_notation->{original_ref},
2143
      2
16✔
2144
    );
2145

2146
    return undef unless $hgvs_notation->{ref};
16✔
2147
  }
2148
  elsif($hgvs_notation->{type} eq "del" ){
2149
    ##check if bases directly after deletion match the deletion
2150
    $hgvs_notation = $self->_check_peptides_post_var($hgvs_notation);
14✔
2151
  }
2152

2153
  ### Convert peptide to 3 letter code as used in HGVS
2154
  if ( $convert_to_three_letter ){
133✔
2155
      $hgvs_notation->{ref}  = Bio::SeqUtils->seq3(Bio::PrimarySeq->new(-seq => $hgvs_notation->{ref}, -id => 'ref',  -alphabet => 'protein')) || "" unless ($hgvs_notation->{ref} eq "-");
133✔
2156
      $hgvs_notation->{alt}  = Bio::SeqUtils->seq3(Bio::PrimarySeq->new(-seq => $hgvs_notation->{alt}, -id => 'ref',  -alphabet => 'protein')) || "" unless ($hgvs_notation->{alt} eq "-");
133✔
2157
  }
2158
  $hgvs_notation->{alt} = "del" if $hgvs_notation->{alt} eq "-"; 
133✔
2159

2160
  ### handle special cases
2161
  if( start_lost($self) ){
133✔
2162
    #### handle initiator loss -> probably no translation => alt allele is '?'
2163
    $hgvs_notation->{alt}  = "?";    
4✔
2164
    $hgvs_notation->{type} = "";
4✔
2165
  }
2166

2167
  elsif(  $hgvs_notation->{type} eq "del"){ 
2168
    if( $hgvs_notation->{ref} =~/\w+/){
14✔
2169
      $hgvs_notation->{alt} = "del";
14✔
2170
    }
2171
    else{
2172
      $hgvs_notation = $self->_get_del_peptides($hgvs_notation);
×
2173
    }
2174
  } 
2175
  elsif($hgvs_notation->{type} eq "fs"){
2176
    ### only quote first ref peptide for frameshift
2177
    $hgvs_notation->{ref} = substr($hgvs_notation->{ref},0,3);
28✔
2178
  }
2179

2180
  ### 2012-08-31  - Ter now recommended in HGVS
2181
  if(defined $hgvs_notation->{ref}){ $hgvs_notation->{ref} =~ s/Xaa/Ter/g; }
133✔
2182
  if(defined $hgvs_notation->{alt}){ $hgvs_notation->{alt} =~ s/Xaa/Ter/g; }
133✔
2183

2184
  return ($hgvs_notation);           
133✔
2185
}
2186

2187
### HGVS:  remove common peptides/nucleotides from alt and ref strings & shift start/end accordingly 
2188
sub _clip_alleles {
2189
    
2190
  my $hgvs_notation = shift;
282✔
2191
    
2192
  my $check_alt   = $hgvs_notation->{alt};
282✔
2193
  my $check_ref   = $hgvs_notation->{ref};
282✔
2194
  my $check_start = $hgvs_notation->{start};
282✔
2195
  my $check_end   = $hgvs_notation->{end};
282✔
2196

2197
  ## cache this - if stop needed later
2198
  $hgvs_notation->{original_ref} = $hgvs_notation->{ref};
282✔
2199

2200
  ## store identical trimmed seq 
2201
  my $preseq = "";
282✔
2202
  print "can we clip :  $check_ref &  $check_alt\n" if $DEBUG ==1;
282✔
2203
  ### strip same bases from start of string
2204
  for (my $p =0; $p <length ($hgvs_notation->{ref}); $p++){
282✔
2205
    my $check_next_ref = substr( $check_ref, 0, 1);
258✔
2206
    my $check_next_alt = substr( $check_alt, 0, 1);
258✔
2207
    
2208
    ### stop re-created by variant - no protein change
2209
    if( defined $hgvs_notation->{'numbering'} &&
258✔
2210
        $hgvs_notation->{'numbering'} eq 'p' &&
2211
        $check_next_ref eq  "*" && $check_next_alt eq "*"){
2212
      $hgvs_notation->{type} = "=";
×
2213

2214
      return($hgvs_notation);
×
2215
    }
2216
    
2217
    if($check_next_ref eq  $check_next_alt){
258✔
2218
      $check_start++;
14✔
2219
      $check_ref  = substr( $check_ref, 1);
14✔
2220
      $check_alt  = substr( $check_alt, 1);
14✔
2221
      $preseq    .= $check_next_ref;
14✔
2222
    }
2223
    else{
2224
      last;
244✔
2225
    }
2226
  }
2227

2228
  my $len = length ($check_ref);
282✔
2229
  #### strip same bases from end of string
2230

2231
  for (my $q =0; $q < $len; $q++) {
282✔
2232
    my $check_next_ref = substr( $check_ref, -1, 1);
251✔
2233
    my $check_next_alt = substr( $check_alt, -1, 1);
251✔
2234
    if($check_next_ref eq  $check_next_alt){
251✔
2235
      chop $check_ref;
9✔
2236
      chop $check_alt;
9✔
2237
      $check_end--;
9✔
2238
    }
2239
    else{
2240
      last;
242✔
2241
    }
2242
  }
2243
  
2244
  ## ammend positions & ref/alt
2245
  $hgvs_notation->{alt}   = $check_alt;
282✔
2246
  $hgvs_notation->{ref}   = $check_ref;
282✔
2247
  $hgvs_notation->{start} = $check_start;
282✔
2248
  $hgvs_notation->{end}   = $check_end;
282✔
2249
  $hgvs_notation->{preseq} =   $preseq ;
282✔
2250

2251
  ### check if clipping suggests a type change 
2252
  
2253
  ## no protein change - use transcript level annotation 
2254
  if( defined $hgvs_notation->{'numbering'} &&
282✔
2255
        $hgvs_notation->{'numbering'} eq 'p'&&
2256
        $check_ref eq $check_alt) {
2257
      $hgvs_notation->{type} = "=";
×
2258
  }   
2259
  
2260
  ## re-set as > not delins
2261
  elsif( $check_ref ne "-" && 
2262
        length ($check_ref) == 1 && 
2263
        length ($check_alt) == 1 && 
2264
        $hgvs_notation->{alt} ne $hgvs_notation->{ref}) {
2265
      
2266
      $hgvs_notation->{type} = ">";
159✔
2267
  }
2268
  
2269
  
2270
  ### re-set as ins/dup not delins 
2271
  elsif(length ($check_ref) == 0 && length ($check_alt) >= 1){
2272
      ### re-set as dup not delins (ignore for protein as it is checked later on)
2273
      my $prev_str = substr($preseq, length($preseq) - length($check_alt));
40✔
2274
      if( defined $hgvs_notation->{'numbering'} &&
40✔
2275
          $hgvs_notation->{'numbering'} ne 'p' &&
2276
          $check_alt eq $prev_str) {
2277
            $hgvs_notation->{type} = "dup";
×
2278
            $hgvs_notation->{start} -= length($check_alt);
×
2279
      }
2280
    
2281
      ### re-set as ins not delins
2282
      else {
2283
        $hgvs_notation->{type} ="ins";
40✔
2284
      }
2285
  }
2286
  
2287
  ### re-set as del not delins  
2288
  elsif(length ($check_ref) >=1 && length ($check_alt) == 0){
2289
    $hgvs_notation->{type}  = "del" ;      
43✔
2290
  }
2291

2292
  print "clipped :  $check_ref &  $check_alt\n" if $DEBUG ==1;
282✔
2293

2294
  return $hgvs_notation;
282✔
2295
}
2296
    
2297

2298

2299

2300

2301
#### HGVS: check allele lengths to look for frameshifts
2302
sub _get_allele_length {
2303
  my $self       = shift;
×
2304
  my $ref_length = 0;
×
2305
  my $alt_length = 0;
×
2306

2307
  my $al_string = $self->allele_string();
×
2308
  my $ref_allele = (split/\//, $al_string)[0];
×
2309
  $ref_allele =~ s/\-//;
×
2310
  $ref_length = length $ref_allele;
×
2311

2312
  my $alt_allele = $self->variation_feature_seq();
×
2313
  $alt_allele =~ s/\-//;
×
2314
  $alt_length = length $alt_allele;
×
2315

2316
  return ($ref_length, $alt_length );  
×
2317
}
2318

2319
### HGVS: list first different peptide [may not be first changed codon]
2320
sub _get_fs_peptides {
2321
  my $self    = shift;
28✔
2322
  my $hgvs_notation = shift;
28✔
2323

2324
  ### get CDS with alt variant
2325
  my $alt_cds = $self->_get_alternate_cds();
28✔
2326
  return undef unless defined($alt_cds);
28✔
2327
  unless ($alt_cds->seq =~ /([ACGT-])+/) {
28✔
2328
    warn('Alternate CDS not found on seq region ' . $self->variation_feature->seq_region_name . '. Are you missing a synonyms file?');
×
2329
    return undef;
×
2330
  }
2331

2332
  #### get new translation
2333
  my $alt_trans = $alt_cds->translate()->seq();
28✔
2334

2335
  ### get changed end (currently in single letter AA coding)    
2336
  my $ref_trans  = $self->transcript_variation->_peptide;
28✔
2337

2338
  $ref_trans    .= "*";   ## appending ref stop for checking purposes 
28✔
2339
  
2340
  $hgvs_notation->{start} = $self->transcript_variation->translation_start() ;
28✔
2341

2342
  if( $hgvs_notation->{start} > length $alt_trans){ ## deletion of stop, no further AA in alt seq 
28✔
2343
    $hgvs_notation->{alt}  = "del";
×
2344
    $hgvs_notation->{type} = "del";
×
2345
    return $hgvs_notation;
×
2346
  }
2347

2348
  while ($hgvs_notation->{start} <= length $alt_trans){
28✔
2349
    ### frame shift may result in the same AA#
2350

2351
    $hgvs_notation->{ref} = substr($ref_trans, $hgvs_notation->{start}-1, 1);
28✔
2352
    $hgvs_notation->{alt} = substr($alt_trans, $hgvs_notation->{start}-1, 1);
28✔
2353

2354
    if($hgvs_notation->{ref} eq "*" && $hgvs_notation->{alt} eq "*"){
28✔
2355
      ### variation at stop codon, but maintains stop codon - set to synonymous
2356
      $hgvs_notation->{type} = "=";
×
2357
      return ($hgvs_notation);
×
2358
    }
2359

2360
    last if $hgvs_notation->{ref} ne $hgvs_notation->{alt};
28✔
2361
    $hgvs_notation->{start}++;
×
2362
  }
2363

2364
  return ($hgvs_notation);
28✔
2365
}
2366

2367
#### HGVS: if variant is an insertion, ref pep is initially "-", so seek AA before and after insertion
2368
sub _get_surrounding_peptides {
2369
  my $self    = shift;
46✔
2370
  my $ref_pos = shift; 
46✔
2371
  my $original_ref = shift;
46✔
2372
  my $length  = shift;
46✔
2373

2374
  my $ref_trans  = $self->transcript_variation->_peptide();
46✔
2375
  $ref_trans .= $original_ref
46✔
2376
    if defined $original_ref && $original_ref =~ /^\*/;
2377

2378
  ## can't find peptide after the end
2379
  return if length($ref_trans) <=  $ref_pos ;
46✔
2380

2381
  my $ref_string;
33✔
2382
  if(defined $length) {
33✔
2383
    $ref_string = substr($ref_trans, $ref_pos-1, $length);
16✔
2384
  }
2385
  else{
2386
    $ref_string = substr($ref_trans, $ref_pos-1 );
17✔
2387
  }
2388

2389
  return ($ref_string);
33✔
2390
}
2391

2392

2393
#### HGVS: alternate CDS needed to check for new stop when variant disrupts 'reference' stop
2394
sub _get_alternate_cds {
2395
    
2396
  my $self = shift;
861✔
2397

2398
  ### get reference sequence
2399
  my $reference_cds_seq = $self->transcript_variation->_translateable_seq();
861✔
2400
  
2401
  my $tv = $self->transcript_variation;
861✔
2402
  my $vf = $tv->variation_feature;
861✔
2403
  my $tr = $tv->transcript;
861✔
2404
  my $shifting_offset = (defined($self->{shift_hash}) && defined($self->{shift_hash}->{shift_length})) ? $self->{shift_hash}->{shift_length} : 0;
861✔
2405
  
2406
  return undef unless defined($tv->cds_start(undef, $tr->strand * $shifting_offset)) && defined($tv->cds_end(undef, $tr->strand * $shifting_offset));
861✔
2407

2408
  ### get sequences upstream and downstream of variant
2409
  my $upstream_seq   =  substr($reference_cds_seq, 0, ($tv->cds_start(undef, $tr->strand * $shifting_offset) -1) );
861✔
2410
  my $downstream_seq =  substr($reference_cds_seq, ($tv->cds_end(undef, $tr->strand * $shifting_offset)) );
861✔
2411
  return undef unless defined($downstream_seq) && defined($upstream_seq);
861✔
2412
  
2413
  ### fix alternate allele if deletion or on opposite strand
2414
  my $alt_allele  = $self->variation_feature_seq();
861✔
2415
  $alt_allele  =~ s/\-//;
861✔
2416
  if($alt_allele && $vf->strand() != $tr->strand()){    
861✔
2417
    reverse_comp(\$alt_allele) ;
371✔
2418
  }
2419

2420
  ### build alternate seq
2421
  my $alternate_seq  = $upstream_seq . $alt_allele . $downstream_seq ;
861✔
2422
  $alternate_seq  = $self->_trim_incomplete_codon($alternate_seq );
861✔
2423

2424
  ### create seq obj with alternative allele in the CDS sequence
2425
  my $alt_cds =Bio::PrimarySeq->new(-seq => $alternate_seq,  -id => 'alt_cds', -alphabet => 'dna');
861✔
2426

2427
  ### append UTR if available as stop may be disrupted
2428
  my $utr = $self->transcript_variation->_three_prime_utr();
861✔
2429

2430
  if (defined $utr) {
861✔
2431
  ### append the UTR to the alternative CDS 
2432
    $alt_cds->seq($alt_cds->seq() . $utr->seq()); 
722✔
2433
  }
2434
  else{
2435
   ##warn "No UTR available for alternate CDS\n";
2436
  }
2437

2438
  return $alt_cds;
861✔
2439
}
2440

2441
### HGVS: if inserted string is identical to 3' reference sequence, describe as duplication
2442
sub _check_for_peptide_duplication {    
2443
  my $self = shift;
12✔
2444
  my $hgvs_notation = shift;
12✔
2445

2446
  ##### get reference sequence
2447
  my $reference_cds_seq = $self->transcript_variation->_translateable_seq();
12✔
2448

2449
  my $reference_cds = Bio::PrimarySeq->new(-seq => $reference_cds_seq,  -id => 'alt_cds', -alphabet => 'dna');
12✔
2450
  my $reference_trans = $reference_cds->translate()->seq();
12✔
2451

2452
  ##### get sequence upstream of variant - use hgvs start; may have been shifted
2453
  my $upstream_trans  = substr($reference_trans, 0, ($hgvs_notation->{'start'} -1) );
12✔
2454
  print "Checking for peptide duplication: $hgvs_notation->{alt} vs $upstream_trans  $hgvs_notation->{preseq} \n" if $DEBUG ==1;
12✔
2455

2456
  $upstream_trans  .= $hgvs_notation->{preseq} if defined $hgvs_notation->{preseq}; ## add back on anything previously chopped off ref allele
12✔
2457

2458
  ## Test whether alt peptide matches the reference sequence just before the variant
2459
  my $test_new_start = $hgvs_notation->{'start'} - length($hgvs_notation->{'alt'}) -1 ;
12✔
2460

2461
  if( (length($upstream_trans) >=  $test_new_start + length($hgvs_notation->{'alt'}) ) && $test_new_start  >=0){
12✔
2462
    my $test_seq       =  substr($upstream_trans, $test_new_start, length($hgvs_notation->{'alt'}));
12✔
2463

2464
    if ( $test_new_start >= 0 && $test_seq eq $hgvs_notation->{alt}) {           
12✔
2465
      $hgvs_notation->{type}   = 'dup';
×
2466
      $hgvs_notation->{end}    = $hgvs_notation->{start} -1;
×
2467
      $hgvs_notation->{start} -= length($hgvs_notation->{alt});
×
2468

2469
      ## convert to 3 letter code
2470
      $hgvs_notation->{alt}  = Bio::SeqUtils->seq3(Bio::PrimarySeq->new(-seq => $hgvs_notation->{alt}, -id => 'ref',  -alphabet => 'protein')) || "";
×
2471
    }
2472
  }
2473
  return $hgvs_notation;
12✔
2474
}
2475

2476
#### HGVS: if a stop is lost, seek the next in transcript & count number of extra AA's
2477
sub _stop_loss_extra_AA{
2478

2479
  my $self        = shift;
28✔
2480
  my $ref_var_pos = shift;  ### first effected AA - supply for frameshifts
28✔
2481
  my $test        = shift;
28✔
2482

2483
  return undef unless $ref_var_pos;
28✔
2484

2485
  my $extra_aa;
28✔
2486

2487
  ### get the sequence with variant added
2488
  my $alt_cds   = $self->_get_alternate_cds();
28✔
2489
  return undef unless defined($alt_cds);
28✔
2490
  
2491
  ### get new translation
2492
  my $alt_trans = $alt_cds->translate();
28✔
2493

2494
  my $ref_temp  =  $self->transcript_variation->_peptide();
28✔
2495
  my $ref_len = length($ref_temp);
28✔
2496
  
2497
  if($DEBUG==1){ 
28✔
2498
    print "alt translated:\n" . $alt_trans->seq() . "\n";
×
2499
    print "ref translated:\n$ref_temp\n";;
×
2500
  }
2501
  
2502
  #### Find the number of residues that are translated until a termination codon is encountered
2503
  if ($alt_trans->seq() =~ m/\*/) {
28✔
2504
    if($DEBUG==1){print "Got $+[0] aa before stop, var event at $ref_var_pos \n";}
26✔
2505

2506
    if(defined $test && $test eq "fs" ){
26✔
2507
      ### frame shift - count from first AA effected by variant to stop
2508
      $extra_aa = $+[0] - $ref_var_pos;
20✔
2509
      if($DEBUG==1){ print "Stop change ($test): found $extra_aa amino acids before fs stop [ $+[0] - peptide ref_start: $ref_var_pos )]\n";}
20✔
2510
    }
2511
  
2512
    else{
2513
      $extra_aa = $+[0]  - 1 - $ref_len;
6✔
2514
      if($DEBUG==1){ print "Stop change (non-fs): found $extra_aa amino acids before next stop [ $+[0] - 1 -normal stop $ref_len)]\n";}        
6✔
2515
    }
2516
  }
2517
  
2518
  # A special case is if the first aa is a stop codon => don't display the number of residues until the stop codon
2519
  if(defined $extra_aa && $extra_aa >0){ 
28✔
2520
    return $extra_aa ;
26✔
2521
  }
2522
  else{ 
2523
    #warn "No stop found in alternate sequence\n";
2524
    return undef;
2✔
2525
  }
2526
}
2527

2528
## doing this to stop final incomplete codons being guessed
2529
sub _trim_incomplete_codon{
2530
  my $self = shift;
861✔
2531
  my $seq  = shift;
861✔
2532

2533
  return undef unless $seq;
861✔
2534

2535
  my $full_length = length $seq;
861✔
2536
  my $keep_length = $full_length -  $full_length % 3;
861✔
2537
  return $seq if $full_length = $keep_length ;
861✔
2538

2539
  return substr($seq, 0, $keep_length);
×
2540
}
2541

2542

2543
## This is used for rare in-frame deletions removing an intron and part of both surrounding exons
2544
sub _get_del_peptides{
2545

2546
  my $self    = shift;
×
2547
  my $hgvs_notation = shift;
×
2548

2549
  ### get CDS with alt variant
2550
  my $alt_cds = $self->_get_alternate_cds();
×
2551
  return undef unless defined($alt_cds);
×
2552

2553
  #### get new translation
2554
  my $start = $self->transcript_variation->translation_start() - 1;
×
2555
  my $alt = substr($alt_cds->translate()->seq(), $start );
×
2556
  $hgvs_notation->{alt} = (split/\*/, $alt)[0];
×
2557

2558
  ### get changed end (currently in single letter AA coding)    
2559
  $hgvs_notation->{ref}  = substr($self->transcript->translate()->seq(), $start );
×
2560

2561
  $hgvs_notation->{start} = $self->transcript_variation->translation_start() ;
×
2562

2563
  $hgvs_notation = _clip_alleles($hgvs_notation);
×
2564

2565
  ## switch to 3 letter AA coding
2566
  $hgvs_notation->{alt}  = Bio::SeqUtils->seq3(Bio::PrimarySeq->new(-seq => $hgvs_notation->{alt}, -id => 'ref',  -alphabet => 'protein')) || "";
×
2567
  $hgvs_notation->{ref}  = Bio::SeqUtils->seq3(Bio::PrimarySeq->new(-seq => $hgvs_notation->{ref}, -id => 'ref',  -alphabet => 'protein')) || "";
×
2568
  return $hgvs_notation;
×
2569
}
2570

2571
## HGVS counts from first different peptide, 
2572
## so check the sequence post variant and increment accordingly
2573
sub _check_peptides_post_var{
2574
  my $self          = shift;
30✔
2575
  my $hgvs_notation = shift;
30✔
2576

2577
  ## check peptides after deletion 
2578
  my $post_pos = $hgvs_notation->{end}+1;
30✔
2579
  my $post_seq = $self->_get_surrounding_peptides(
2580
    $post_pos,
2581
    $hgvs_notation->{original_ref}
2582
  );
30✔
2583

2584
  ## if a stop is deleted and no sequence is available beyond to check, return
2585
  return $hgvs_notation unless defined $post_seq;
30✔
2586

2587
  $hgvs_notation = _shift_3prime($hgvs_notation, $post_seq);
17✔
2588
  return $hgvs_notation;
17✔
2589
}
2590

2591
## HGVS aligns changes 3' (alt string may look different at transcript level to genomic level)
2592
## AAC[TG]TAT => AACT[GT]AT
2593
## TTA[GGG]GGTTTA =>TTAGG[GGG]TTTA
2594

2595
sub _shift_3prime{
2596

2597
  my $hgvs_notation = shift;
17✔
2598
  my $post_seq      = shift;
17✔
2599

2600
  my $seq_to_check;
17✔
2601
  if( $hgvs_notation->{type} eq 'ins'){
17✔
2602
    $seq_to_check = $hgvs_notation->{alt};
16✔
2603
  }
2604
  elsif ($hgvs_notation->{type} eq 'del'){
2605
    $seq_to_check = $hgvs_notation->{ref};
1✔
2606
  }
2607
  else{
2608
    return $hgvs_notation;
×
2609
  }
2610

2611
  ## return if nothing to check
2612
  return $hgvs_notation unless defined $post_seq && defined $seq_to_check;
17✔
2613

2614
  ## get length of pattern to check 
2615
  my $deleted_length = (length $seq_to_check);
17✔
2616
  
2617
  # warn "Checking $seq_to_check v $post_seq\n";
2618
  ## move along sequence after deletion looking for match to start of deletion
2619
  for (my $n = 0; $n<= (length($post_seq) - $deleted_length); $n++ ){
17✔
2620

2621
    ## check each position in deletion/ following seq for match
2622
    my $check_next_del  = substr( $seq_to_check, 0, 1);
17✔
2623
    my $check_next_post = substr( $post_seq, $n, 1);
17✔
2624

2625
    if($check_next_del eq $check_next_post){
17✔
2626

2627
      ## move position of deletion along
2628
      $hgvs_notation->{start}++;
×
2629
      $hgvs_notation->{end}++;
×
2630
          
2631
      ## modify deleted sequence - remove start & append to end
2632
      $seq_to_check = substr($seq_to_check,1);
×
2633
      $seq_to_check .= $check_next_del;
×
2634
    }
2635
    else{
2636
      last;            
17✔
2637
    }
2638
  }
2639
  ## set new HGVS string
2640
  $hgvs_notation->{alt} = $seq_to_check if $hgvs_notation->{type} eq 'ins';
17✔
2641
  $hgvs_notation->{ref} = $seq_to_check if $hgvs_notation->{type} eq 'del';
17✔
2642

2643
  return $hgvs_notation;
17✔
2644
}
2645
=head
2646
# We haven't implemented support for these methods yet
2647

2648
sub hgvs_rna {
2649
    return _hgvs_generic(@_,'rna');
2650
}
2651

2652
sub hgvs_mitochondrial {
2653
    return _hgvs_generic(@_,'mitochondrial');
2654
}
2655

2656
=cut
2657

2658
sub _hgvs_generic {
2659
  my $self = shift;
6✔
2660
  my $reference = pop;
6✔
2661
  my $notation = shift;
6✔
2662

2663
  #The rna and mitochondrial modes have not yet been implemented, so return undef in case we get a call to these
2664
  return undef if ($reference =~ m/rna|mitochondrial/);
6✔
2665
  
2666
  my $sub = qq{hgvs_$reference};
6✔
2667
  
2668
  $self->{$sub} = $notation if defined $notation;
6✔
2669
  
2670
  unless ($self->{$sub}) {
6✔
2671

2672
    # Use the transcript this VF is on as the reference feature
2673
    my $reference_feature = $self->feature;
3✔
2674
    # If we want genomic coordinates, the reference_feature should actually be the slice for the underlying seq_region
2675
    $reference_feature = $reference_feature->slice->seq_region_Slice if ($reference eq 'genomic');
3✔
2676

2677
    # Calculate the HGVS notation on-the-fly and pass it to the TranscriptVariation in order to distribute the result to the other alleles
2678
    my $tv = $self->base_variation_feature_overlap;
3✔
2679
    my $vf = $self->base_variation_feature;
3✔
2680

2681
    $tv->$sub($vf->get_all_hgvs_notations($reference_feature,substr($reference,0,1),undef,undef,$tv));
3✔
2682
  }
2683
  
2684
  return $self->{$sub};
6✔
2685
}
2686

2687

2688
### HGVS: move variant to transcript slice
2689
sub _var2transcript_slice_coords{
2690
  my ($self, $tr, $tv, $vf) = @_;
216✔
2691

2692
  $tv ||= $self->base_variation_feature_overlap;
216✔
2693
  $tr ||= $tv->transcript;
216✔
2694
  $vf ||= $tv->base_variation_feature;
216✔
2695
  
2696
  # what we want is VF coords relative to transcript feature slice
2697
  my ($tr_start, $tr_end) = ($tr->start, $tr->end);
216✔
2698
  my ($vf_start, $vf_end);
216✔
2699

2700
  # same slice, easy and we don't need to use transfer
2701
  if($NO_TRANSFER || $tr->slice eq $vf->slice) {
216✔
2702

2703
    # different transform depending on transcript strand
2704
    if($tr->strand < 1) {
215✔
2705

2706
      # note we switch start/end here
2707
      # this also works in the case of insertions thankfully
2708
      ($vf_start, $vf_end) = map {($tr_end - $_) + 1} ($vf->end, $vf->start) unless $vf->{shifted_flag};
98✔
2709
      ($vf_start, $vf_end) = map {($tr_end - $_) + 1} ($vf->{unshifted_end}, $vf->{unshifted_start}) if $vf->{shifted_flag};
98✔
2710
    }
2711
    else {
2712
      ($vf_start, $vf_end) = map {($_ - $tr_start) + 1} ($vf->start, $vf->end) unless $vf->{shifted_flag};
117✔
2713
      ($vf_start, $vf_end) = map {($_ - $tr_start) + 1} ($vf->{unshifted_start}, $vf->{unshifted_end}) if $vf->{shifted_flag};
117✔
2714
    }
2715
  }
2716

2717
  # different slices used to fetch features
2718
  # have to use transfer for safety
2719
  else {
2720
    my $tr_vf = $vf->transfer($self->_transcript_feature_Slice($tr));
1✔
2721
    return undef unless $tr_vf;
1✔
2722
    ($vf_start, $vf_end) = ($tr_vf->start, $tr_vf->end);
1✔
2723
  }
2724

2725
  # Check for overlap before clamping
2726
  my $tr_length = $tr_end - $tr_start + 1;
216✔
2727

2728
  # Variant is entirely before or after transcript
2729
  return undef if (($vf_start < 1 && $vf_end < 1) || ($vf_start > $tr_length && $vf_end > $tr_length));
216✔
2730

2731
  # Clamp coordinates to transcript boundaries
2732
  my $clamped_start = ($vf_start < 1 ? 1 : $vf_start > $tr_length ? $tr_length : $vf_start);
216✔
2733
  my $clamped_end   = ($vf_end   < 1 ? 1 : $vf_end   > $tr_length ? $tr_length : $vf_end);
216✔
2734

2735
  return ($clamped_start, $clamped_end, $self->_transcript_feature_Slice($tr));
216✔
2736
}
2737

2738

2739

2740
### HGVS: get variant position in transcript 
2741

2742
# intron: 
2743
# If the position is in an intron, the boundary position of the closest exon and 
2744
# a + or - offset into the intron is returned.
2745
# Ordered by genome forward not 5' -> 3'
2746

2747
# upstream:
2748
# If the position is 5' of the start codon, it is reported relative to the start codon 
2749
# (-1 being the last nucleotide before the 'A' of ATG).
2750

2751
#downstream:
2752
# If the position is 3' pf the stop codon, it is reported with a '*' prefix and the offset 
2753
# from the start codon (*1 being the first nucleotide after the last position of the stop codon)
2754

2755
sub _get_cDNA_position {
2756

2757
  my $self     = shift;
271✔
2758
  my $position = shift; ### start or end of variation
271✔
2759

2760
  my $tv         = $self->base_variation_feature_overlap;
271✔
2761
  my $transcript = $tv->transcript();
271✔
2762
  my $strand     = $transcript->strand();    
271✔
2763

2764
  #### TranscriptVariation start/stop coord relative to transcript 
2765
  #### Switch to chromosome coordinates taking into account strand
2766
  $position = ( $strand > 0 ? 
271✔
2767
    ( $transcript->start() + $position - 1 )  :   
2768
    ( $transcript->end()   - $position + 1));
2769

2770
  # Get all exons sorted in positional order
2771
  my $exons = $tv->_sorted_exons();
271✔
2772

2773
  my $n_exons = scalar(@$exons);
271✔
2774

2775
  my $cdna_position;
271✔
2776
  # Loop over the exons and get the coordinates of the variation in exon+intron notation
2777
  for (my $i=0; $i<$n_exons; $i++) {
271✔
2778

2779
    my $exon = $exons->[$i];
1,380✔
2780
    my ($exon_start, $exon_end) = ($exon->{start}, $exon->{end});
1,380✔
2781

2782
    # Skip if the start point is beyond this exon
2783
    next if ($position > $exon_end);
1,380✔
2784

2785

2786
    # EXONIC:  If the start coordinate is within this exon
2787
    if ($position >= $exon_start) {
271✔
2788
      # Get the cDNA start coordinate of the exon and add the number of nucleotides from the exon boundary to the variation
2789
      # If the transcript is in the opposite direction, count from the end instead
2790
      $cdna_position = $self->_exon_cdna_start($exon, $transcript) + (
215✔
2791
        $strand > 0 ? 
2792
        ( $position - $exon_start ) : 
2793
        ( $exon_end - $position ) 
2794
      );
2795
      last;  #### last exon checked
215✔
2796
    }
2797

2798
    ## INTRONIC
2799
    # Else the start coordinate is between this exon and the previous one, determine which one is closest and get coordinates relative to that one
2800
    else {
2801

2802
      my $prev_exon = $exons->[$i-1];
56✔
2803

2804
      my $updist   = ($position - $prev_exon->{end});
56✔
2805
      $updist =~ s/\-//; ## avoid problems with incomplete transcripts
56✔
2806
      my $downdist = ($exon_start - $position);
56✔
2807
      $downdist =~ s/\-//; ## avoid problems with incomplete transcripts
56✔
2808

2809
      # If the distance to the upstream exon is the shortest, or equal and in the positive orientation, use that
2810
      if ($updist < $downdist || ($updist == $downdist && $strand >= 0)) {
56✔
2811
        
2812
        # If the orientation is reversed, we should use the cDNA start and a '-' offset
2813
        $cdna_position = (
25✔
2814
          $strand >= 0 ? 
2815
          $self->_exon_cdna_end($prev_exon, $transcript).'+' : 
2816
          $self->_exon_cdna_start($prev_exon, $transcript).'-'
2817
        ).$updist;
2818
      }
2819
      # Else if downstream is shortest...
2820
      else {
2821
        # If the orientation is reversed, we should use the cDNA end and a '+' offset
2822
        $cdna_position = (
31✔
2823
          $strand >= 0 ?
2824
          $self->_exon_cdna_start($exon, $transcript).'-' : 
2825
          $self->_exon_cdna_end($exon, $transcript).'+'
2826
        ).$downdist;
2827
      }
2828

2829
      last; ## last exon checked
56✔
2830
    }
2831
  }
2832

2833
  ## this should not happen; potential refseq oddness
2834
  return undef unless $cdna_position; 
271✔
2835
 
2836
  # Shift the position to make it relative to the start & stop codons
2837
  my $start_codon  =  $transcript->cdna_coding_start();
271✔
2838
  my $stop_codon   =  $transcript->cdna_coding_end();
271✔
2839

2840
  # Disassemble the cDNA coordinate into the exon and intron parts
2841
  ### just built this now taking it appart again
2842
  my ($cdna_coord, $intron_offset) = $cdna_position =~ m/([0-9]+)([\+\-][0-9]+)?/;
271✔
2843
  
2844

2845
  # Start by correcting for the stop codon
2846
  if (defined($stop_codon) ){
271✔
2847

2848
    if($cdna_coord > $stop_codon) {
236✔
2849
      # Get the offset from the stop codon
2850
      $cdna_coord -= $stop_codon;
11✔
2851
      # Prepend a * to indicate the position is in the 3' UTR
2852
      $cdna_coord = '*' . $cdna_coord;
11✔
2853
    }
2854
    elsif ( $cdna_coord eq $stop_codon && defined $intron_offset) {
2855
      $intron_offset =~ s/\+//g;
×
2856
      $cdna_coord ='' ;
×
2857

2858
      # Prepend a * to indicate the position is in the 3' UTR
2859
      $cdna_coord = '*' . $cdna_coord;
×
2860
    }
2861
  }
2862
  if (defined($start_codon) && $cdna_coord  !~/\*/) {
271✔
2863
    
2864
    # If the position is beyond the start codon, add 1 to get the correct offset
2865
    $cdna_coord += ($cdna_coord >= $start_codon);
225✔
2866
    # Subtract the position of the start codon
2867
    $cdna_coord -= $start_codon;
225✔
2868
  }
2869
  else{
2870
          print "Checking non-coding transcript\n" if $DEBUG==1;
46✔
2871
  }
2872

2873
  # Re-assemble the cDNA position  [ return exon num & offset & direction for intron eg. 142+363] 
2874
  $cdna_position = $cdna_coord . (defined($intron_offset) ? $intron_offset : '');
271✔
2875

2876
  return $cdna_position;
271✔
2877
}
2878

2879
# $exon->cdna_start doesn't cache
2880
# so use our own method that does
2881
sub _exon_cdna_start {
2882
  my ($self, $exon, $tr) = @_;
240✔
2883

2884
  my $tr_stable_id = $tr->stable_id;
240✔
2885
  my $fc = $exon->{_variation_effect_feature_cache}->{$tr_stable_id} ||= {};
240✔
2886

2887
  if(!exists($fc->{_cdna_start})) {
240✔
2888
    $fc->{_cdna_start} = $exon->cdna_start($tr);
179✔
2889
  }
2890

2891
  return $fc->{_cdna_start};
240✔
2892
}
2893

2894
sub _exon_cdna_end {
2895
  my ($self, $exon, $tr) = @_;
31✔
2896

2897
  my $tr_stable_id = $tr->stable_id;
31✔
2898
  my $fc = $exon->{_variation_effect_feature_cache}->{$tr_stable_id} ||= {};
31✔
2899

2900
  if(!exists($fc->{_cdna_end})) {
31✔
2901
    $fc->{_cdna_end} = $exon->cdna_end($tr);
26✔
2902
  }
2903

2904
  return $fc->{_cdna_end};
31✔
2905
}
2906

2907
# same for $transcript->feature_Slice
2908
# need to be careful here in case the transcript has moved slice
2909
# you never know!
2910
sub _transcript_feature_Slice {
2911
  my ($self, $tr) = @_;
217✔
2912

2913
  my $fc = $tr->{_variation_effect_feature_cache} ||= {};
217✔
2914

2915
  # check that we haven't moved slice
2916
  my $curr_slice_ref = sprintf('%s', $tr->slice());
217✔
2917
  my $prev_slice_ref = $fc->{slice_ref};
217✔
2918

2919
  if(
217✔
2920
    !exists($fc->{feature_Slice}) ||
2921
    $fc->{slice_ref} && $fc->{slice_ref} ne $curr_slice_ref
2922
  ) {
2923

2924
    # log the reference of this slice
2925
    $fc->{slice_ref} = $curr_slice_ref;
216✔
2926
    $fc->{feature_Slice} = $tr->feature_Slice();
216✔
2927
  }
2928

2929
  return $fc->{feature_Slice};
217✔
2930
}
2931

2932

2933
1;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc