diff --git a/src/main/scala/org/phenopackets/pxftools/util/NoctuaDiseasePhenotypeModelReader.scala b/src/main/scala/org/phenopackets/pxftools/util/NoctuaDiseasePhenotypeModelReader.scala new file mode 100644 index 0000000..68d19ea --- /dev/null +++ b/src/main/scala/org/phenopackets/pxftools/util/NoctuaDiseasePhenotypeModelReader.scala @@ -0,0 +1,186 @@ +package org.phenopackets.pxftools.util + +import java.io.InputStream + +import scala.collection.JavaConverters._ + +import org.phenopackets.api.PhenoPacket +import org.phenopackets.api.model.association.PhenotypeAssociation +import org.phenopackets.api.model.condition.Phenotype +import org.phenopackets.api.model.entity.Disease +import org.phenopackets.api.model.evidence.Evidence +import org.phenopackets.api.model.evidence.Publication +import org.phenopackets.api.model.ontology.OntologyClass +import org.phenopackets.pxftools.util.NoctuaModelVocabulary._ +import org.phenoscape.scowl._ +import org.semanticweb.owlapi.apibinding.OWLManager +import org.semanticweb.owlapi.model.AxiomType +import org.semanticweb.owlapi.model.IRI +import org.semanticweb.owlapi.model.OWLClass +import org.semanticweb.owlapi.model.OWLEntity +import org.semanticweb.owlapi.model.OWLNamedIndividual +import org.semanticweb.owlapi.model.OWLOntology + +import com.google.common.base.Optional +import com.typesafe.scalalogging.LazyLogging + +import scalaz._ +import scalaz.Scalaz._ +import org.phenopackets.api.model.condition.ConditionFrequency +import org.phenopackets.api.model.ontology.ClassInstance +import org.phenopackets.api.model.condition.ConditionSeverity +import org.phenopackets.api.model.condition.TemporalRegion + +object NoctuaDiseasePhenotypeModelReader extends LazyLogging { + + def read(stream: InputStream): PhenoPacket = { + val manager = OWLManager.createOWLOntologyManager() + val ont = manager.loadOntologyFromOntologyDocument(stream) + stream.close() + createPhenoPacket(ont) + } + + def createPhenoPacket(ont: OWLOntology): PhenoPacket = { + val builder = PhenoPacket.newBuilder() + ont.getOntologyID.getOntologyIRI.asScala.foreach(iri => builder.id(iri.toString)) + val associationsAndAnnotations = (for { + ObjectPropertyAssertion(annotations, HasPart, subj: OWLNamedIndividual, obj: OWLNamedIndividual) <- ont.getAxioms(AxiomType.OBJECT_PROPERTY_ASSERTION).asScala + } yield Map(Association(subj, obj) -> annotations)).reduce(_ |+| _) + var diseases = Set.empty[Disease] + for { + (Association(diseaseInd, phenotypeInd), annotations) <- associationsAndAnnotations + } { + val allDiseaseTypes = findOWLTypes(diseaseInd, ont) + if (allDiseaseTypes.size > 1) { + logger.warn(s"More than one type found for disease $diseaseInd; keeping only one to use as the entity ID.") + } + allDiseaseTypes.headOption.foreach { diseaseClass => + val disease = new Disease() + disease.setId(diseaseClass.getIRI.toString) + findLabel(diseaseClass, ont).foreach(disease.setLabel) + findDescription(diseaseInd, ont).foreach(disease.setLabel) + diseases += disease + val phenotype = new Phenotype() + updateAsClassInstance(phenotype, phenotypeInd, ont) + findFrequency(phenotypeInd, ont).foreach(phenotype.setFrequency) + findSeverity(phenotypeInd, ont).foreach(phenotype.setSeverity) + findTimeOfOnset(phenotypeInd, ont).foreach(phenotype.setTimeOfOnset) + findTimeOfOffset(phenotypeInd, ont).foreach(phenotype.setTimeOfFinishing) + val associationBuilder = new PhenotypeAssociation.Builder(phenotype) + associationBuilder.setEntity(disease) + annotations.foreach { + case Annotation(AxiomHasEvidence, evidence: IRI) => associationBuilder.addEvidence(toEvidence(Individual(evidence), ont)) + case Annotation(DCContributor, contributor ^^ _) => associationBuilder.setContributorId(contributor) + case Annotation(DCDate, date ^^ _) => associationBuilder.setDate(date) + case _ => + } + builder.addPhenotypeAssociation(associationBuilder.build()) + } + } + builder.setDiseases(diseases.toSeq.asJava) + builder.build() + } + + private def toEvidence(ind: OWLNamedIndividual, ont: OWLOntology): Evidence = { + val evidence = new Evidence() + updateAsClassInstance(evidence, ind, ont) + val publications = for { + ObjectPropertyAssertion(_, HasSupportingReference, _, reference: IRI) <- ont.getObjectPropertyAssertionAxioms(ind).asScala + } yield { + val pubBuilder = new Publication.Builder() + pubBuilder.setId(reference.toString) + findLabel(Individual(reference), ont).foreach(pubBuilder.setTitle) + pubBuilder.build() + } + evidence.setSupportingPublications(publications.toSeq.asJava) + evidence + } + + private def toOntologyClass(term: OWLClass, ont: OWLOntology): OntologyClass = { + val classBuilder = new OntologyClass.Builder(term.getIRI.toString) + findLabel(term, ont).foreach(classBuilder.setLabel) + classBuilder.build() + } + + private def findLabel(item: OWLEntity, ont: OWLOntology): Option[String] = (for { + AnnotationAssertion(_, RDFSLabel, _, label ^^ _) <- ont.getAnnotationAssertionAxioms(item.getIRI).asScala + } yield label).headOption + + private def findDescription(item: OWLEntity, ont: OWLOntology): Option[String] = (for { + AnnotationAssertion(_, DCDescription, _, desc ^^ _) <- ont.getAnnotationAssertionAxioms(item.getIRI).asScala + } yield desc).headOption + + private def findFrequency(phenotypeInd: OWLNamedIndividual, ont: OWLOntology): Option[ConditionFrequency] = (for { + ObjectPropertyAssertion(_, ConditionToFrequency, _, frequencyInd: OWLNamedIndividual) <- ont.getObjectPropertyAssertionAxioms(phenotypeInd).asScala + } yield { + val frequency = new ConditionFrequency() + updateAsClassInstance(frequency, frequencyInd, ont) + frequency + }).headOption + + private def findSeverity(phenotypeInd: OWLNamedIndividual, ont: OWLOntology): Option[ConditionSeverity] = (for { + ObjectPropertyAssertion(_, ConditionToSeverity, _, severityInd: OWLNamedIndividual) <- ont.getObjectPropertyAssertionAxioms(phenotypeInd).asScala + } yield { + val severity = new ConditionSeverity() + updateAsClassInstance(severity, severityInd, ont) + severity + }).headOption + + private def findTimeOfOnset(phenotypeInd: OWLNamedIndividual, ont: OWLOntology): Option[TemporalRegion] = (for { + ObjectPropertyAssertion(_, ExistenceStartsDuring, _, onsetInd: OWLNamedIndividual) <- ont.getObjectPropertyAssertionAxioms(phenotypeInd).asScala + } yield { + toTemporalRegion(onsetInd, ont) + }).headOption + + private def findTimeOfOffset(phenotypeInd: OWLNamedIndividual, ont: OWLOntology): Option[TemporalRegion] = (for { + ObjectPropertyAssertion(_, ExistenceEndsDuring, _, offsetInd: OWLNamedIndividual) <- ont.getObjectPropertyAssertionAxioms(phenotypeInd).asScala + } yield { + toTemporalRegion(offsetInd, ont) + }).headOption + + private def toTemporalRegion(regionInd: OWLNamedIndividual, ont: OWLOntology): TemporalRegion = { + val region = new TemporalRegion() + updateAsClassInstance(region, regionInd, ont) + for { + ObjectPropertyAssertion(_, TemporalRegionToStart, _, start ^^ _) <- ont.getObjectPropertyAssertionAxioms(regionInd).asScala + } { + region.setStartTime(start) + } + for { + ObjectPropertyAssertion(_, TemporalRegionToEnd, _, end ^^ _) <- ont.getObjectPropertyAssertionAxioms(regionInd).asScala + } { + region.setEndTime(end) + } + region + + } + + private def updateAsClassInstance(instance: ClassInstance, ind: OWLNamedIndividual, ont: OWLOntology): Unit = { + findDescription(ind, ont).foreach(instance.setDescription) + instance.setTypes(findTypes(ind, ont).toSeq.asJava) + instance.setNegatedTypes(findNegatedTypes(ind, ont).toSeq.asJava) + } + + private def findTypes(individual: OWLNamedIndividual, ont: OWLOntology): Set[OntologyClass] = + findOWLTypes(individual, ont).map(toOntologyClass(_, ont)) + + private def findOWLTypes(individual: OWLNamedIndividual, ont: OWLOntology): Set[OWLClass] = for { + ClassAssertion(_, owlClass: OWLClass, subj) <- ont.getClassAssertionAxioms(individual).asScala.toSet + } yield owlClass + + private def findNegatedTypes(individual: OWLNamedIndividual, ont: OWLOntology): Set[OntologyClass] = + findNegatedOWLTypes(individual, ont).map(toOntologyClass(_, ont)) + + private def findNegatedOWLTypes(individual: OWLNamedIndividual, ont: OWLOntology): Set[OWLClass] = for { + ClassAssertion(_, ObjectComplementOf(owlClass: OWLClass), subj) <- ont.getClassAssertionAxioms(individual).asScala.toSet + } yield owlClass + + private case class Association(subj: OWLNamedIndividual, obj: OWLNamedIndividual) + + private implicit class OptionalOption[T](val self: Optional[T]) extends AnyVal { + + def asScala: Option[T] = if (self.isPresent) Some(self.get) else None + + } + +} \ No newline at end of file diff --git a/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelVocabulary.scala b/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelVocabulary.scala new file mode 100644 index 0000000..5c77e7b --- /dev/null +++ b/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelVocabulary.scala @@ -0,0 +1,29 @@ +package org.phenopackets.pxftools.util + +import org.phenoscape.scowl._ +import org.semanticweb.owlapi.apibinding.OWLManager + +import com.hp.hpl.jena.vocabulary.DC_11 + +object NoctuaModelVocabulary { + + private val factory = OWLManager.getOWLDataFactory + + val DCTitle = AnnotationProperty(DC_11.title.getURI) + val DCDescription = AnnotationProperty(DC_11.description.getURI) + val DCSource = AnnotationProperty(DC_11.source.getURI) + val DCDate = AnnotationProperty(DC_11.date.getURI) + val DCContributor = AnnotationProperty(DC_11.contributor.getURI) + val RDFSComment = factory.getRDFSComment + val HasPart = ObjectProperty("http://purl.obolibrary.org/obo/BFO_0000051") + val ConditionToFrequency = ObjectProperty("http://example.org/condition_to_frequency") //FIXME + val ConditionToSeverity = ObjectProperty("http://example.org/condition_to_severity") //FIXME + val TemporalRegionToStart = DataProperty("http://example.org/temporal_region_start_at") //FIXME + val TemporalRegionToEnd = DataProperty("http://example.org/temporal_region_end_at") //FIXME + val ExistenceStartsDuring = ObjectProperty("http://purl.obolibrary.org/obo/RO_0002488") + val ExistenceEndsDuring = ObjectProperty("http://purl.obolibrary.org/obo/RO_0002492") + val AxiomHasEvidence = AnnotationProperty("http://purl.obolibrary.org/obo/RO_0002612") + val HasSupportingReference = ObjectProperty("http://purl.obolibrary.org/obo/SEPIO_0000124") + val Publication = Class("http://purl.obolibrary.org/obo/IAO_0000311") + +} \ No newline at end of file diff --git a/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelWriter.scala b/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelWriter.scala index b98c9c6..37f341b 100644 --- a/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelWriter.scala +++ b/src/main/scala/org/phenopackets/pxftools/util/NoctuaModelWriter.scala @@ -17,6 +17,7 @@ import org.phenopackets.api.model.evidence.Evidence import org.phenopackets.api.model.evidence.Publication import org.phenopackets.api.model.ontology.ClassInstance import org.phenopackets.api.util.ContextUtil +import org.phenopackets.pxftools.util.NoctuaModelVocabulary._ import org.phenoscape.scowl._ import org.semanticweb.owlapi.apibinding.OWLManager import org.semanticweb.owlapi.formats.ManchesterSyntaxDocumentFormat @@ -28,30 +29,10 @@ import org.semanticweb.owlapi.model.OWLNamedIndividual import org.semanticweb.owlapi.model.OWLOntology import com.github.jsonldjava.core.Context -import com.hp.hpl.jena.vocabulary.DC_11 import com.typesafe.scalalogging.LazyLogging object NoctuaModelWriter extends LazyLogging { - private val factory = OWLManager.getOWLDataFactory - private val DCTitle = AnnotationProperty(DC_11.title.getURI) - private val DCDescription = AnnotationProperty(DC_11.description.getURI) - private val DCSource = AnnotationProperty(DC_11.source.getURI) - private val DCDate = AnnotationProperty(DC_11.date.getURI) - private val DCContributor = AnnotationProperty(DC_11.contributor.getURI) - private val RDFSComment = factory.getRDFSComment - private val RDFSLabel = factory.getRDFSLabel - private val HasPart = ObjectProperty("http://purl.obolibrary.org/obo/BFO_0000051") - private val ConditionToFrequency = ObjectProperty("http://example.org/condition_to_frequency") //FIXME - private val ConditionToSeverity = ObjectProperty("http://example.org/condition_to_severity") //FIXME - private val TemporalRegionToStart = DataProperty("http://example.org/temporal_region_start_at") //FIXME - private val TemporalRegionToEnd = DataProperty("http://example.org/temporal_region_end_at") //FIXME - private val ExistenceStartsDuring = ObjectProperty("http://purl.obolibrary.org/obo/RO_0002488") - private val ExistenceEndsDuring = ObjectProperty("http://purl.obolibrary.org/obo/RO_0002492") - private val AxiomHasEvidence = AnnotationProperty("http://purl.obolibrary.org/obo/RO_0002612") - private val HasSupportingReference = ObjectProperty("http://purl.obolibrary.org/obo/SEPIO_0000124") - private val Publication = Class("http://purl.obolibrary.org/obo/IAO_0000311") - def fromPhenoPacket(packet: PhenoPacket): OWLOntology = { val manager = OWLManager.createOWLOntologyManager() val context = ContextUtil.getJSONLDContext(packet) @@ -214,7 +195,7 @@ object NoctuaModelWriter extends LazyLogging { var axioms = Set.empty[OWLAxiom] val pubIndividual = Individual(iri(publication.getId, context)) axioms += Declaration(pubIndividual) - axioms += pubIndividual Type Publication + axioms += pubIndividual Type org.phenopackets.pxftools.util.NoctuaModelVocabulary.Publication axioms ++= Option(publication.getTitle).map(pubIndividual Annotation (DCTitle, _)) (pubIndividual, axioms) } @@ -244,7 +225,7 @@ object NoctuaModelWriter extends LazyLogging { private def iri(id: String, context: Context): IRI = { val expanded = ContextUtil.expandIdentifierAsValue(id, context) val expandedID = if (expanded.contains(":")) { - if (!expanded.startsWith("http")) logger.warn(s"No HTTP URI found for identifer: $id") + if (!expanded.startsWith("http")) logger.warn(s"No HTTP URI found for identifier: $id") expanded } else { logger.warn(s"No URI found for identifer: $id")