Skip to content

Commit

Permalink
Merge branch 'main' into feature/4399-Allow-OpenNLP-Multi-Token-Seque…
Browse files Browse the repository at this point in the history
…nce-Classifier-to-work-for-cross-sentence-layers

* main:
  #4402 - Some suggestions are not hidden even though they cannot be accepted
  #4406 - Useless "not in tagset" message on suggestions without a label
  #4406 - Useless "not in tagset" message on suggestions without a label
  #4404 - Some suggestions are not shown right away
  • Loading branch information
reckart committed Dec 28, 2023
2 parents 6320dae + 1a23df2 commit b8f2b0a
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public List<VLazyDetailGroup> lookupLazyDetails(AnnotationFeature aFeature, Obje
var value = (String) v;
var tag = schemaService.getTag(value, aFeature.getTagset());

if (aFeature.getTagset() != null && tag.isEmpty()) {
if (aFeature.getTagset() != null && !tag.isEmpty()) {
results.addDetail(new VLazyDetail(value, "Tag not in tagset"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public List<VLazyDetailGroup> lookupLazyDetails(AnnotationFeature aFeature, Obje
if (aValue instanceof String) {
var value = (String) aValue;
var tag = schemaService.getTag(value, aFeature.getTagset());
if (aFeature.getTagset() != null && tag.isEmpty()) {
if (aFeature.getTagset() != null && !tag.isEmpty()) {
return asList(new VLazyDetailGroup(new VLazyDetail(value, "Tag not in tagset")));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import static de.tudarmstadt.ukp.clarin.webanno.model.Mode.ANNOTATION;
import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.MAIN_EDITOR;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.wicket.event.Broadcast.BREADTH;
Expand Down Expand Up @@ -386,22 +385,24 @@ public List<VLazyDetailGroup> lookupLazyDetails(SourceDocument aDocument, User a
var sortedByScore = group.get().bestSuggestionsByFeatureAndLabel(pref, aFeature.getName(),
label);

var details = new VLazyDetailGroup();
var detailGroups = new ArrayList<VLazyDetailGroup>();
for (var ao : sortedByScore) {
var items = new ArrayList<String>();
var detailGroup = new VLazyDetailGroup(ao.getRecommenderName());
// detailGroup.addDetail(new VLazyDetail("Age", String.valueOf(ao.getAge())));
if (ao.getScore() > 0.0d) {
items.add(String.format("Score: %.2f", ao.getScore()));
detailGroup
.addDetail(new VLazyDetail("Score", String.format("%.2f", ao.getScore())));
}
if (ao.getScoreExplanation().isPresent()) {
items.add("Explanation: " + ao.getScoreExplanation().get());
detailGroup
.addDetail(new VLazyDetail("Explanation", ao.getScoreExplanation().get()));
}
if (pref.isShowAllPredictions() && !ao.isVisible()) {
items.add("Hidden: " + ao.getReasonForHiding());
detailGroup.addDetail(new VLazyDetail("Hidden", ao.getReasonForHiding()));
}
details.addDetail(
new VLazyDetail(ao.getRecommenderName(), "\n" + String.join("\n", items)));
detailGroups.add(detailGroup);
}

return asList(details);
return detailGroups;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ private void computePredictions(LazyCas aOriginalCas,

// Make sure we have the latest recommender config from the DB - the one
// from the active recommenders list may be outdated
Recommender recommender = aEvaluatedRecommender.getRecommender();
var recommender = aEvaluatedRecommender.getRecommender();
try {
recommender = getRecommender(recommender.getId());
}
Expand All @@ -1454,7 +1454,7 @@ private void computePredictions(LazyCas aOriginalCas,
return;
}

Optional<RecommenderContext> context = getContext(aSessionOwner.getUsername(), recommender);
var context = getContext(aSessionOwner.getUsername(), recommender);

if (!context.isPresent()) {
aPredictions.log(LogMessage.info(recommender.getName(),
Expand All @@ -1465,15 +1465,15 @@ private void computePredictions(LazyCas aOriginalCas,
return;
}

Optional<RecommendationEngineFactory<?>> maybeFactory = getRecommenderFactory(recommender);
var maybeFactory = getRecommenderFactory(recommender);

if (maybeFactory.isEmpty()) {
LOG.warn("{}[{}]: No factory found - skipping recommender", aSessionOwner,
recommender.getName());
return;
}

RecommendationEngineFactory<?> factory = maybeFactory.get();
var factory = maybeFactory.get();

// Check that configured layer and feature are accepted
// by this type of recommender
Expand All @@ -1488,13 +1488,13 @@ private void computePredictions(LazyCas aOriginalCas,
// We lazily load the CAS only at this point because that allows us to skip
// loading the CAS entirely if there is no enabled layer or recommender.
// If the CAS cannot be loaded, then we skip to the next document.
CAS originalCas = aOriginalCas.get();
var originalCas = aOriginalCas.get();
predictionBegin = aPredictionBegin < 0 ? 0 : aPredictionBegin;
predictionEnd = aPredictionEnd < 0 ? originalCas.getDocumentText().length()
: aPredictionEnd;

try {
RecommendationEngine engine = factory.build(recommender);
var engine = factory.build(recommender);

if (!engine.isReadyForPrediction(context.get())) {
aPredictions.log(LogMessage.info(recommender.getName(),
Expand Down Expand Up @@ -1808,11 +1808,11 @@ void generateSuggestions(Predictions aIncomingPredictions, PredictionContext aCt

static ReconciliationResult reconcile(Predictions aActivePredictions, SourceDocument aDocument,
Recommender recommender, Range predictedRange,
List<AnnotationSuggestion> aNewProtoSuggesitons)
List<AnnotationSuggestion> aNewProtoSuggestions)
{
if (aActivePredictions == null) {
return new ReconciliationResult(aNewProtoSuggesitons.size(), 0, 0,
aNewProtoSuggesitons);
return new ReconciliationResult(aNewProtoSuggestions.size(), 0, 0,
aNewProtoSuggestions);
}

var reconciledSuggestions = new LinkedHashSet<AnnotationSuggestion>();
Expand All @@ -1826,7 +1826,7 @@ static ReconciliationResult reconcile(Predictions aActivePredictions, SourceDocu
.filter(s -> s.coveredBy(predictedRange)) //
.collect(groupingBy(AnnotationSuggestion::getPosition));

for (var newSuggestion : aNewProtoSuggesitons) {
for (var newSuggestion : aNewProtoSuggestions) {
var existingSuggestions = existingSuggestionsByPosition
.getOrDefault(newSuggestion.getPosition(), emptyList()).stream() //
.filter(s -> Objects.equals(s.getLabel(), newSuggestion.getLabel()) && //
Expand All @@ -1837,7 +1837,6 @@ static ReconciliationResult reconcile(Predictions aActivePredictions, SourceDocu

if (existingSuggestions.isEmpty()) {
addedSuggestions.add(newSuggestion);
reconciledSuggestions.add(newSuggestion);
continue;
}

Expand All @@ -1847,18 +1846,22 @@ static ReconciliationResult reconcile(Predictions aActivePredictions, SourceDocu
}

var existingSuggestion = existingSuggestions.get(0);
existingSuggestion.incrementAge();
agedSuggestionsCount++;
reconciledSuggestions.add(existingSuggestion);
if (!reconciledSuggestions.contains(existingSuggestion)) {
existingSuggestion.incrementAge();
agedSuggestionsCount++;
reconciledSuggestions.add(existingSuggestion);
}
}

var removedSuggestions = predictionsByRecommenderAndDocument.stream() //
.filter(s -> s.coveredBy(predictedRange)) //
.filter(s -> !reconciledSuggestions.contains(s)) //
.collect(toList());
.toList();

var finalSuggestions = new ArrayList<>(reconciledSuggestions);
finalSuggestions.addAll(addedSuggestions);
return new ReconciliationResult(addedSuggestions.size(), removedSuggestions.size(),
agedSuggestionsCount, new ArrayList<>(reconciledSuggestions));
agedSuggestionsCount, finalSuggestions);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList;
import static org.apache.uima.cas.text.AnnotationPredicates.colocated;
import static org.apache.uima.fit.util.CasUtil.getType;
import static org.apache.uima.fit.util.CasUtil.select;
Expand Down Expand Up @@ -198,7 +197,7 @@ public <T extends AnnotationSuggestion> void calculateSuggestionVisibility(Strin
return AnnotationPredicates.coveredBy(offset.getBegin(), offset.getEnd(),
aWindowBegin, aWindowEnd);
}) //
.collect(toList());
.toList();

// Get all the skipped/rejected entries for the current layer
var recordedAnnotations = learningRecordService.listLearningRecords(aSessionOwner,
Expand Down Expand Up @@ -262,77 +261,72 @@ private void hideSpanSuggestionsThatOverlapWithAnnotations(
// This iterator gives us pairs of annotations and suggestions. Note that both lists
// must be sorted in the same way. The suggestion offsets are sorted because they are
// the keys in a TreeSet - and the annotation offsets are sorted in the same way manually
var oi = new OverlapIterator(sortedAnnotationKeys, new ArrayList<>(suggestions.keySet()));
var oi = new OverlapIterator(new ArrayList<>(suggestions.keySet()), sortedAnnotationKeys);

// Bulk-hide any groups that overlap with existing annotations on the current layer
// and for the current feature
var hiddenForOverlap = new ArrayList<AnnotationSuggestion>();
while (oi.hasNext()) {
if (oi.getA().overlaps(oi.getB())) {
// Fetch the current suggestion and annotation
var group = suggestions.get(oi.getB());
for (var annotation : annotations.get(oi.getA())) {
var label = annotation.getFeatureValueAsString(feat);
for (var suggestion : group) {
// The suggestion would just create an annotation and not set any
// feature
boolean colocated = colocated(annotation, suggestion.getBegin(),
suggestion.getEnd());
if (suggestion.getLabel() == null) {
// If there is already an annotation, then we hide any suggestions
// that would just trigger the creation of the same annotation and
// not set any new feature. This applies whether stacking is allowed
// or not.
if (colocated) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}

// If stacking is enabled, we do allow suggestions that create an
// annotation with no label, but only if the offsets differ
if (feature.getLayer().isAllowStacking() && !colocated) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}
var pair = oi.next();
var suggestionOffset = pair.getKey();
var annotationOffset = pair.getValue();
// Fetch the current suggestion and annotation
var group = suggestions.get(suggestionOffset);
for (var annotation : annotations.get(annotationOffset)) {
var label = annotation.getFeatureValueAsString(feat);
for (var suggestion : group) {
// The suggestion would just create an annotation and not set any
// feature
boolean colocated = colocated(annotation, suggestion.getBegin(),
suggestion.getEnd());
if (suggestion.getLabel() == null) {
// If there is already an annotation, then we hide any suggestions
// that would just trigger the creation of the same annotation and
// not set any new feature. This applies whether stacking is allowed
// or not.
if (colocated) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}
// The suggestion would merge the suggested feature value into an
// existing annotation or create a new annotation with the feature if
// stacking were enabled.
else {
// Is the feature still unset in the current annotation - i.e. would
// accepting the suggestion merge the feature into it? If yes, we do
// not hide
if (label == null && colocated) {
continue;
}

// Does the suggested label match the label of an existing annotation
// at the same position then we hide
if (label != null && label.equals(suggestion.getLabel()) && colocated) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}

// Would accepting the suggestion create a new annotation but
// stacking is not enabled - then we need to hide
if (!feature.getLayer().isAllowStacking()) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}

// If stacking is enabled, we do allow suggestions that create an
// annotation with no label, but only if the offsets differ
if (!(feature.getLayer().isAllowStacking() && !colocated)) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}
}
}
// The suggestion would merge the suggested feature value into an
// existing annotation or create a new annotation with the feature if
// stacking were enabled.
else {
// Is the feature still unset in the current annotation - i.e. would
// accepting the suggestion merge the feature into it? If yes, we do
// not hide
if (label == null && suggestion.getLabel() != null && colocated) {
continue;
}

// Do not want to process the annotation again since the relevant suggestions are
// already hidden
oi.ignoreA();
}
// Does the suggested label match the label of an existing annotation
// at the same position then we hide
if (label != null && label.equals(suggestion.getLabel()) && colocated) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}

oi.step();
// Would accepting the suggestion create a new annotation but
// stacking is not enabled - then we need to hide
if (!feature.getLayer().isAllowStacking()) {
suggestion.hide(FLAG_OVERLAP);
hiddenForOverlap.add(suggestion);
continue;
}
}
}
}
}

if (LOG.isTraceEnabled()) {
Expand Down
Loading

0 comments on commit b8f2b0a

Please sign in to comment.