X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Fensembl%2FEnsemblSeqProxy.java;h=7aa7178b07e21f97f62ecc4537b4061e6ee7348d;hb=0a5ce6145bb76fc7eb8a5cc2670e20453fbedd29;hp=4af6525c7a3bdb97475f1766f63986206d12e57f;hpb=6c52cc0b81ae3abdc3c5f6f88a23364a0246351a;p=jalview.git diff --git a/src/jalview/ext/ensembl/EnsemblSeqProxy.java b/src/jalview/ext/ensembl/EnsemblSeqProxy.java index 4af6525..7aa7178 100644 --- a/src/jalview/ext/ensembl/EnsemblSeqProxy.java +++ b/src/jalview/ext/ensembl/EnsemblSeqProxy.java @@ -1,6 +1,28 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.ext.ensembl; import jalview.analysis.AlignmentUtils; +import jalview.analysis.Dna; +import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; @@ -13,6 +35,7 @@ import jalview.io.FastaFile; import jalview.io.FileParse; import jalview.io.gff.SequenceOntologyFactory; import jalview.io.gff.SequenceOntologyI; +import jalview.util.Comparison; import jalview.util.DBRefUtils; import jalview.util.MapList; @@ -33,11 +56,7 @@ import java.util.List; */ public abstract class EnsemblSeqProxy extends EnsemblRestClient { - private static final List CROSS_REFERENCES = Arrays - .asList(new String[] { "CCDS", "Uniprot/SWISSPROT", - "Uniprot/SPTREMBL" }); - - protected static final String CONSEQUENCE_TYPE = "consequence_type"; + private static final String ALLELES = "alleles"; protected static final String PARENT = "Parent"; @@ -58,7 +77,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient GENOMIC("genomic"), /** - * type=cdna to fetch dna including UTRs + * type=cdna to fetch coding dna including UTRs */ CDNA("cdna"), @@ -161,6 +180,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient + " chunks. Unexpected problem (" + r.getLocalizedMessage() + ")"; System.err.println(msg); + r.printStackTrace(); break; } } @@ -274,22 +294,63 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient proteinSeq.createDatasetSequence(); querySeq.createDatasetSequence(); - MapList mapList = AlignmentUtils.mapCdsToProtein(querySeq, proteinSeq); + MapList mapList = AlignmentUtils + .mapCdsToProtein(querySeq, proteinSeq); if (mapList != null) { // clunky: ensure Uniprot xref if we have one is on mapped sequence SequenceI ds = proteinSeq.getDatasetSequence(); - ds.setSourceDBRef(proteinSeq.getSourceDBRef()); + // TODO: Verify ensp primary ref is on proteinSeq.getDatasetSequence() Mapping map = new Mapping(ds, mapList); - DBRefEntry dbr = new DBRefEntry(getDbSource(), getDbVersion(), - accId, map); + DBRefEntry dbr = new DBRefEntry(getDbSource(), + getEnsemblDataVersion(), proteinSeq.getName(), map); querySeq.getDatasetSequence().addDBRef(dbr); - + DBRefEntry[] uprots = DBRefUtils.selectRefs(ds.getDBRefs(), + new String[] { DBRefSource.UNIPROT }); + DBRefEntry[] upxrefs = DBRefUtils.selectRefs(querySeq.getDBRefs(), + new String[] { DBRefSource.UNIPROT }); + if (uprots != null) + { + for (DBRefEntry up : uprots) + { + // locate local uniprot ref and map + List upx = DBRefUtils.searchRefs(upxrefs, + up.getAccessionId()); + DBRefEntry upxref; + if (upx.size() != 0) + { + upxref = upx.get(0); + + if (upx.size() > 1) + { + Cache.log + .warn("Implementation issue - multiple uniprot acc on product sequence."); + } + } + else + { + upxref = new DBRefEntry(DBRefSource.UNIPROT, + getEnsemblDataVersion(), up.getAccessionId()); + } + + Mapping newMap = new Mapping(ds, mapList); + upxref.setVersion(getEnsemblDataVersion()); + upxref.setMap(newMap); + if (upx.size() == 0) + { + // add the new uniprot ref + querySeq.getDatasetSequence().addDBRef(upxref); + } + + } + } + /* * copy exon features to protein, compute peptide variants from dna * variants and add as features on the protein sequence ta-da */ - AlignmentUtils.computeProteinFeatures(querySeq, proteinSeq, mapList); + AlignmentUtils + .computeProteinFeatures(querySeq, proteinSeq, mapList); } } catch (Exception e) { @@ -311,32 +372,20 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient seq = seq.getDatasetSequence(); } - EnsemblXref xrefFetcher = new EnsemblXref(getDomain()); - List xrefs = xrefFetcher.getCrossReferences(seq.getName(), - getCrossReferenceDatabases()); + EnsemblXref xrefFetcher = new EnsemblXref(getDomain(), getDbSource(), + getEnsemblDataVersion()); + List xrefs = xrefFetcher.getCrossReferences(seq.getName()); for (DBRefEntry xref : xrefs) { seq.addDBRef(xref); - /* - * Save any Uniprot xref to be the reference for SIFTS mapping - */ - if (DBRefSource.UNIPROT.equals(xref.getSource())) - { - seq.setSourceDBRef(xref); - } } - } - /** - * Returns a list of database names to be used when fetching cross-references. - * Specifically, the names are used to filter data returned by the Ensembl - * xrefs REST service on the value in field 'dbname'. - * - * @return - */ - protected List getCrossReferenceDatabases() - { - return CROSS_REFERENCES; + /* + * and add a reference to itself + */ + DBRefEntry self = new DBRefEntry(getDbSource(), + getEnsemblDataVersion(), seq.getName()); + seq.addDBRef(self); } /** @@ -358,6 +407,11 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient throw new JalviewException("ENSEMBL Rest API not available."); } FileParse fp = getSequenceReader(ids); + if (fp == null) + { + return alignment; + } + FastaFile fr = new FastaFile(fp); if (fr.hasWarningMessage()) { @@ -382,9 +436,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient if (fr.getSeqs().size() > 0) { - AlignmentI seqal = new Alignment( - fr.getSeqsAsArray()); - for (SequenceI sq:seqal.getSequences()) + AlignmentI seqal = new Alignment(fr.getSeqsAsArray()); + for (SequenceI sq : seqal.getSequences()) { if (sq.getDescription() == null) { @@ -394,7 +447,9 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient if (ids.contains(name) || ids.contains(name.replace("ENSP", "ENST"))) { - DBRefUtils.parseToDbRef(sq, DBRefSource.ENSEMBL, "0", name); + DBRefEntry dbref = DBRefUtils.parseToDbRef(sq, getDbSource(), + getEnsemblDataVersion(), name); + sq.addDBRef(dbref); } } if (alignment == null) @@ -514,7 +569,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient int mappedLength = 0; int direction = 1; // forward boolean directionSet = false; - + for (SequenceFeature sf : sfs) { /* @@ -531,20 +586,20 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient // abort - mix of forward and backward System.err.println("Error: forward and backward strand for " + accId); - return null; - } - direction = strand; - directionSet = true; - - /* - * add to CDS ranges, semi-sorted forwards/backwards - */ - if (strand < 0) - { - regions.add(0, new int[] { sf.getEnd(), sf.getBegin() }); - } - else - { + return null; + } + direction = strand; + directionSet = true; + + /* + * add to CDS ranges, semi-sorted forwards/backwards + */ + if (strand < 0) + { + regions.add(0, new int[] { sf.getEnd(), sf.getBegin() }); + } + else + { regions.add(new int[] { sf.getBegin(), sf.getEnd() }); } mappedLength += Math.abs(sf.getEnd() - sf.getBegin() + 1); @@ -559,7 +614,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } } } - + if (regions.isEmpty()) { System.out.println("Failed to identify target sequence for " + accId @@ -572,10 +627,10 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient * (havana / ensembl_havana) */ Collections.sort(regions, new RangeSorter(direction == 1)); - + List to = Arrays.asList(new int[] { start, start + mappedLength - 1 }); - + return new MapList(regions, to, 1, 1); } @@ -611,36 +666,103 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient * @param mapping * mapping from the sequence feature's coordinates to the target * sequence + * @param forwardStrand */ protected void transferFeature(SequenceFeature sf, - SequenceI targetSequence, MapList mapping) + SequenceI targetSequence, MapList mapping, boolean forwardStrand) { int start = sf.getBegin(); int end = sf.getEnd(); int[] mappedRange = mapping.locateInTo(start, end); - + if (mappedRange != null) { SequenceFeature copy = new SequenceFeature(sf); copy.setBegin(Math.min(mappedRange[0], mappedRange[1])); copy.setEnd(Math.max(mappedRange[0], mappedRange[1])); + if (".".equals(copy.getFeatureGroup())) + { + copy.setFeatureGroup(getDbSource()); + } targetSequence.addSequenceFeature(copy); /* - * for sequence_variant, make an additional feature with consequence + * for sequence_variant on reverse strand, have to convert the allele + * values to their complements */ - // if (SequenceOntologyFactory.getInstance().isA(sf.getType(), - // SequenceOntologyI.SEQUENCE_VARIANT)) - // { - // String consequence = (String) sf.getValue(CONSEQUENCE_TYPE); - // if (consequence != null) - // { - // SequenceFeature sf2 = new SequenceFeature("consequence", - // consequence, copy.getBegin(), copy.getEnd(), 0f, - // null); - // targetSequence.addSequenceFeature(sf2); - // } - // } + if (!forwardStrand + && SequenceOntologyFactory.getInstance().isA(sf.getType(), + SequenceOntologyI.SEQUENCE_VARIANT)) + { + reverseComplementAlleles(copy); + } + } + } + + /** + * Change the 'alleles' value of a feature by converting to complementary + * bases, and also update the feature description to match + * + * @param sf + */ + static void reverseComplementAlleles(SequenceFeature sf) + { + final String alleles = (String) sf.getValue(ALLELES); + if (alleles == null) + { + return; + } + StringBuilder complement = new StringBuilder(alleles.length()); + for (String allele : alleles.split(",")) + { + reverseComplementAllele(complement, allele); + } + String comp = complement.toString(); + sf.setValue(ALLELES, comp); + sf.setDescription(comp); + + /* + * replace value of "alleles=" in sf.ATTRIBUTES as well + * so 'output as GFF' shows reverse complement alleles + */ + String atts = sf.getAttributes(); + if (atts != null) + { + atts = atts.replace(ALLELES + "=" + alleles, ALLELES + "=" + comp); + sf.setAttributes(atts); + } + } + + /** + * Makes the 'reverse complement' of the given allele and appends it to the + * buffer, after a comma separator if not the first + * + * @param complement + * @param allele + */ + static void reverseComplementAllele(StringBuilder complement, + String allele) + { + if (complement.length() > 0) + { + complement.append(","); + } + + /* + * some 'alleles' are actually descriptive terms + * e.g. HGMD_MUTATION, PhenCode_variation + * - we don't want to 'reverse complement' these + */ + if (!Comparison.isNucleotideSequence(allele, true)) + { + complement.append(allele); + } + else + { + for (int i = allele.length() - 1; i >= 0; i--) + { + complement.append(Dna.getComplement(allele.charAt(i))); + } } } @@ -662,8 +784,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient // long start = System.currentTimeMillis(); SequenceFeature[] sfs = sourceSequence.getSequenceFeatures(); - MapList mapping = getGenomicRangesFromFeatures(sourceSequence, accessionId, - targetSequence.getStart()); + MapList mapping = getGenomicRangesFromFeatures(sourceSequence, + accessionId, targetSequence.getStart()); if (mapping == null) { return false; @@ -695,25 +817,18 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient final boolean forwardStrand = mapping.isFromForwardStrand(); /* - * sort features by start position (descending if reverse strand) - * before transferring (in forwards order) to the target sequence + * sort features by start position (which corresponds to end + * position descending if reverse strand) so as to add them in + * 'forwards' order to the target sequence */ - Arrays.sort(features, new Comparator() - { - @Override - public int compare(SequenceFeature o1, SequenceFeature o2) - { - int c = Integer.compare(o1.getBegin(), o2.getBegin()); - return forwardStrand ? c : -c; - } - }); + sortFeatures(features, forwardStrand); boolean transferred = false; for (SequenceFeature sf : features) { if (retainFeature(sf, parentId)) { - transferFeature(sf, targetSequence, mapping); + transferFeature(sf, targetSequence, mapping, forwardStrand); transferred = true; } } @@ -721,6 +836,33 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient } /** + * Sort features by start position ascending (if on forward strand), or end + * position descending (if on reverse strand) + * + * @param features + * @param forwardStrand + */ + protected static void sortFeatures(SequenceFeature[] features, + final boolean forwardStrand) + { + Arrays.sort(features, new Comparator() + { + @Override + public int compare(SequenceFeature o1, SequenceFeature o2) + { + if (forwardStrand) + { + return Integer.compare(o1.getBegin(), o2.getBegin()); + } + else + { + return Integer.compare(o2.getEnd(), o1.getEnd()); + } + } + }); + } + + /** * Answers true if the feature type is one we want to keep for the sequence. * Some features are only retrieved in order to identify the sequence range, * and may then be discarded as redundant information (e.g. "CDS" feature for @@ -774,11 +916,13 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient String type, String parentId) { List result = new ArrayList(); - + SequenceFeature[] sfs = sequence.getSequenceFeatures(); - if (sfs != null) { + if (sfs != null) + { SequenceOntologyI so = SequenceOntologyFactory.getInstance(); - for (SequenceFeature sf :sfs) { + for (SequenceFeature sf : sfs) + { if (so.isA(sf.getType(), type)) { String parent = (String) sf.getValue(PARENT);