From 3feccfbec4fb7e295d75b6dc8df60cc59c34f624 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Sat, 20 Apr 2019 07:00:47 +0100 Subject: [PATCH] JAL-3010 discover root parent SO terms at runtime --- src/jalview/datamodel/ontology/OntologyBase.java | 4 ++ src/jalview/datamodel/ontology/OntologyI.java | 12 ++++ src/jalview/ext/so/SequenceOntology.java | 79 +++++++++++++++++++++ src/jalview/gui/FeatureTypeSettings.java | 22 ++---- src/jalview/io/gff/SequenceOntologyLite.java | 53 ++++++++++++++ test/jalview/ext/so/SequenceOntologyTest.java | 22 ++++++ test/jalview/io/gff/SequenceOntologyLiteTest.java | 14 ++++ 7 files changed, 188 insertions(+), 18 deletions(-) diff --git a/src/jalview/datamodel/ontology/OntologyBase.java b/src/jalview/datamodel/ontology/OntologyBase.java index 25dae22..8bdebbd 100644 --- a/src/jalview/datamodel/ontology/OntologyBase.java +++ b/src/jalview/datamodel/ontology/OntologyBase.java @@ -1,8 +1,10 @@ package jalview.datamodel.ontology; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -13,6 +15,8 @@ import java.util.Set; */ public abstract class OntologyBase implements OntologyI { + protected Map> rootParents = new HashMap<>(); + @Override public Set getParentTerms(Set terms) { diff --git a/src/jalview/datamodel/ontology/OntologyI.java b/src/jalview/datamodel/ontology/OntologyI.java index 545a3c7..5ac5a97 100644 --- a/src/jalview/datamodel/ontology/OntologyI.java +++ b/src/jalview/datamodel/ontology/OntologyI.java @@ -58,4 +58,16 @@ public interface OntologyI * @return */ List termsNotFound(); + + /** + * Answers the top level parent terms (normally only one) for the given term, + * that is, those that have no parent themselves. Answers null if {@code term} + * is not a sequence ontology term. Answers a list just containing + * {@code term} if it is a valid term with no parent. + * + * @param term + * @return + */ + List getRootParents(String term); + } \ No newline at end of file diff --git a/src/jalview/ext/so/SequenceOntology.java b/src/jalview/ext/so/SequenceOntology.java index 7842294..8a3805d 100644 --- a/src/jalview/ext/so/SequenceOntology.java +++ b/src/jalview/ext/so/SequenceOntology.java @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -473,4 +474,82 @@ public class SequenceOntology extends OntologyBase return termsNotFound; } } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException + * if a loop is detected in the ontology + */ + @Override + public List getRootParents(final String term) + { + /* + * check in cache first + */ + if (rootParents.containsKey(term)) + { + return rootParents.get(term); + } + Term t = getTerm(term); + if (t == null) + { + return null; + } + + /* + * todo: check for loops using 'seen', allowing for alternate paths e.g. + * stop_gained isA feature_truncation isA feature_variant + * " isA nonsynonymous_variant ... isA geneVariant isA feature_variant + */ + List seen = new ArrayList<>(); + List top = new ArrayList<>(); + List query = new ArrayList<>(); + query.add(t); + + while (!query.isEmpty()) + { + List nextQuery = new ArrayList<>(); + for (Term q : query) + { + Set parents = ontology.getTriples(q, null, isA); + if (parents.isEmpty()) + { + /* + * q has no parents so is a top level term + */ + top.add(q); + } + else + { + /* + * search all parent terms + */ + for (Triple triple : parents) + { + Term parent = triple.getObject(); + nextQuery.add(parent); + } + } + } + query = nextQuery; + } + + List result = new ArrayList<>(); + for (Term found : top) + { + String desc = found.getDescription(); + if (!result.contains(desc)) + { + result.add(desc); + } + } + + /* + * save result in cache + */ + rootParents.put(term, result); + + return result; + } } diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java index 538c93c..0dd0f1f 100644 --- a/src/jalview/gui/FeatureTypeSettings.java +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -22,7 +22,6 @@ package jalview.gui; import jalview.api.AlignmentViewPanel; import jalview.api.FeatureColourI; -import jalview.bin.Cache; import jalview.datamodel.GraphLine; import jalview.datamodel.features.FeatureAttributes; import jalview.datamodel.features.FeatureAttributes.Datatype; @@ -85,11 +84,6 @@ import javax.swing.plaf.basic.BasicArrowButton; */ public class FeatureTypeSettings extends JalviewDialog { - /* - * 'top level' Sequence Ontology terms - */ - private final static String SO_ROOTS = "sequence_variant,sequence_attribute,sequence_collection,sequence_feature"; - private final static String LABEL_18N = MessageManager .getString("label.label"); @@ -309,23 +303,15 @@ public class FeatureTypeSettings extends JalviewDialog * parent of the current type */ SequenceOntologyI so = SequenceOntologyFactory.getInstance(); - String[] roots = Cache.getDefault("SO_ROOTS", SO_ROOTS).split(","); - rootSOTerm = null; - for (String root : roots) - { - if (so.isA(featureType, root.trim())) - { - rootSOTerm = root; - break; - } - } - if (rootSOTerm == null) + List roots = so.getRootParents(featureType); + if (roots == null || roots.size() > 1) { /* - * feature type is not an SO term + * feature type is not an SO term, or has ambiguous root */ return peers; } + rootSOTerm = roots.get(0); List types = fr.getRenderOrder(); for (String type : types) diff --git a/src/jalview/io/gff/SequenceOntologyLite.java b/src/jalview/io/gff/SequenceOntologyLite.java index 670d887..d4c2278 100644 --- a/src/jalview/io/gff/SequenceOntologyLite.java +++ b/src/jalview/io/gff/SequenceOntologyLite.java @@ -253,4 +253,57 @@ public class SequenceOntologyLite extends OntologyBase return termsNotFound; } } + + @Override + public List getRootParents(final String term) + { + /* + * check in cache first + */ + if (rootParents.containsKey(term)) + { + return rootParents.get(term); + } + + List top = new ArrayList<>(); + List query = new ArrayList<>(); + query.add(term); + + while (!query.isEmpty()) + { + List nextQuery = new ArrayList<>(); + for (String q : query) + { + List theParents = parents.get(q); + if (theParents != null) + { + if (theParents.size() == 1 && theParents.get(0).equals(q)) + { + /* + * top-level term + */ + if (!top.contains(q)) + { + top.add(q); + } + } + else + { + for (String p : theParents) + { + if (!p.equals(q)) + { + nextQuery.add(p); + } + } + } + } + } + query = nextQuery; + } + + rootParents.put(term, top); + + return top.isEmpty() ? null : top; + } } diff --git a/test/jalview/ext/so/SequenceOntologyTest.java b/test/jalview/ext/so/SequenceOntologyTest.java index c7776a3..489e462 100644 --- a/test/jalview/ext/so/SequenceOntologyTest.java +++ b/test/jalview/ext/so/SequenceOntologyTest.java @@ -22,6 +22,7 @@ package jalview.ext.so; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.datamodel.ontology.OntologyI; @@ -177,4 +178,25 @@ public class SequenceOntologyTest assertTrue(parents.contains("sequence_variant")); assertTrue(parents.contains("chain")); } + + @Test(groups = "Functional") + public void testGetRootParents() + { + List roots = so.getRootParents("xyz"); + assertNull(roots); + roots = so.getRootParents(null); + assertNull(roots); + + roots = so.getRootParents("stop_gained"); + assertEquals(roots.size(), 1); + assertEquals(roots.get(0), "sequence_variant"); + + roots = so.getRootParents("sequence_variant"); + assertEquals(roots.size(), 1); + assertEquals(roots.get(0), "sequence_variant"); + + roots = so.getRootParents("alanine"); + assertEquals(roots.size(), 1); + assertEquals(roots.get(0), "sequence_feature"); + } } diff --git a/test/jalview/io/gff/SequenceOntologyLiteTest.java b/test/jalview/io/gff/SequenceOntologyLiteTest.java index 3076f96..abc9fef 100644 --- a/test/jalview/io/gff/SequenceOntologyLiteTest.java +++ b/test/jalview/io/gff/SequenceOntologyLiteTest.java @@ -2,6 +2,7 @@ package jalview.io.gff; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.datamodel.ontology.OntologyI; @@ -88,4 +89,17 @@ public class SequenceOntologyLiteTest assertTrue(children.contains("synonymous_variant")); assertTrue(children.contains("stop_lost")); } + + @Test(groups = "Functional") + public void testGetRootParents() + { + List roots = so.getRootParents("xyz"); + assertNull(roots); + roots = so.getRootParents(null); + assertNull(roots); + + roots = so.getRootParents("stop_gained"); + assertEquals(roots.size(), 1); + assertEquals(roots.get(0), "sequence_variant"); + } } -- 1.7.10.2