Skip to content

Commit

Permalink
Merge pull request #4393 from inception-project/feature/2696-Document…
Browse files Browse the repository at this point in the history
…-level-recommendations

#2696 - Document-level recommendations
  • Loading branch information
reckart authored Dec 25, 2023
2 parents a0bda83 + db28f12 commit 5ec948d
Show file tree
Hide file tree
Showing 37 changed files with 1,479 additions and 1,274 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
import de.tudarmstadt.ukp.inception.ui.core.docanno.event.DocumentMetadataAnnotationActionUndoSupport;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerSingletonCreatingWatcher;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerSupport;
import de.tudarmstadt.ukp.inception.ui.core.docanno.recommendation.MetadataSuggestionSupport;
import de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar.DocumentMetadataSidebarFactory;
import de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar.MetadataSuggestionSupport;

/**
* Provides support for document-level annotations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar;
package de.tudarmstadt.ukp.inception.ui.core.docanno.recommendation;

import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_ALL;
import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_OVERLAP;
import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_REJECTED;
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;
import java.util.Optional;

import org.apache.commons.lang3.NotImplementedException;
import org.apache.uima.cas.AnnotationBaseFS;
Expand All @@ -41,22 +43,26 @@
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService;
import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService;
import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionRenderer;
import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupport_ImplBase;
import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationRejectedEvent;
import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction;
import de.tudarmstadt.ukp.inception.recommendation.api.model.MetadataSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerAdapter;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerSupport;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerTraits;

public class MetadataSuggestionSupport
extends SuggestionSupport_ImplBase<MetadataSuggestion>
extends SuggestionSupport_ImplBase

{
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Expand All @@ -73,9 +79,18 @@ public MetadataSuggestionSupport(RecommendationService aRecommendationService,
}

@Override
public boolean accepts(AnnotationSuggestion aContext)
public boolean accepts(Recommender aContext)
{
return aContext instanceof MetadataSuggestion;
if (!DocumentMetadataLayerSupport.TYPE.equals(aContext.getLayer().getType())) {
return false;
}

var feature = aContext.getFeature();
if (CAS.TYPE_NAME_STRING.equals(feature.getType()) || feature.isVirtualFeature()) {
return true;
}

return false;
}

@Override
Expand Down Expand Up @@ -269,4 +284,43 @@ var record = new LearningRecord();
record.setAnnotationFeature(aFeature);
return record;
}

@Override
public Optional<SuggestionRenderer> getRenderer()
{
return Optional.empty();
}

@Override
public List<AnnotationSuggestion> extractSuggestions(ExtractionContext ctx)
{
var result = new ArrayList<AnnotationSuggestion>();
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.inception.recommendation.service;
package de.tudarmstadt.ukp.inception.ui.core.docanno.recommendation;

import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_IS_PREDICTION;
import static de.tudarmstadt.ukp.inception.support.uima.FeatureStructureBuilder.buildFS;
Expand All @@ -30,26 +30,43 @@
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;

import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService;
import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService;
import de.tudarmstadt.ukp.inception.recommendation.api.RecommenderTypeSystemUtils;
import de.tudarmstadt.ukp.inception.recommendation.api.model.MetadataSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.support.uima.SegmentationUtils;
import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerSupport;
import de.tudarmstadt.ukp.inception.ui.core.docanno.recommendation.MetadataSuggestionSupport;

@ExtendWith(MockitoExtension.class)
class MetadataSuggestionExtractionTest
{
private @Mock RecommendationService recommendationService;
private @Mock LearningRecordService learningRecordService;
private @Mock ApplicationEventPublisher applicationEventPublisher;
private @Mock AnnotationSchemaService schemaService;

private Project project;
private SourceDocument document;
private TypeSystemDescription tsd;
private CAS originalCas;
private TypeDescription metadataType;
private FeatureDescription metadataLabelFeature;

private MetadataSuggestionSupport sut;

@BeforeEach
void setup() throws Exception
{
Expand All @@ -73,6 +90,9 @@ void setup() throws Exception

SegmentationUtils.splitSentences(originalCas);
SegmentationUtils.tokenize(originalCas);

sut = new MetadataSuggestionSupport(recommendationService, learningRecordService,
applicationEventPublisher, schemaService);
}

@Test
Expand Down Expand Up @@ -103,8 +123,8 @@ void testDocumentMetadataExtraction() throws Exception
.withFeature(FEATURE_NAME_IS_PREDICTION, true) //
.buildAndAddToIndexes();

var suggestions = SuggestionExtraction.extractSuggestions(1, originalCas, predictionCas,
document, recommender);
var ctx = new ExtractionContext(0, recommender, document, originalCas, predictionCas);
var suggestions = sut.extractSuggestions(ctx);

assertThat(suggestions) //
.filteredOn(a -> a instanceof MetadataSuggestion) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.api.model.RecommenderGeneralSettings;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationEngineFactory;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext;
Expand Down Expand Up @@ -304,7 +305,9 @@ Predictions computePredictions(User aSessionOwner, Project aProject,
* @param aLayer
* the layer to which the suggestions belong.
* @param aRecommendations
* the suggestions.
* the suggestions which must all be of the same type, e.g. all
* {@link SpanSuggestion}s. Use e.g.
* {@link SuggestionDocumentGroup#groupsOfType(Class, List)} to generate them.
* @param aWindowBegin
* the range of the document for which to update the suggestions.
* @param aWindowEnd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.inception.recommendation.render;
package de.tudarmstadt.ukp.inception.recommendation.api;

import de.tudarmstadt.ukp.inception.recommendation.api.model.Predictions;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer;
import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup;
import de.tudarmstadt.ukp.inception.rendering.request.RenderRequest;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VDocument;
import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter;

/**
* Type Adapters for span, arc, and chain annotations
*/
public interface RecommendationTypeRenderer<T extends TypeAdapter>
public interface SuggestionRenderer
{
String COLOR = "#cccccc";

Expand All @@ -35,8 +36,12 @@ public interface RecommendationTypeRenderer<T extends TypeAdapter>
*
* @param aVdoc
* a {@link VDocument} containing annotations for the given layer
* @param aSuggestions
* the suggestions to render
* @param aRequest
* a render request
*/
void render(VDocument aVdoc, RenderRequest aRequest, Predictions aPredictions, T aAdapter);
void render(VDocument aVdoc, RenderRequest aRequest,
SuggestionDocumentGroup<? extends AnnotationSuggestion> aSuggestions,
AnnotationLayer aLayer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package de.tudarmstadt.ukp.inception.recommendation.api;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import org.apache.uima.cas.AnnotationBaseFS;
import org.apache.uima.cas.CAS;
Expand All @@ -29,13 +31,15 @@
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup;
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter;
import de.tudarmstadt.ukp.inception.support.extensionpoint.Extension;

public interface SuggestionSupport<S extends AnnotationSuggestion>
extends Extension<AnnotationSuggestion>
public interface SuggestionSupport
extends Extension<Recommender>
{
AnnotationBaseFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument,
String aDataOwner, CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature,
Expand All @@ -58,4 +62,8 @@ <T extends AnnotationSuggestion> void calculateSuggestionVisibility(String aSess
LearningRecord toLearningRecord(SourceDocument aDocument, String aUsername,
AnnotationSuggestion aSuggestion, AnnotationFeature aFeature,
LearningRecordUserAction aUserAction, LearningRecordChangeLocation aLocation);

Optional<SuggestionRenderer> getRenderer();

List<AnnotationSuggestion> extractSuggestions(ExtractionContext aCtx);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
*/
package de.tudarmstadt.ukp.inception.recommendation.api;

import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender;
import de.tudarmstadt.ukp.inception.support.extensionpoint.ContextLookupExtensionPoint;

public interface SuggestionSupportRegistry
extends ContextLookupExtensionPoint<AnnotationSuggestion, SuggestionSupport<?>>
extends ContextLookupExtensionPoint<Recommender, SuggestionSupport>
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,26 @@

import org.apache.uima.cas.AnnotationBaseFS;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.fit.util.FSUtil;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationEventPublisher;

import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationAcceptedEvent;
import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion;
import de.tudarmstadt.ukp.inception.recommendation.api.model.AutoAcceptMode;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation;
import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction;
import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;
import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter;
import de.tudarmstadt.ukp.inception.support.uima.ICasUtil;

public abstract class SuggestionSupport_ImplBase<S extends AnnotationSuggestion>
implements SuggestionSupport<S>, BeanNameAware
public abstract class SuggestionSupport_ImplBase
implements SuggestionSupport, BeanNameAware
{
protected final RecommendationService recommendationService;
protected final LearningRecordService learningRecordService;
Expand Down Expand Up @@ -95,4 +99,29 @@ protected void commmitLabel(String aSessionOwner, SourceDocument aDocument, Stri
annotation, aFeature, aSuggestion.getLabel()));
}
}

private static final String AUTO_ACCEPT_ON_FIRST_ACCESS = "on-first-access";

public static AutoAcceptMode getAutoAcceptMode(FeatureStructure aFS, Feature aModeFeature)
{
var autoAcceptMode = AutoAcceptMode.NEVER;
var autoAcceptFeatureValue = aFS.getStringValue(aModeFeature);
if (autoAcceptFeatureValue != null) {
switch (autoAcceptFeatureValue) {
case AUTO_ACCEPT_ON_FIRST_ACCESS:
autoAcceptMode = AutoAcceptMode.ON_FIRST_ACCESS;
}
}
return autoAcceptMode;
}

public static String[] getPredictedLabels(FeatureStructure predictedFS,
Feature predictedFeature, boolean isStringMultiValue)
{
if (isStringMultiValue) {
return FSUtil.getFeature(predictedFS, predictedFeature, String[].class);
}

return new String[] { predictedFS.getFeatureValueAsString(predictedFeature) };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public AnnotationSuggestion(int aId, int aGeneration, int aAge, long aRecommende
scoreExplanation = aScoreExplanation;
recommenderId = aRecommenderId;
documentName = aDocumentName;
autoAcceptMode = aAutoAcceptMode;
autoAcceptMode = aAutoAcceptMode != null ? aAutoAcceptMode : AutoAcceptMode.NEVER;
hidingFlags = aHidingFlags;
}

Expand Down
Loading

0 comments on commit 5ec948d

Please sign in to comment.