JAL-2094 new classes ColorI, Colour added
[jalview.git] / src / jalview / ext / ensembl / EnsemblGene.java
1 package jalview.ext.ensembl;
2
3 import jalview.api.ColorI;
4 import jalview.api.FeatureColourI;
5 import jalview.api.FeatureSettingsModelI;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.Sequence;
8 import jalview.datamodel.SequenceFeature;
9 import jalview.datamodel.SequenceI;
10 import jalview.io.gff.SequenceOntologyFactory;
11 import jalview.io.gff.SequenceOntologyI;
12 import jalview.schemes.Colour;
13 import jalview.schemes.FeatureColourAdapter;
14 import jalview.schemes.FeatureSettingsAdapter;
15 import jalview.util.MapList;
16
17 import java.io.UnsupportedEncodingException;
18 import java.net.URLDecoder;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22
23 import com.stevesoft.pat.Regex;
24
25 /**
26  * A class that fetches genomic sequence and all transcripts for an Ensembl gene
27  * 
28  * @author gmcarstairs
29  */
30 public class EnsemblGene extends EnsemblSeqProxy
31 {
32   private static final String GENE_PREFIX = "gene:";
33
34   /*
35    * accepts anything as we will attempt lookup of gene or 
36    * transcript id or gene name
37    */
38   private static final Regex ACCESSION_REGEX = new Regex(".*");
39
40   private static final EnsemblFeatureType[] FEATURES_TO_FETCH = {
41       EnsemblFeatureType.gene, EnsemblFeatureType.transcript,
42       EnsemblFeatureType.exon, EnsemblFeatureType.cds,
43       EnsemblFeatureType.variation };
44
45   /**
46    * Default constructor (to use rest.ensembl.org)
47    */
48   public EnsemblGene()
49   {
50     super();
51   }
52
53   /**
54    * Constructor given the target domain to fetch data from
55    * 
56    * @param d
57    */
58   public EnsemblGene(String d)
59   {
60     super(d);
61   }
62
63   @Override
64   public String getDbName()
65   {
66     return "ENSEMBL";
67   }
68
69   @Override
70   protected EnsemblFeatureType[] getFeaturesToFetch()
71   {
72     return FEATURES_TO_FETCH;
73   }
74
75   @Override
76   protected EnsemblSeqType getSourceEnsemblType()
77   {
78     return EnsemblSeqType.GENOMIC;
79   }
80
81   /**
82    * Returns an alignment containing the gene(s) for the given gene or
83    * transcript identifier, or external identifier (e.g. Uniprot id). If given a
84    * gene name or external identifier, returns any related gene sequences found
85    * for model organisms. If only a single gene is queried for, then its
86    * transcripts are also retrieved and added to the alignment. <br>
87    * Method:
88    * <ul>
89    * <li>resolves a transcript identifier by looking up its parent gene id</li>
90    * <li>resolves an external identifier by looking up xref-ed gene ids</li>
91    * <li>fetches the gene sequence</li>
92    * <li>fetches features on the sequence</li>
93    * <li>identifies "transcript" features whose Parent is the requested gene</li>
94    * <li>fetches the transcript sequence for each transcript</li>
95    * <li>makes a mapping from the gene to each transcript</li>
96    * <li>copies features from gene to transcript sequences</li>
97    * <li>fetches the protein sequence for each transcript, maps and saves it as
98    * a cross-reference</li>
99    * <li>aligns each transcript against the gene sequence based on the position
100    * mappings</li>
101    * </ul>
102    * 
103    * @param query
104    *          a single gene or transcript identifier or gene name
105    * @return an alignment containing a gene, and possibly transcripts, or null
106    */
107   @Override
108   public AlignmentI getSequenceRecords(String query) throws Exception
109   {
110     /*
111      * convert to a non-duplicated list of gene identifiers
112      */
113     List<String> geneIds = getGeneIds(query);
114
115     AlignmentI al = null;
116     for (String geneId : geneIds)
117     {
118       /*
119        * fetch the gene sequence(s) with features and xrefs
120        */
121       AlignmentI geneAlignment = super.getSequenceRecords(geneId);
122
123       if (geneAlignment.getHeight() == 1)
124       {
125         getTranscripts(geneAlignment, geneId);
126       }
127       if (al == null)
128       {
129         al = geneAlignment;
130       }
131       else
132       {
133         al.append(geneAlignment);
134       }
135     }
136     return al;
137   }
138
139   /**
140    * Converts a query, which may contain one or more gene or transcript
141    * identifiers, into a non-redundant list of gene identifiers.
142    * 
143    * @param accessions
144    * @return
145    */
146   List<String> getGeneIds(String accessions)
147   {
148     List<String> geneIds = new ArrayList<String>();
149
150     for (String acc : accessions.split(getAccessionSeparator()))
151     {
152       if (isGeneIdentifier(acc))
153       {
154         if (!geneIds.contains(acc))
155         {
156           geneIds.add(acc);
157         }
158       }
159
160       /*
161        * if given a transcript id, look up its gene parent
162        */
163       else if (isTranscriptIdentifier(acc))
164       {
165         String geneId = new EnsemblLookup(getDomain()).getParent(acc);
166         if (geneId != null && !geneIds.contains(geneId))
167         {
168           geneIds.add(geneId);
169         }
170       }
171
172       /*
173        * if given a gene or other external name, lookup and fetch 
174        * the corresponding gene for all model organisms 
175        */
176       else
177       {
178         List<String> ids = new EnsemblSymbol(getDomain()).getIds(acc);
179         for (String geneId : ids)
180         {
181           if (!geneIds.contains(geneId))
182           {
183             geneIds.add(geneId);
184           }
185         }
186       }
187     }
188     return geneIds;
189   }
190
191   /**
192    * Attempts to get Ensembl stable identifiers for model organisms for a gene
193    * name by calling the xrefs symbol REST service to resolve the gene name.
194    * 
195    * @param query
196    * @return
197    */
198   protected String getGeneIdentifiersForName(String query)
199   {
200     List<String> ids = new EnsemblSymbol(getDomain()).getIds(query);
201     if (ids != null)
202     {
203       for (String id : ids)
204       {
205         if (isGeneIdentifier(id))
206         {
207           return id;
208         }
209       }
210     }
211     return null;
212   }
213
214   /**
215    * Constructs all transcripts for the gene, as identified by "transcript"
216    * features whose Parent is the requested gene. The coding transcript
217    * sequences (i.e. with introns omitted) are added to the alignment.
218    * 
219    * @param al
220    * @param accId
221    * @throws Exception
222    */
223   protected void getTranscripts(AlignmentI al, String accId)
224           throws Exception
225   {
226     SequenceI gene = al.getSequenceAt(0);
227     List<SequenceFeature> transcriptFeatures = getTranscriptFeatures(accId,
228             gene);
229
230     for (SequenceFeature transcriptFeature : transcriptFeatures)
231     {
232       makeTranscript(transcriptFeature, al, gene);
233     }
234
235     clearGeneFeatures(gene);
236   }
237
238   /**
239    * Remove unwanted features (transcript, exon, CDS) from the gene sequence
240    * after we have used them to derive transcripts and transfer features
241    * 
242    * @param gene
243    */
244   protected void clearGeneFeatures(SequenceI gene)
245   {
246     SequenceFeature[] sfs = gene.getSequenceFeatures();
247     if (sfs != null)
248     {
249       SequenceOntologyI so = SequenceOntologyFactory.getInstance();
250       List<SequenceFeature> filtered = new ArrayList<SequenceFeature>();
251       for (SequenceFeature sf : sfs)
252       {
253         String type = sf.getType();
254         if (!isTranscript(type) && !so.isA(type, SequenceOntologyI.EXON)
255                 && !so.isA(type, SequenceOntologyI.CDS))
256         {
257           filtered.add(sf);
258         }
259       }
260       gene.setSequenceFeatures(filtered
261               .toArray(new SequenceFeature[filtered
262               .size()]));
263     }
264   }
265
266   /**
267    * Constructs a spliced transcript sequence by finding 'exon' features for the
268    * given id (or failing that 'CDS'). Copies features on to the new sequence.
269    * 'Aligns' the new sequence against the gene sequence by padding with gaps,
270    * and adds it to the alignment.
271    * 
272    * @param transcriptFeature
273    * @param al
274    *          the alignment to which to add the new sequence
275    * @param gene
276    *          the parent gene sequence, with features
277    * @return
278    */
279   SequenceI makeTranscript(SequenceFeature transcriptFeature,
280           AlignmentI al, SequenceI gene)
281   {
282     String accId = getTranscriptId(transcriptFeature);
283     if (accId == null)
284     {
285       return null;
286     }
287
288     /*
289      * NB we are mapping from gene sequence (not genome), so do not
290      * need to check for reverse strand (gene and transcript sequences 
291      * are in forward sense)
292      */
293
294     /*
295      * make a gene-length sequence filled with gaps
296      * we will fill in the bases for transcript regions
297      */
298     char[] seqChars = new char[gene.getLength()];
299     Arrays.fill(seqChars, al.getGapCharacter());
300
301     /*
302      * look for exon features of the transcript, failing that for CDS
303      * (for example ENSG00000124610 has 1 CDS but no exon features)
304      */
305     String parentId = "transcript:" + accId;
306     List<SequenceFeature> splices = findFeatures(gene,
307             SequenceOntologyI.EXON, parentId);
308     if (splices.isEmpty())
309     {
310       splices = findFeatures(gene, SequenceOntologyI.CDS, parentId);
311     }
312
313     int transcriptLength = 0;
314     final char[] geneChars = gene.getSequence();
315     int offset = gene.getStart(); // to convert to 0-based positions
316     List<int[]> mappedFrom = new ArrayList<int[]>();
317
318     for (SequenceFeature sf : splices)
319     {
320       int start = sf.getBegin() - offset;
321       int end = sf.getEnd() - offset;
322       int spliceLength = end - start + 1;
323       System.arraycopy(geneChars, start, seqChars, start, spliceLength);
324       transcriptLength += spliceLength;
325       mappedFrom.add(new int[] { sf.getBegin(), sf.getEnd() });
326     }
327
328     Sequence transcript = new Sequence(accId, seqChars, 1, transcriptLength);
329
330     /*
331      * Ensembl has gene name as transcript Name
332      * EnsemblGenomes doesn't, but has a url-encoded description field
333      */
334     String description = (String) transcriptFeature.getValue(NAME);
335     if (description == null)
336     {
337       description = (String) transcriptFeature.getValue(DESCRIPTION);
338     }
339     if (description != null)
340     {
341       try
342       {
343         transcript.setDescription(URLDecoder.decode(description, "UTF-8"));
344       } catch (UnsupportedEncodingException e)
345       {
346         e.printStackTrace(); // as if
347       }
348     }
349     transcript.createDatasetSequence();
350
351     al.addSequence(transcript);
352
353     /*
354      * transfer features to the new sequence; we use EnsemblCdna to do this,
355      * to filter out unwanted features types (see method retainFeature)
356      */
357     List<int[]> mapTo = new ArrayList<int[]>();
358     mapTo.add(new int[] { 1, transcriptLength });
359     MapList mapping = new MapList(mappedFrom, mapTo, 1, 1);
360     EnsemblCdna cdna = new EnsemblCdna(getDomain());
361     cdna.transferFeatures(gene.getSequenceFeatures(),
362             transcript.getDatasetSequence(), mapping, parentId);
363
364     /*
365      * fetch and save cross-references
366      */
367     cdna.getCrossReferences(transcript);
368
369     /*
370      * and finally fetch the protein product and save as a cross-reference
371      */
372     cdna.addProteinProduct(transcript);
373
374     return transcript;
375   }
376
377   /**
378    * Returns the 'transcript_id' property of the sequence feature (or null)
379    * 
380    * @param feature
381    * @return
382    */
383   protected String getTranscriptId(SequenceFeature feature)
384   {
385     return (String) feature.getValue("transcript_id");
386   }
387
388   /**
389    * Returns a list of the transcript features on the sequence whose Parent is
390    * the gene for the accession id.
391    * 
392    * @param accId
393    * @param geneSequence
394    * @return
395    */
396   protected List<SequenceFeature> getTranscriptFeatures(String accId,
397           SequenceI geneSequence)
398   {
399     List<SequenceFeature> transcriptFeatures = new ArrayList<SequenceFeature>();
400
401     String parentIdentifier = GENE_PREFIX + accId;
402     SequenceFeature[] sfs = geneSequence.getSequenceFeatures();
403
404     if (sfs != null)
405     {
406       for (SequenceFeature sf : sfs)
407       {
408         if (isTranscript(sf.getType()))
409         {
410           String parent = (String) sf.getValue(PARENT);
411           if (parentIdentifier.equals(parent))
412           {
413             transcriptFeatures.add(sf);
414           }
415         }
416       }
417     }
418
419     return transcriptFeatures;
420   }
421
422   @Override
423   public String getDescription()
424   {
425     return "Fetches all transcripts and variant features for a gene or transcript";
426   }
427
428   /**
429    * Default test query is a gene id (can also enter a transcript id)
430    */
431   @Override
432   public String getTestQuery()
433   {
434     return "ENSG00000157764"; // BRAF, 5 transcripts, reverse strand
435     // ENSG00000090266 // NDUFB2, 15 transcripts, forward strand
436     // ENSG00000101812 // H2BFM histone, 3 transcripts, forward strand
437     // ENSG00000123569 // H2BFWT histone, 2 transcripts, reverse strand
438   }
439
440   /**
441    * Answers true for a feature of type 'gene' (or a sub-type of gene in the
442    * Sequence Ontology), whose ID is the accession we are retrieving
443    */
444   @Override
445   protected boolean identifiesSequence(SequenceFeature sf, String accId)
446   {
447     if (SequenceOntologyFactory.getInstance().isA(sf.getType(),
448             SequenceOntologyI.GENE))
449     {
450       String id = (String) sf.getValue(ID);
451       if ((GENE_PREFIX + accId).equals(id))
452       {
453         return true;
454       }
455     }
456     return false;
457   }
458
459   /**
460    * Answers true unless feature type is 'gene', or 'transcript' with a parent
461    * which is a different gene. We need the gene features to identify the range,
462    * but it is redundant information on the gene sequence. Checking the parent
463    * allows us to drop transcript features which belong to different
464    * (overlapping) genes.
465    */
466   @Override
467   protected boolean retainFeature(SequenceFeature sf, String accessionId)
468   {
469     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
470     String type = sf.getType();
471     if (so.isA(type, SequenceOntologyI.GENE))
472     {
473       return false;
474     }
475     if (isTranscript(type))
476     {
477       String parent = (String) sf.getValue(PARENT);
478       if (!(GENE_PREFIX + accessionId).equals(parent))
479       {
480         return false;
481       }
482     }
483     return true;
484   }
485
486   /**
487    * Answers false. This allows an optimisation - a single 'gene' feature is all
488    * that is needed to identify the positions of the gene on the genomic
489    * sequence.
490    */
491   @Override
492   protected boolean isSpliceable()
493   {
494     return false;
495   }
496
497   /**
498    * Override to do nothing as Ensembl doesn't return a protein sequence for a
499    * gene identifier
500    */
501   @Override
502   protected void addProteinProduct(SequenceI querySeq)
503   {
504   }
505
506   @Override
507   public Regex getAccessionValidator()
508   {
509     return ACCESSION_REGEX;
510   }
511
512   /**
513    * Returns a descriptor for suitable feature display settings with
514    * <ul>
515    * <li>only exon or sequence_variant features (or their subtypes in the
516    * Sequence Ontology) visible</li>
517    * <li>variant features coloured red</li>
518    * <li>exon features coloured by label (exon name)</li>
519    * <li>variants displayed above (on top of) exons</li>
520    * </ul>
521    */
522   @Override
523   public FeatureSettingsModelI getFeatureColourScheme()
524   {
525     return new FeatureSettingsAdapter()
526     {
527       SequenceOntologyI so = SequenceOntologyFactory.getInstance();
528       @Override
529       public boolean isFeatureDisplayed(String type)
530       {
531         return (so.isA(type, SequenceOntologyI.EXON) || so.isA(type,
532                 SequenceOntologyI.SEQUENCE_VARIANT));
533       }
534
535       @Override
536       public FeatureColourI getFeatureColour(String type)
537       {
538         if (so.isA(type, SequenceOntologyI.EXON))
539         {
540           return new FeatureColourAdapter()
541           {
542             @Override
543             public boolean isColourByLabel()
544             {
545               return true;
546             }
547           };
548         }
549         if (so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT))
550         {
551           return new FeatureColourAdapter()
552           {
553
554             @Override
555             public ColorI getColour()
556             {
557               return Colour.red;
558             }
559           };
560         }
561         return null;
562       }
563
564       /**
565        * order to render sequence_variant after exon after the rest
566        */
567       @Override
568       public int compare(String feature1, String feature2)
569       {
570         if (so.isA(feature1, SequenceOntologyI.SEQUENCE_VARIANT))
571         {
572           return +1;
573         }
574         if (so.isA(feature2, SequenceOntologyI.SEQUENCE_VARIANT))
575         {
576           return -1;
577         }
578         if (so.isA(feature1, SequenceOntologyI.EXON))
579         {
580           return +1;
581         }
582         if (so.isA(feature2, SequenceOntologyI.EXON))
583         {
584           return -1;
585         }
586         return 0;
587       }
588     };
589   }
590
591 }