ensembl-hive  2.7.0
AssemblyExceptionFeatureAdaptor.pm
Go to the documentation of this file.
1 =head1 LICENSE
2 
3 See the NOTICE file distributed with this work for additional information
4 regarding copyright ownership.
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 
34 
35 =head1 SYNOPSIS
36 
37  my $assembly_exception_feature_adaptor =
38  $database_adaptor->get_AssemblyExceptionFeatureAdaptor();
39 
40  @assembly_exception_features =
41  $assembly_exception_feature_adaptor->fetch_all_by_Slice($slice);
42 
43 =head1 DESCRIPTION
44 
45 Assembly Exception Feature Adaptor - database access for assembly
46 exception features.
47 
48 =head1 METHODS
49 
50 =cut
51 
52 package Bio::EnsEMBL::DBSQL::AssemblyExceptionFeatureAdaptor;
53 
54 use strict;
55 use warnings;
56 no warnings qw(uninitialized);
57 
61 use Bio::EnsEMBL::Utils::Exception qw(throw warning);
63 
65 
66 # set the number of slices you'd like to cache
67 our $ASSEMBLY_EXCEPTION_FEATURE_CACHE_SIZE = 100;
68 
69 =head2 new
70 
71  Arg [1] : list of args @args
72  Superclass constructor arguments
73  Example : none
74  Description: Constructor which just initializes internal cache structures
76  Exceptions : none
77  Caller : implementing subclass constructors
78  Status : Stable
79 
80 =cut
81 
82 sub new {
83  my $caller = shift;
84  my $class = ref($caller) || $caller;
85 
86  my $self = $class->SUPER::new(@_);
87 
88  # initialize an LRU cache for slices
89  my %cache;
90  tie(%cache, 'Bio::EnsEMBL::Utils::Cache',
91  $ASSEMBLY_EXCEPTION_FEATURE_CACHE_SIZE);
92 
93  $self->{'_aexc_slice_cache'} = \%cache;
94 
95  return $self;
96 }
97 
98 =head2 fetch_all
99 
100  Arg [1] : none
101  Example : my @axfs = @{$axfa->fetch_all()};
102  Description: Retrieves all assembly exception features which are in the
103  database and builds internal caches of the features.
104  Returntype : reference to list of Bio::EnsEMBL::AssemblyExceptionFeatures
105  Exceptions : none
106  Caller : fetch_by_dbID, fetch_by_Slice
107  Status : Stable
108 
109 =cut
110 
111 sub fetch_all {
112  my $self = shift;
113 
114  # this is the "global" cache for all assembly exception features in the db
115  if(defined($self->{'_aexc_cache'})) {
116  return $self->{'_aexc_cache'};
117  }
118 
119  my $statement = qq(
120  SELECT ae.assembly_exception_id,
121  ae.seq_region_id,
122  ae.seq_region_start,
123  ae.seq_region_end,
124  ae.exc_type,
125  ae.exc_seq_region_id,
126  ae.exc_seq_region_start,
127  ae.exc_seq_region_end,
128  ae.ori
129  FROM assembly_exception ae,
130  coord_system cs,
131  seq_region sr
132  WHERE cs.species_id = ?
133  AND sr.coord_system_id = cs.coord_system_id
134  AND sr.seq_region_id = ae.seq_region_id);
135 
136  my $sth = $self->prepare($statement);
137 
138  $sth->bind_param( 1, $self->species_id(), SQL_INTEGER );
139 
140  $sth->execute();
141 
142  my ($ax_id, $sr_id, $sr_start, $sr_end,
143  $x_type, $x_sr_id, $x_sr_start, $x_sr_end, $ori);
144 
145  $sth->bind_columns(\$ax_id, \$sr_id, \$sr_start, \$sr_end,
146  \$x_type, \$x_sr_id, \$x_sr_start, \$x_sr_end, \$ori);
147 
148  my @features;
149  my $sa = $self->db()->get_SliceAdaptor();
150 
151  $self->{'_aexc_dbID_cache'} = {};
152 
153  while($sth->fetch()) {
154  my $slice = $sa->fetch_by_seq_region_id($sr_id);
155  my $x_slice = $sa->fetch_by_seq_region_id($x_sr_id);
156 
157  # each row creates TWO features, each of which has alternate_slice
158  # pointing to the "other" one
159 
160 
162  ('-dbID' => $ax_id,
163  '-start' => $sr_start,
164  '-end' => $sr_end,
165  '-strand' => 1,
166  '-adaptor' => $self,
167  '-slice' => $slice,
168  '-alternate_slice' => $x_slice->sub_Slice($x_sr_start, $x_sr_end),
169  '-type' => $x_type);
170 
171  push @features, $a;
172  $self->{'_aexc_dbID_cache'}->{$ax_id} = $a;
173 
175  ('-dbID' => $ax_id,
176  '-start' => $x_sr_start,
177  '-end' => $x_sr_end,
178  '-strand' => 1,
179  '-adaptor' => $self,
180  '-slice' => $x_slice,
181  '-alternate_slice' => $slice->sub_Slice($sr_start, $sr_end),
182  '-type' => "$x_type REF" );
183  }
184 
185  $sth->finish();
186 
187  $self->{'_aexc_cache'} = \@features;
188 
189  return \@features;
190 }
191 
192 
193 =head2 fetch_by_dbID
194 
195  Arg [1] : int $dbID
196  Example : my $axf = $axfa->fetch_by_dbID(3);
197  Description: Retrieves a single assembly exception feature via its internal
198  identifier. Note that this only retrieves one of the two
199  assembly exception features which are represented by a single
200  row in the assembly_exception table.
202  Exceptions : none
203  Caller : general
204  Status : Stable
205 
206 =cut
207 
208 sub fetch_by_dbID {
209  my $self = shift;
210  my $dbID = shift;
211 
212  if(!exists($self->{'_aexc_dbID_cache'})) {
213  # force loading of cache
214  $self->fetch_all();
215  }
216 
217  return $self->{'_aexc_dbID_cache'}->{$dbID};
218 }
219 
220 
221 =head2 fetch_all_by_Slice
222 
223  Arg [1] : Bio::EnsEMBL::Slice $slice
224  Example : my @axfs = @{$axfa->fetch_all_by_Slice($slice)};
225  Description: Retrieves all assembly exception features which overlap the
226  provided slice. The returned features will be in coordinate
227  system of the slice.
228  Returntype : reference to list of Bio::EnsEMBL::AssemblyException features
229  Exceptions : none
230  Caller : Feature::get_all_alt_locations, general
231  Status : Stable
232 
233 =cut
234 
235 sub fetch_all_by_Slice {
236  my $self = shift;
237  my $slice = shift;
238 
239  my $key= uc($slice->name());
240 
241  # return features from the slice cache if present
242  if(exists($self->{'_aexc_slice_cache'}->{$key})) {
243  return $self->{'_aexc_slice_cache'}->{$key};
244  }
245 
246  my $all_features = $self->fetch_all();
247 
248  my $mcc = $self->db()->get_MetaCoordContainer();
249  my $css = $mcc->fetch_all_CoordSystems_by_feature_type('assembly_exception');
250 
251  my @features;
252 
253  my $ma = $self->db()->get_AssemblyMapperAdaptor();
254 
255  foreach my $cs (@$css) {
256  my $mapper;
257  if($cs->equals($slice->coord_system)) {
258  $mapper = undef;
259  } else {
260  $mapper = $ma->fetch_by_CoordSystems($cs,$slice->coord_system());
261  }
262 
263  push @features, @{ $self->_remap($all_features, $mapper, $slice) };
264  }
265 
266  $self->{'_aexc_slice_cache'}->{$key} = \@features;
267 
268  return \@features;
269 }
270 
271 
272 #
273 # Given a list of features checks if they are in the correct coord system
274 # by looking at the first features slice. If they are not then they are
275 # converted and placed on the slice.
276 #
277 # Note that this is a re-implementation of a method with the same name in
278 # BaseFeatureAdaptor, and in contrast to the latter which maps features in
279 # place, this method returns a remapped copy of each feature. The reason for
280 # this is to get around conflicts with caching.
281 #
282 sub _remap {
283  my ($self, $features, $mapper, $slice) = @_;
284 
285  # check if any remapping is actually needed
286  if(@$features && (!$features->[0]->isa('Bio::EnsEMBL::Feature') ||
287  $features->[0]->slice == $slice)) {
288  return $features;
289  }
290  # remapping has not been done, we have to do our own conversion from
291  # to slice coords
292 
293  my @out;
294 
295  my $slice_start = $slice->start();
296  my $slice_end = $slice->end();
297  my $slice_strand = $slice->strand();
298  my $slice_cs = $slice->coord_system();
299 
300  my ($seq_region, $start, $end, $strand, $seq_region_name);
301 
302  my $slice_seq_region = $slice->seq_region_name();
303 
304  foreach my $f (@$features) {
305  # since feats were obtained in contig coords, attached seq is a contig
306  my $fslice = $f->slice();
307  if(!$fslice) {
308  throw("Feature does not have attached slice.\n");
309  }
310  my $fseq_region = $fslice->seq_region_name();
311  my $fcs = $fslice->coord_system();
312  if(!$slice_cs->equals($fcs)) {
313  # slice of feature in different coord system, mapping required
314  ($seq_region, $start, $end, $strand) =
315  $mapper->fastmap($fseq_region,$f->start(),$f->end(),$f->strand(),$fcs);
316  # undefined start means gap
317  next if(!defined $start);
318 
319  my $slice_adaptor = $self->db()->get_SliceAdaptor();
320  $seq_region_name = $slice_adaptor->fetch_by_seq_region_id($seq_region)->seq_region_name;
321 
322  } else {
323  $start = $f->start();
324  $end = $f->end();
325  $strand = $f->strand();
326  $seq_region_name = $f->slice->seq_region_name();
327  }
328 
329  # maps to region outside desired area
330  next if ($start > $slice_end) || ($end < $slice_start) ||
331  ($slice_seq_region ne $seq_region_name);
332 
333  # create new copies of successfully mapped feaatures with shifted start,
334  # end and strand
335  my ($new_start, $new_end);
336  my $seq_region_len = $slice->seq_region_length();
337 
338  if ($slice_strand == 1) { # Positive strand
339 
340  $new_start = $start - $slice_start + 1;
341  $new_end = $end - $slice_start + 1;
342 
343  if ($slice->is_circular()) {
344  # Handle circular chromosomes.
345 
346  if ($new_start > $new_end) {
347  # Looking at a feature overlapping the chromsome origin.
348  if ($new_end > $slice_start) {
349  # Looking at the region in the beginning of the chromosome.
350  $new_start -= $seq_region_len;
351  }
352  if ($new_end < 0) {
353  $new_end += $seq_region_len;
354  }
355  } else {
356  if ( $slice_start > $slice_end && $new_end < 0) {
357  # Looking at the region overlapping the chromosome
358  # origin and a feature which is at the beginning of the
359  # chromosome.
360  $new_start += $seq_region_len;
361  $new_end += $seq_region_len;
362  }
363  }
364  } ## end if ($dest_slice->is_circular...)
365  } else { # Negative strand
366 
367  $new_start = $slice_end - $end + 1;
368  $new_end = $slice_end - $start + 1;
369 
370  if ($slice->is_circular()) {
371 
372  if ($slice_start > $slice_end) {
373  # slice spans origin or replication
374 
375  if ($start >= $slice_start) {
376  $new_end += $seq_region_len;
377  $new_start += $seq_region_len if $end > $slice_start;
378 
379  } elsif ($start <= $slice_end) {
380  # do nothing
381  } elsif ($end >= $slice_start) {
382  $new_start += $seq_region_len;
383  $new_end += $seq_region_len;
384 
385  } elsif ($end <= $slice_end) {
386  $new_end += $seq_region_len if $new_end < 0;
387  } elsif ($start > $end) {
388  $new_end += $seq_region_len;
389  } else {
390 
391  }
392 
393  } else {
394 
395  if ($start <= $slice_end and $end >= $slice_start) {
396  # do nothing
397  } elsif ($start > $end) {
398  if ($start <= $slice_end) {
399 
400  $new_start -= $seq_region_len;
401 
402  } elsif ($end >= $slice_start) {
403  $new_end += $seq_region_len;
404  } else {
405 
406  }
407  }
408  }
409 
410  }
411 
412  } ## end else [ if ($dest_slice_strand...)]
414  '-dbID' => $f->dbID,
415  '-start' => $new_start,
416  '-end' => $new_end,
417  '-strand' => $strand * $slice_strand,
418  '-adaptor' => $self,
419  '-slice' => $slice,
420  '-alternate_slice' => $f->alternate_slice,
421  '-type' => $f->type,
422  );
423  } # end foreach assembly exception
424 
425  return \@out;
426 }
427 
428 =head2 store
429 
430  Arg[1] : Bio::EnsEMBL::AssemblyException $asx
431  Arg[2] : Bio::EnsEMBL::AssemblyException $asx2
432 
433  Example : $asx = Bio::EnsEMBL::AssemblyExceptionFeature->new(...)
435  $asx_seq_region_id = $asx_adaptor->store($asx);
436  Description: This stores a assembly exception feature in the
437  assembly_exception table and returns the assembly_exception_id.
438  Needs 2 features: one pointing to the Assembly_exception, and the
439  other pointing to the region in the reference that is being mapped to
440  Will check that start, end and type are defined, and the alternate
441  slice is present as well.
442  ReturnType: int
443  Exceptions: throw if assembly exception not defined (needs start, end,
444  type and alternate_slice) of if $asx not a Bio::EnsEMBL::AssemblyException
445  Caller: general
446  Status: Stable
447 
448 =cut
449 
450 sub store{
451  my $self = shift;
452  my $asx = shift;
453  my $asx2 = shift;
454 
455 
456  if (! $asx->isa('Bio::EnsEMBL::AssemblyExceptionFeature')){
457  throw("$asx is not a Ensembl assemlby exception -- not stored");
458  }
459  #if already present, return ID in the database
460  my $db = $self->db();
461  if ($asx->is_stored($db)){
462  return $asx->dbID();
463  }
464  #do some checkings for the object
465  #at the moment, the orientation is always 1
466  if (! $asx->start || ! $asx->end ){
467  throw("Assembly exception does not have coordinates");
468  }
469  if ($asx->type !~ /PAR|HAP|PATCH_NOVEL|PATCH_FIX/){
470  throw("Only types of assembly exception features valid are PAR, HAP, PATCH_FIX or PATCH_NOVEL");
471  }
472  if ( !($asx->alternate_slice->isa('Bio::EnsEMBL::Slice')) ){
473  throw("Alternate slice should be a Bio::EnsEMBL::Slice");
474  }
475  #now check the other Assembly exception feature, the one pointing to the REF
476  # region
477  if (!$asx2->isa('Bio::EnsEMBL::AssemblyExceptionFeature')){
478  throw("$asx2 is not a Ensembl assemlby exception -- not stored");
479  }
480  if (! $asx2->start || ! $asx2->end ){
481  throw("Assembly exception does not have coordinates");
482  }
483  if ($asx2->type !~ /HAP REF|PAR REF|PATCH_NOVEL REF|PATCH_FIX REF/){
484  throw("$asx2 should have type of assembly exception features HAP REF, PAR REF, PATCH_FIX REF or PATCH_NOVEL REF");
485  }
486  if (! ($asx2->alternate_slice->isa('Bio::EnsEMBL::Slice')) ){
487  throw("Alternate slice should be a Bio::EnsEMBL::Slice");
488  }
489  #finally check that both features are pointing to each other slice
490  if ($asx->slice != $asx2->alternate_slice || $asx->alternate_slice != $asx2->slice){
491  throw("Slice and alternate slice in both features are not pointing to each other");
492  }
493  #prepare the SQL
494  my $asx_sql = q{
495  INSERT INTO assembly_exception( seq_region_id, seq_region_start,
496  seq_region_end,
497  exc_type, exc_seq_region_id,
498  exc_seq_region_start, exc_seq_region_end,
499  ori)
500  VALUES (?, ?, ?, ?, ?, ?, ?, 1)
501  };
502 
503  my $asx_st = $self->prepare($asx_sql);
504  my $asx_id = undef;
505  my $asx_seq_region_id;
506  my $asx2_seq_region_id;
507  my $original = $asx;
508  my $original2 = $asx2;
509  #check all feature information
510  ($asx, $asx_seq_region_id) = $self->_pre_store($asx);
511  ($asx2, $asx2_seq_region_id) = $self->_pre_store($asx2);
512 
513  #and store it
514  $asx_st->bind_param(1, $asx_seq_region_id, SQL_INTEGER);
515  $asx_st->bind_param(2, $asx->start(), SQL_INTEGER);
516  $asx_st->bind_param(3, $asx->end(), SQL_INTEGER);
517  $asx_st->bind_param(4, $asx->type(), SQL_VARCHAR);
518  $asx_st->bind_param(5, $asx2_seq_region_id, SQL_INTEGER);
519  $asx_st->bind_param(6, $asx2->start(), SQL_INTEGER);
520  $asx_st->bind_param(7, $asx2->end(), SQL_INTEGER);
521 
522  $asx_st->execute();
523  $asx_id = $self->last_insert_id('assembly_exception_id', undef, 'assembly_exception');
524 
525  #finally, update the dbID and adaptor of the asx and asx2
526  $original->adaptor($self);
527  $original->dbID($asx_id);
528  $original2->adaptor($self);
529  $original2->dbID($asx_id);
530  #and finally update dbID cache with new assembly exception
531  $self->{'_aexc_dbID_cache'}->{$asx_id} = $original;
532  #and update the other caches as well
533  push @{$self->{'_aexc_slice_cache'}->{uc($asx->slice->name)}},$original, $original2;
534  push @{$self->{'_aexc_cache'}}, $original, $original2;
535 
536  return $asx_id;
537 }
538 
539 #
540 # Helper function containing some common feature storing functionality
541 #
542 # Given a Feature this will return a copy (or the same feature if no changes
543 # to the feature are needed) of the feature which is relative to the start
544 # of the seq_region it is on. The seq_region_id of the seq_region it is on
545 # is also returned.
546 #
547 # This method will also ensure that the database knows which coordinate
548 # systems that this feature is stored in.
549 # Since this adaptor doesn't inherit from BaseFeatureAdaptor, we need to copy
550 # the code
551 #
552 
553 sub _pre_store {
554  my $self = shift;
555  my $feature = shift;
556 
557  if(!ref($feature) || !$feature->isa('Bio::EnsEMBL::Feature')) {
558  throw('Expected Feature argument.');
559  }
560 
561  $self->_check_start_end_strand($feature->start(),$feature->end(),
562  $feature->strand());
563 
564 
565  my $db = $self->db();
566 
567  my $slice_adaptor = $db->get_SliceAdaptor();
568  my $slice = $feature->slice();
569 
570  if(!ref($slice) || !($slice->isa('Bio::EnsEMBL::Slice') or $slice->isa('Bio::EnsEMBL::LRGSlice')) ) {
571  throw('Feature must be attached to Slice to be stored.');
572  }
573 
574  # make sure feature coords are relative to start of entire seq_region
575 
576  if($slice->start != 1 || $slice->strand != 1) {
577  #move feature onto a slice of the entire seq_region
578  $slice = $slice_adaptor->fetch_by_region($slice->coord_system->name(),
579  $slice->seq_region_name(),
580  undef, #start
581  undef, #end
582  undef, #strand
583  $slice->coord_system->version());
584 
585  $feature = $feature->transfer($slice);
586 
587  if(!$feature) {
588  throw('Could not transfer Feature to slice of ' .
589  'entire seq_region prior to storing');
590  }
591  }
592 
593  # Ensure this type of feature is known to be stored in this coord system.
594  my $cs = $slice->coord_system;
595 
596  my $mcc = $db->get_MetaCoordContainer();
597 
598  $mcc->add_feature_type($cs, 'assembly_exception', $feature->length);
599 
600  my $seq_region_id = $slice_adaptor->get_seq_region_id($slice);
601 
602  if(!$seq_region_id) {
603  throw('Feature is associated with seq_region which is not in this DB.');
604  }
605 
606  return ($feature, $seq_region_id);
607 }
608 
609 #
610 # helper function used to validate start/end/strand and
611 # hstart/hend/hstrand etc.
612 #
613 sub _check_start_end_strand {
614  my $self = shift;
615  my $start = shift;
616  my $end = shift;
617  my $strand = shift;
618 
619  #
620  # Make sure that the start, end, strand are valid
621  #
622  if(int($start) != $start) {
623  throw("Invalid Feature start [$start]. Must be integer.");
624  }
625  if(int($end) != $end) {
626  throw("Invalid Feature end [$end]. Must be integer.");
627  }
628  if(int($strand) != $strand || $strand < -1 || $strand > 1) {
629  throw("Invalid Feature strand [$strand]. Must be -1, 0 or 1.");
630  }
631  if($end < $start) {
632  throw("Invalid Feature start/end [$start/$end]. Start must be less " .
633  "than or equal to end.");
634  }
635 
636  return 1;
637 }
638 
639 =head2 remove
640 
641  Arg [1] : $asx Bio::EnsEMBL::AssemblyFeatureException
642  Example : $asx_adaptor->remove($asx);
643  Description: This removes a assembly exception feature from the database.
644  Returntype : none
645  Exceptions : thrown if $asx arg does not implement dbID(), or if
646  $asx->dbID is not a true value
647  Caller : general
648  Status : Stable
649 
650 =cut
651 
652 #again, this method is generic in BaseFeatureAdaptor, but since this class
653 #is not inheriting, need to copy&paste
654 sub remove {
655  my ($self, $feature) = @_;
656 
657  if(!$feature || !ref($feature) || !$feature->isa('Bio::EnsEMBL::AssemblyExceptionFeature')) {
658  throw('AssemblyExceptionFeature argument is required');
659  }
660 
661  if(!$feature->is_stored($self->db)) {
662  throw("This feature is not stored in this database");
663  }
664 
665  my $asx_id = $feature->dbID();
666  my $key = uc($feature->slice->name);
667  my $sth = $self->prepare("DELETE FROM assembly_exception WHERE assembly_exception_id = ?");
668  $sth->bind_param(1,$feature->dbID,SQL_INTEGER);
669  $sth->execute();
670 
671  #and clear cache
672  #and finally update dbID cache
673  delete $self->{'_aexc_dbID_cache'}->{$asx_id};
674  #and remove from cache feature
675  my @features;
676  foreach my $asx (@{$self->{'_aexc_slice_cache'}->{$key}}){
677  if ($asx->dbID != $asx_id){
678  push @features, $asx;
679  }
680  }
681  $self->{'_aexc_slice_cache'}->{$key} = \@features;
682  @features = ();
683  foreach my $asx (@{$self->{'_aexc_cache'}}){
684  if ($asx->dbID != $asx_id){
685  push @features, $asx;
686  }
687  }
688  $self->{'_aexc_cache'} = \@features;
689 
690 #unset the feature dbID ad adaptor
691  $feature->dbID(undef);
692  $feature->adaptor(undef);
693 
694  return;
695 }
696 
697 
698 1;
Bio::EnsEMBL::Storable::dbID
public Int dbID()
Bio::EnsEMBL::DBSQL::BaseFeatureAdaptor
Definition: BaseFeatureAdaptor.pm:24
Bio::EnsEMBL::Feature
Definition: Feature.pm:47
Bio::EnsEMBL::DBSQL::AssemblyExceptionFeatureAdaptor::fetch_all_by_Slice
public Reference fetch_all_by_Slice()
Bio::EnsEMBL::Slice
Definition: Slice.pm:50
Bio::EnsEMBL::DBSQL::AssemblyExceptionFeatureAdaptor
Definition: AssemblyExceptionFeatureAdaptor.pm:25
Bio::EnsEMBL::Utils::Cache
Definition: Cache.pm:71
Bio::EnsEMBL::DBSQL::BaseAdaptor
Definition: BaseAdaptor.pm:71
Bio::EnsEMBL::AssemblyExceptionFeature::new
public Bio::EnsEMBL::Feature new()
Bio::EnsEMBL::AssemblyExceptionFeature
Definition: AssemblyExceptionFeature.pm:27
Bio::EnsEMBL::Utils::Exception
Definition: Exception.pm:68