From afb315d0b4bf5f06149ed1db1f6f9e789dfa5982 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Mon, 25 Dec 2023 13:10:55 +0100 Subject: [PATCH] #2696 - Document-level recommendations - Towards handling extraction as part of the suggestion supports --- .../sidebar/MetadataSuggestionSupport.java | 49 +++++++----- .../api/recommender/ExtractionContext.java | 29 ------- .../relation/RelationSuggestionSupport.java | 75 +++++++++++-------- .../service/SuggestionExtraction.java | 37 ++++----- .../span/SpanSuggestionSupport.java | 72 +++++++++--------- 5 files changed, 123 insertions(+), 139 deletions(-) diff --git a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/MetadataSuggestionSupport.java b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/MetadataSuggestionSupport.java index 5175775f7a6..70c2aad4103 100644 --- a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/MetadataSuggestionSupport.java +++ b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/MetadataSuggestionSupport.java @@ -23,6 +23,7 @@ import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.REJECTED; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -33,7 +34,6 @@ import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.jcas.cas.AnnotationBase; -import org.apache.uima.jcas.cas.TOP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; @@ -280,26 +280,35 @@ public Optional getRenderer() return Optional.empty(); } - public static void extractSuggestion(ExtractionContext ctx, TOP predictedFS) + public static List extractSuggestions(ExtractionContext ctx) { - var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); - var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), ctx.isMultiLabels()); - var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); - var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); - - for (var label : labels) { - var suggestion = MetadataSuggestion.builder() // - .withId(MetadataSuggestion.NEW_ID) // - .withGeneration(ctx.getGeneration()) // - .withRecommender(ctx.getRecommender()) // - .withDocument(ctx.getDocument()) // - .withLabel(label) // - .withUiLabel(label) // - .withScore(score) // - .withScoreExplanation(scoreExplanation) // - .withAutoAcceptMode(autoAcceptMode) // - .build(); - ctx.getResult().add(suggestion); + var result = new ArrayList(); + for (var predictedFS : ctx.getPredictionCas().select(ctx.getPredictedType())) { + if (!predictedFS.getBooleanValue(ctx.getPredictionFeature())) { + continue; + } + + var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); + var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), + ctx.isMultiLabels()); + var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); + var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); + + for (var label : labels) { + var suggestion = MetadataSuggestion.builder() // + .withId(MetadataSuggestion.NEW_ID) // + .withGeneration(ctx.getGeneration()) // + .withRecommender(ctx.getRecommender()) // + .withDocument(ctx.getDocument()) // + .withLabel(label) // + .withUiLabel(label) // + .withScore(score) // + .withScoreExplanation(scoreExplanation) // + .withAutoAcceptMode(autoAcceptMode) // + .build(); + result.add(suggestion); + } } + return result; } } diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/recommender/ExtractionContext.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/recommender/ExtractionContext.java index 58777e21480..6d333fc1889 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/recommender/ExtractionContext.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/recommender/ExtractionContext.java @@ -21,13 +21,8 @@ import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_IS_PREDICTION; import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_SCORE_EXPLANATION_SUFFIX; import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_SCORE_SUFFIX; -import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_SOURCE; -import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_TARGET; import static org.apache.uima.cas.CAS.TYPE_NAME_STRING_ARRAY; -import java.util.ArrayList; -import java.util.List; - import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.cas.Type; @@ -35,7 +30,6 @@ import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; -import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; public final class ExtractionContext @@ -56,8 +50,6 @@ public final class ExtractionContext private final Type predictedType; private final Feature labelFeature; - private final Feature sourceFeature; - private final Feature targetFeature; private final Feature scoreFeature; private final Feature scoreExplanationFeature; private final Feature modeFeature; @@ -65,8 +57,6 @@ public final class ExtractionContext private final boolean isMultiLabels; - private final List result; - public ExtractionContext(int aGeneration, Recommender aRecommender, SourceDocument aDocument, CAS aOriginalCas, CAS aPredictionCas) { @@ -84,8 +74,6 @@ public ExtractionContext(int aGeneration, Recommender aRecommender, SourceDocume predictedType = CasUtil.getType(aPredictionCas, typeName); labelFeature = predictedType.getFeatureByBaseName(featureName); - sourceFeature = predictedType.getFeatureByBaseName(FEAT_REL_SOURCE); - targetFeature = predictedType.getFeatureByBaseName(FEAT_REL_TARGET); scoreFeature = predictedType.getFeatureByBaseName(featureName + FEATURE_NAME_SCORE_SUFFIX); scoreExplanationFeature = predictedType .getFeatureByBaseName(featureName + FEATURE_NAME_SCORE_EXPLANATION_SUFFIX); @@ -93,8 +81,6 @@ public ExtractionContext(int aGeneration, Recommender aRecommender, SourceDocume .getFeatureByBaseName(featureName + FEATURE_NAME_AUTO_ACCEPT_MODE_SUFFIX); predictionFeature = predictedType.getFeatureByBaseName(FEATURE_NAME_IS_PREDICTION); isMultiLabels = TYPE_NAME_STRING_ARRAY.equals(labelFeature.getRange().getName()); - - result = new ArrayList(); } public int getGeneration() @@ -152,16 +138,6 @@ public Feature getLabelFeature() return labelFeature; } - public Feature getSourceFeature() - { - return sourceFeature; - } - - public Feature getTargetFeature() - { - return targetFeature; - } - public Feature getScoreFeature() { return scoreFeature; @@ -186,9 +162,4 @@ public boolean isMultiLabels() { return isMultiLabels; } - - public List getResult() - { - return result; - } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java index 98db34b99ee..78721cf2d0a 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java @@ -39,7 +39,6 @@ import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; -import org.apache.uima.jcas.cas.TOP; import org.apache.uima.jcas.tcas.Annotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -377,40 +376,52 @@ public Optional getRenderer() featureSupportRegistry)); } - public static void extractSuggestion(ExtractionContext ctx, TOP predictedFS) + public static List extractSuggestions(ExtractionContext ctx) { - var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); - var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), ctx.isMultiLabels()); - var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); - var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); - - var source = (AnnotationFS) predictedFS.getFeatureValue(ctx.getSourceFeature()); - var target = (AnnotationFS) predictedFS.getFeatureValue(ctx.getTargetFeature()); - - var originalSource = findEquivalentSpan(ctx.getOriginalCas(), source); - var originalTarget = findEquivalentSpan(ctx.getOriginalCas(), target); - if (originalSource.isEmpty() || originalTarget.isEmpty()) { - LOG.debug("Unable to find source or target of predicted relation in original CAS"); - return; - } + // TODO Use adapter instead - once the method is no longer static + var sourceFeature = ctx.getPredictedType().getFeatureByBaseName(FEAT_REL_SOURCE); + var targetFeature = ctx.getPredictedType().getFeatureByBaseName(FEAT_REL_TARGET); + + var result = new ArrayList(); + for (var predictedFS : ctx.getPredictionCas().select(ctx.getPredictedType())) { + if (!predictedFS.getBooleanValue(ctx.getPredictionFeature())) { + continue; + } - var position = new RelationPosition(originalSource.get(), originalTarget.get()); - - for (var label : labels) { - var suggestion = RelationSuggestion.builder() // - .withId(RelationSuggestion.NEW_ID) // - .withGeneration(ctx.getGeneration()) // - .withRecommender(ctx.getRecommender()) // - .withDocumentName(ctx.getDocument().getName()) // - .withPosition(position) // - .withLabel(label) // - .withUiLabel(label) // - .withScore(score) // - .withScoreExplanation(scoreExplanation) // - .withAutoAcceptMode(autoAcceptMode) // - .build(); - ctx.getResult().add(suggestion); + var source = (AnnotationFS) predictedFS.getFeatureValue(sourceFeature); + var target = (AnnotationFS) predictedFS.getFeatureValue(targetFeature); + + var originalSource = findEquivalentSpan(ctx.getOriginalCas(), source); + var originalTarget = findEquivalentSpan(ctx.getOriginalCas(), target); + if (originalSource.isEmpty() || originalTarget.isEmpty()) { + LOG.debug("Unable to find source or target of predicted relation in original CAS"); + continue; + } + + var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); + var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), + ctx.isMultiLabels()); + var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); + var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); + var position = new RelationPosition(originalSource.get(), originalTarget.get()); + + for (var label : labels) { + var suggestion = RelationSuggestion.builder() // + .withId(RelationSuggestion.NEW_ID) // + .withGeneration(ctx.getGeneration()) // + .withRecommender(ctx.getRecommender()) // + .withDocumentName(ctx.getDocument().getName()) // + .withPosition(position) // + .withLabel(label) // + .withUiLabel(label) // + .withScore(score) // + .withScoreExplanation(scoreExplanation) // + .withAutoAcceptMode(autoAcceptMode) // + .build(); + result.add(suggestion); + } } + return result; } /** diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SuggestionExtraction.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SuggestionExtraction.java index e4b0914ef31..f974cc3a0a0 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SuggestionExtraction.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SuggestionExtraction.java @@ -40,30 +40,19 @@ public static List extractSuggestions(int aGeneration, CAS var ctx = new ExtractionContext(aGeneration, aRecommender, aDocument, aOriginalCas, aPredictionCas); - for (var predictedFS : aPredictionCas.select(ctx.getPredictedType())) { - if (!predictedFS.getBooleanValue(ctx.getPredictionFeature())) { - continue; - } - - switch (ctx.getLayer().getType()) { - case SpanLayerSupport.TYPE: { - SpanSuggestionSupport.extractSuggestion(ctx, predictedFS); - break; - } - case RelationLayerSupport.TYPE: { - RelationSuggestionSupport.extractSuggestion(ctx, predictedFS); - break; - } - case DocumentMetadataLayerSupport.TYPE: { - MetadataSuggestionSupport.extractSuggestion(ctx, predictedFS); - break; - } - default: - throw new IllegalStateException( - "Unsupported layer type [" + ctx.getLayer().getType() + "]"); - } + switch (ctx.getLayer().getType()) { + case SpanLayerSupport.TYPE: { + return SpanSuggestionSupport.extractSuggestions(ctx); + } + case RelationLayerSupport.TYPE: { + return RelationSuggestionSupport.extractSuggestions(ctx); + } + case DocumentMetadataLayerSupport.TYPE: { + return MetadataSuggestionSupport.extractSuggestions(ctx); + } + default: + throw new IllegalStateException( + "Unsupported layer type [" + ctx.getLayer().getType() + "]"); } - - return ctx.getResult(); } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java index 10869bfeb86..898550b960e 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java @@ -48,7 +48,6 @@ import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.cas.text.AnnotationPredicates; import org.apache.uima.fit.util.CasUtil; -import org.apache.uima.jcas.cas.TOP; import org.apache.uima.jcas.tcas.Annotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -460,42 +459,47 @@ public Optional getRenderer() featureSupportRegistry, recommenderProperties)); } - public static void extractSuggestion(ExtractionContext ctx, TOP predictedFS) + public static List extractSuggestions(ExtractionContext ctx) { - var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); - var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), ctx.isMultiLabels()); - var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); - var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); - - var predictedAnnotation = (Annotation) predictedFS; - var targetOffsets = getOffsets(ctx.getLayer().getAnchoringMode(), ctx.getOriginalCas(), - predictedAnnotation); - - if (targetOffsets.isEmpty()) { - LOG.debug("Prediction cannot be anchored to [{}]: {}", - ctx.getLayer().getAnchoringMode(), predictedAnnotation); - return; - } + var result = new ArrayList(); + for (var predictedFS : ctx.getPredictionCas(). select(ctx.getPredictedType())) { + if (!predictedFS.getBooleanValue(ctx.getPredictionFeature())) { + continue; + } - var offsets = targetOffsets.get(); - var coveredText = ctx.getDocumentText().substring(offsets.getBegin(), offsets.getEnd()); - - for (var label : labels) { - var suggestion = SpanSuggestion.builder() // - .withId(RelationSuggestion.NEW_ID) // - .withGeneration(ctx.getGeneration()) // - .withRecommender(ctx.getRecommender()) // - .withDocument(ctx.getDocument()) // - .withPosition(offsets) // - .withCoveredText(coveredText) // - .withLabel(label) // - .withUiLabel(label) // - .withScore(score) // - .withScoreExplanation(scoreExplanation) // - .withAutoAcceptMode(autoAcceptMode) // - .build(); - ctx.getResult().add(suggestion); + var anchoringMode = ctx.getLayer().getAnchoringMode(); + var targetOffsets = getOffsets(anchoringMode, ctx.getOriginalCas(), predictedFS); + if (targetOffsets.isEmpty()) { + LOG.debug("Prediction cannot be anchored to [{}]: {}", anchoringMode, predictedFS); + continue; + } + + var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); + var labels = getPredictedLabels(predictedFS, ctx.getLabelFeature(), + ctx.isMultiLabels()); + var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); + var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); + var offsets = targetOffsets.get(); + var coveredText = ctx.getDocumentText().substring(offsets.getBegin(), offsets.getEnd()); + + for (var label : labels) { + var suggestion = SpanSuggestion.builder() // + .withId(RelationSuggestion.NEW_ID) // + .withGeneration(ctx.getGeneration()) // + .withRecommender(ctx.getRecommender()) // + .withDocument(ctx.getDocument()) // + .withPosition(offsets) // + .withCoveredText(coveredText) // + .withLabel(label) // + .withUiLabel(label) // + .withScore(score) // + .withScoreExplanation(scoreExplanation) // + .withAutoAcceptMode(autoAcceptMode) // + .build(); + result.add(suggestion); + } } + return result; } /**