Skip to content

Commit

Permalink
#4809 - Connecting annotation and entity with arc over different pages
Browse files Browse the repository at this point in the history
- When collecting relations to render, inclode relations that have one of their endpoints overlapping with the current viewport
- If the other endpoint is outside the viewport, create a dummy annotation either at the beginning or end of the viewport and connect the relation to that
  • Loading branch information
reckart committed May 17, 2024
1 parent bc4aafa commit 9350c94
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
*/
package de.tudarmstadt.ukp.inception.annotation.layer.relation;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.abbreviate;
import static org.apache.uima.fit.util.CasUtil.selectCovered;

import java.lang.invoke.MethodHandles;
import java.util.ArrayDeque;
Expand Down Expand Up @@ -56,6 +54,8 @@
import de.tudarmstadt.ukp.inception.rendering.vmodel.VLazyDetail;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VLazyDetailGroup;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VObject;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VRange;
import de.tudarmstadt.ukp.inception.rendering.vmodel.VSpan;
import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry;
import de.tudarmstadt.ukp.inception.schema.api.layer.LayerSupportRegistry;
import de.tudarmstadt.ukp.inception.support.uima.ICasUtil;
Expand Down Expand Up @@ -121,7 +121,21 @@ protected boolean typeSystemInit(TypeSystem aTypeSystem)
@Override
public List<AnnotationFS> selectAnnotationsInWindow(CAS aCas, int aWindowBegin, int aWindowEnd)
{
return selectCovered(aCas, type, aWindowBegin, aWindowEnd);
var result = new ArrayList<AnnotationFS>();
for (var rel : aCas.<Annotation> select(type)) {
var sourceFs = getSourceFs(rel);
var targetFs = getTargetFs(rel);

if (sourceFs instanceof Annotation source && targetFs instanceof Annotation target) {
if (source.overlapping(aWindowBegin, aWindowEnd)
|| target.overlapping(aWindowBegin, aWindowEnd)) {
result.add(rel);
}
}
}
return result;

// return selectCovered(aCas, type, aWindowBegin, aWindowEnd);
}

@Override
Expand All @@ -140,13 +154,14 @@ public void render(final CAS aCas, List<AnnotationFeature> aFeatures, VDocument
var annotations = selectAnnotationsInWindow(aCas, aWindowBegin, aWindowEnd);

for (var fs : annotations) {
for (var arc : render(aResponse, fs, aFeatures, aWindowBegin, aWindowEnd)) {
if (!(arc instanceof VArc)) {
for (var obj : render(aResponse, fs, aFeatures, aWindowBegin, aWindowEnd)) {
if (!(obj instanceof VArc)) {
aResponse.add(obj);
continue;
}

aResponse.add(arc);
annoToArcIdx.put(fs, (VArc) arc);
aResponse.add(obj);
annoToArcIdx.put(fs, (VArc) obj);

renderRequiredFeatureErrors(aFeatures, fs, aResponse);
}
Expand Down Expand Up @@ -191,10 +206,10 @@ public List<VObject> render(VDocument aVDocument, AnnotationFS aFS,
}

var typeAdapter = getTypeAdapter();
var dependentFs = getTargetFs(aFS);
var governorFs = getSourceFs(aFS);
var sourceFs = getSourceFs(aFS);
var targetFs = getTargetFs(aFS);

if (dependentFs == null || governorFs == null) {
if (targetFs == null || sourceFs == null) {
WicketUtil.getPage().ifPresent(page -> {
var message = new StringBuilder();
message.append("Relation [" + typeAdapter.getLayer().getName() + "] with id ["
Expand All @@ -204,8 +219,8 @@ public List<VObject> render(VDocument aVDocument, AnnotationFS aFS,
+ "] attached to feature [" + typeAdapter.getAttachFeatureName()
+ "].");
}
message.append("\nDependent: " + dependentFs);
message.append("\nGovernor: " + governorFs);
message.append("\nSource: " + sourceFs);
message.append("\nTarget: " + targetFs);
page.warn(message.toString());
});

Expand All @@ -215,39 +230,60 @@ public List<VObject> render(VDocument aVDocument, AnnotationFS aFS,
var labelFeatures = renderLabelFeatureValues(typeAdapter, aFS, aFeatures);

if (traits.isRenderArcs()) {
var objects = new ArrayList<VObject>();
var arc = VArc.builder().forAnnotation(aFS) //
.withLayer(typeAdapter.getLayer()) //
.withSource(governorFs) //
.withTarget(dependentFs) //
.withSource(sourceFs) //
.withTarget(targetFs) //
.withFeatures(labelFeatures) //
.build();
objects.add(arc);

return asList(arc);
createDummyEndpoint(sourceFs, aWindowBegin, aWindowEnd, typeAdapter, objects);
createDummyEndpoint(targetFs, aWindowBegin, aWindowEnd, typeAdapter, objects);

return objects;
}
else {
var governor = (AnnotationFS) governorFs;
var dependent = (AnnotationFS) dependentFs;

var noteBuilder = new StringBuilder();
noteBuilder.append(typeAdapter.getLayer().getUiName());
noteBuilder.append("\n");
noteBuilder.append(governor.getCoveredText());
noteBuilder.append(" -> ");
noteBuilder.append(dependent.getCoveredText());
var governor = (AnnotationFS) sourceFs;
var dependent = (AnnotationFS) targetFs;

var noteBuilder = new StringBuilder();
noteBuilder.append(typeAdapter.getLayer().getUiName());
noteBuilder.append("\n");
noteBuilder.append(governor.getCoveredText());
noteBuilder.append(" -> ");
noteBuilder.append(dependent.getCoveredText());
noteBuilder.append("\n");

for (Entry<String, String> entry : labelFeatures.entrySet()) {
noteBuilder.append(entry.getKey());
noteBuilder.append(" = ");
noteBuilder.append(entry.getValue());
noteBuilder.append("\n");
}

for (Entry<String, String> entry : labelFeatures.entrySet()) {
noteBuilder.append(entry.getKey());
noteBuilder.append(" = ");
noteBuilder.append(entry.getValue());
noteBuilder.append("\n");
}
String note = noteBuilder.toString().stripTrailing();
aVDocument.add(new VComment(sourceFs, VCommentType.INFO, "\n⬆️ " + note));
aVDocument.add(new VComment(targetFs, VCommentType.INFO, "\n⬇️ " + note));

String note = noteBuilder.toString().stripTrailing();
aVDocument.add(new VComment(governorFs, VCommentType.INFO, "\n⬆️ " + note));
aVDocument.add(new VComment(dependentFs, VCommentType.INFO, "\n⬇️ " + note));
return Collections.emptyList();
}

return Collections.emptyList();
private void createDummyEndpoint(FeatureStructure aEndpoint, int aWindowBegin, int aWindowEnd,
RelationAdapter aTypeAdapter, List<VObject> aObjects)
{
if (((AnnotationFS) aEndpoint).getEnd() < aWindowBegin) {
aObjects.add(VSpan.builder().forAnnotation((AnnotationFS) aEndpoint) //
.withLayer(aTypeAdapter.getLayer()) //
.withRange(new VRange(0, 0)) //
.build());
}
if (((AnnotationFS) aEndpoint).getBegin() >= aWindowEnd) {
aObjects.add(VSpan.builder().forAnnotation((AnnotationFS) aEndpoint) //
.withLayer(aTypeAdapter.getLayer()) //
.withRange(new VRange(aWindowEnd, aWindowEnd)) //
.build());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import { SVG, Element as SVGJSElement, Svg, Container, Text as SVGText, PathComm
import { INSTANCE as Configuration } from '../configuration/Configuration'
import { INSTANCE as Util } from '../util/Util'
import { AnnotationOutEvent, AnnotationOverEvent, Offsets, Relation, Span } from '@inception-project/inception-js-api'
declare const $: JQueryStatic
import $ from 'jquery'

/**
* [lastRow, textDesc[3], lastX - boxX, textDesc[4]]
Expand Down Expand Up @@ -134,7 +134,7 @@ export class Visualizer {

data?: DocumentData
private sourceData?: SourceData
private requestedData: SourceData = null // FIXME Do we really need requestedData AND sourceData?
private requestedData: SourceData | null = null // FIXME Do we really need requestedData AND sourceData?

private args: Partial<Record<MarkerType, MarkerDto>> = {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

Expand All @@ -37,6 +38,13 @@ public class VSpan

private final List<VRange> ranges;

private VSpan(Builder builder)
{
super(builder.layer, builder.vid, builder.equivalenceSet, builder.features);
setLabelHint(builder.label);
ranges = builder.ranges;
}

public VSpan(AnnotationLayer aLayer, AnnotationFS aFS, VRange aOffsets,
Map<String, String> aFeatures)
{
Expand Down Expand Up @@ -113,4 +121,70 @@ public List<VRange> getRanges()
{
return ranges;
}

public static Builder builder()
{
return new Builder();
}

public static final class Builder
{
private AnnotationLayer layer;
private VID vid;
private int equivalenceSet;
private Map<String, String> features = Collections.emptyMap();
private List<VRange> ranges;
private String label;

private Builder()
{
}

public Builder forAnnotation(AnnotationFS aAnnotation)
{
withVid(VID.of(aAnnotation));
return this;
}

public Builder withLayer(AnnotationLayer aLayer)
{
this.layer = aLayer;
return this;
}

public Builder withVid(VID aVid)
{
this.vid = aVid;
return this;
}

public Builder withEquivalenceSet(int aEquivalenceSet)
{
this.equivalenceSet = aEquivalenceSet;
return this;
}

public Builder withFeatures(Map<String, String> aFeatures)
{
this.features = aFeatures;
return this;
}

public Builder withRange(VRange aRange)
{
this.ranges = asList(aRange);
return this;
}

public Builder withLabel(String aLabel)
{
this.label = aLabel;
return this;
}

public VSpan build()
{
return new VSpan(this);
}
}
}

0 comments on commit 9350c94

Please sign in to comment.