Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --drop-axiom-annotations feature with value constraints #1193

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed
- Updated `duplicate_exact_syonym` [`report`] query to be case-insensitive and ignore synoyms annotated as abbreviation or acronym synonym types [#1179]
- Extend `--drop-axiom-annotations` option to support value-specific removal of axiom annotations [#1193]
- Add `--enforce-obo-format`, `--exclude-named-classes` and `--include-subclass-of` features to relax command [#1060, #1183]

### Fixed
Expand Down
2,526 changes: 2,526 additions & 0 deletions docs/examples/filter_annotations_drop_axioms.owl

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions docs/remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,11 @@ Create a "base" subset by removing external axioms (alternatively, use `filter -
--signature true \
--output results/filter_annotations.owl

Create a "base" subset in which axiom annotations involving IAO:0000117 and IAO:0000119 are removed:
Extracts a base module, then removing _all_ `oboInOwl:hasDbXref` annotations on the remaining axioms, and all `oboInOwl:hasDbXref` axiom annotations whose value matches the regular expression `GO.*`:

robot remove --input template.owl \
--base-iri http://example.com/ \
robot remove --input uberon_module.owl \
--base-iri http://purl.obolibrary.org/obo/UBERON_ \
--axioms external \
--drop-axiom-annotations IAO:0000117 \
--drop-axiom-annotations IAO:0000119 \
--output results/template-drop-axiom-remove.owl


--drop-axiom-annotations oboInOwl:source=~'GO.*' \
--drop-axiom-annotations oboInOwl:hasDbXref \
--output results/filter_annotations_drop_axioms.owl
Original file line number Diff line number Diff line change
Expand Up @@ -1121,4 +1121,36 @@ public static List<OWLOntology> getInputOntologies(
}
return inputOntologies;
}

/**
* Given an IOHelper and a list of unparsed patterns to select annotation assertions to be dropped
* build a Map of property IRIs, and value patterns for further processing.
*
* @param ioHelper the IOHelper to load the ontology with
* @param dropParameters The list of unparsed command line parameters describing the annotations
* to select
* @return A map of IRI to annotation value to drop
*/
public static Map<IRI, String> createAnnotationToDropMap(
IOHelper ioHelper, List<String> dropParameters) throws IOException {
Map<IRI, String> annotationsToDrop = new HashMap<>();

for (String dropParameter : dropParameters) {
if (dropParameter.equalsIgnoreCase("all")) {
// ignore, we communicate this through the dropParameters list
} else if (dropParameter.contains("=")) {
String[] parts = dropParameter.split("=");
String annotationIRI = parts[0];
String annotationValue = parts[1];
IRI iri =
CommandLineHelper.maybeCreateIRI(ioHelper, annotationIRI, "drop-axiom-annotations");
annotationsToDrop.put(iri, annotationValue);
} else {
IRI iri =
CommandLineHelper.maybeCreateIRI(ioHelper, dropParameter, "drop-axiom-annotations");
annotationsToDrop.put(iri, null);
}
}
return annotationsToDrop;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.semanticweb.owlapi.apibinding.OWLManager;
Expand Down Expand Up @@ -167,13 +167,9 @@ public CommandState execute(CommandState state, String[] args) throws Exception

List<String> dropParameters =
CommandLineHelper.getOptionalValues(line, "drop-axiom-annotations");
List<IRI> annotationsToDrop =
dropParameters.stream()
.filter(s -> !s.equalsIgnoreCase("all"))
.map(
curie ->
CommandLineHelper.maybeCreateIRI(ioHelper, curie, "drop-axiom-annotations"))
.collect(Collectors.toList());

Map<IRI, String> annotationsToDrop =
CommandLineHelper.createAnnotationToDropMap(ioHelper, dropParameters);

// Use the select statements to get a set of objects to filter
Set<OWLObject> relatedObjects =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.obolibrary.robot;

import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.semanticweb.owlapi.apibinding.OWLManager;
Expand Down Expand Up @@ -157,13 +156,9 @@ public CommandState execute(CommandState state, String[] args) throws Exception

List<String> dropParameters =
CommandLineHelper.getOptionalValues(line, "drop-axiom-annotations");
List<IRI> annotationsToDrop =
dropParameters.stream()
.filter(s -> !s.equalsIgnoreCase("all"))
.map(
curie ->
CommandLineHelper.maybeCreateIRI(ioHelper, curie, "drop-axiom-annotations"))
.collect(Collectors.toList());

Map<IRI, String> annotationsToDrop =
CommandLineHelper.createAnnotationToDropMap(ioHelper, dropParameters);

// Get the objects to remove
Set<OWLObject> relatedObjects = getObjects(line, ioHelper, ontology, selectGroups);
Expand Down
89 changes: 83 additions & 6 deletions robot-core/src/main/java/org/obolibrary/robot/OntologyHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.StringWriter;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.obolibrary.robot.checks.InvalidReferenceChecker;
import org.obolibrary.robot.export.RendererType;
import org.obolibrary.robot.providers.QuotedAnnotationValueShortFormProvider;
Expand Down Expand Up @@ -1658,13 +1660,29 @@ public static void trimOntology(OWLOntology ontology) {
* Removes all of the axiom annotations for the given annotation properties.
*
* @param ontology OWLOntology to remove axiom annotations
* @param properties Annotation property IRIs to remove related axiom annotations.
* @param properties List of annotation property IRIs to remove related axiom annotations.
*/
public static void removeAxiomAnnotations(OWLOntology ontology, List<IRI> properties) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks a public interface by removing the method with this signature. We try hard to avoid that. Can the old method/signature (with List<IRI>) be maintained (maybe as a thin wrapper) while the new method (with Map<IRI, String>) is added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Thanks you!

Map<IRI, String> annotationsToDrop = new HashMap<>();
properties.forEach(iri -> annotationsToDrop.put(iri, null));
removeAxiomAnnotations(ontology, annotationsToDrop);
}

/**
* Removes all of the axiom annotations for the given annotation properties.
*
* @param ontology OWLOntology to remove axiom annotations
* @param properties Annotation property IRIs to remove related axiom annotations.
*/
public static void removeAxiomAnnotations(
OWLOntology ontology, Map<IRI, String> annotationsToDrop) {
OWLDataFactory owlDataFactory = ontology.getOWLOntologyManager().getOWLDataFactory();
properties.stream()
.map(iri -> owlDataFactory.getOWLAnnotationProperty(iri))
.forEach(p -> OntologyHelper.removeAxiomAnnotations(ontology, p));

for (IRI iri : annotationsToDrop.keySet()) {
OWLAnnotationProperty property = owlDataFactory.getOWLAnnotationProperty(iri);
String value = annotationsToDrop.get(iri);
OntologyHelper.removeAxiomAnnotations(ontology, property, value);
}
}

/**
Expand All @@ -1674,11 +1692,57 @@ public static void removeAxiomAnnotations(OWLOntology ontology, List<IRI> proper
* @param property Annotation property to remove related axiom annotations.
*/
public static void removeAxiomAnnotations(OWLOntology ontology, OWLAnnotationProperty property) {
removeAxiomAnnotations(ontology, property, null);
}

/**
* Removes all of the axiom annotations for the given annotation property and an optional value
* pattern The value pattern should follow
* https://robot.obolibrary.org/remove#pattern-subset-selectors
*
* @param ontology OWLOntology to remove axiom annotations
* @param property Annotation property to remove related axiom annotations.
* @param value a value or value pattern the restrict what is deleted. Can be null.
*/
public static void removeAxiomAnnotations(
OWLOntology ontology, OWLAnnotationProperty property, String value) {
OWLOntologyManager manager = ontology.getOWLOntologyManager();
for (OWLAxiom axiom : ontology.getAxioms(Imports.EXCLUDED)) {
Set<OWLAnnotation> annotationsToRemove = axiom.getAnnotations(property);
Set<OWLAnnotation> annotationsToRemove = new HashSet<>();
Set<OWLAnnotation> allAxiomAnnotations = axiom.getAnnotations();
for (OWLAnnotation annotation : allAxiomAnnotations) {
if (annotation.getProperty().equals(property)) {
String annotationValue = getAnnotationValueAsStringOrNull(annotation);
if (value == null) {
// If there is no value set, we assume all annotations for the property should be
// removed
annotationsToRemove.add(annotation);
} else if (value.startsWith("~")) {
// If a value is set, and it starts with ~, we assume it is a regex pattern
String patternString = value.substring(1).replace("'", "");
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(annotationValue);
if (matcher.find()) {
annotationsToRemove.add(annotation);
}
} else {
// If a value is set, and it does not start with ~, we assume its an exact value
String processed_value = value.replace("'", "");
if (annotationValue.equals(processed_value)) {
annotationsToRemove.add(annotation);
} else {
logger.warn(
"Annotation assertion value pattern must start with ~' to indicate a regex pattern"
+ "or else correspond to the exact value. "
+ "The value is: "
+ value
+ ". Other patterns are not supported.");
}
}
}
}
if (!annotationsToRemove.isEmpty()) {
Set<OWLAnnotation> axiomAnnotations = axiom.getAnnotations();
Set<OWLAnnotation> axiomAnnotations = new HashSet<>(allAxiomAnnotations);
axiomAnnotations.removeAll(annotationsToRemove);
OWLAxiom cleanedAxiom =
axiom.getAxiomWithoutAnnotations().getAnnotatedAxiom(axiomAnnotations);
Expand All @@ -1689,6 +1753,19 @@ public static void removeAxiomAnnotations(OWLOntology ontology, OWLAnnotationPro
}
}

private static String getAnnotationValueAsStringOrNull(OWLAnnotation annotation) {
OWLAnnotationValue v = annotation.getValue();
if (v.isIRI()) {
return v.asIRI().get().toString();
} else if (v.isLiteral()) {
return v.asLiteral().get().getLiteral().toString();
} else if (v.isIndividual()) {
return v.asAnonymousIndividual().get().toString();
} else {
return null;
}
}

/**
* Removes all axiom annotations from the given ontology.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2409,7 +2409,7 @@ private static void spanGapsHelper(
* @param dropParameters list of drop-axiom-annotations parameters
*/
public static void dropAxiomAnnotations(
OWLOntology ontology, List<IRI> annotationsToDrop, List<String> dropParameters) {
OWLOntology ontology, Map<IRI, String> annotationsToDrop, List<String> dropParameters) {
if (dropParameters.stream().anyMatch(x -> x.equalsIgnoreCase("all"))) {
OntologyHelper.removeAllAxiomAnnotations(ontology);
} else {
Expand Down
Loading