Skip to content

Commit

Permalink
Merge branch '0.14.x'
Browse files Browse the repository at this point in the history
* 0.14.x:
  #1561 - Ranking is off if there is no mention to work with
  #1559 - Problems if multiple entities have the same label
  #1559 - Problems if multiple entities have the same label

% Conflicts:
%	inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.java
  • Loading branch information
reckart committed Dec 13, 2019
2 parents 8aafdcc + 4b7f76f commit 000580b
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ public class BaselineRankingStrategy
// Compare geometric mean of the Levenshtein distance to query and mention
// since both are important and a very close similarity in say the mention outweighs
// a not so close similarity in the query
.append(Math.sqrt(e1.get(KEY_LEVENSHTEIN_QUERY).get() *
e1.get(KEY_LEVENSHTEIN_MENTION).get()),
Math.sqrt(e2.get(KEY_LEVENSHTEIN_QUERY).get() *
e2.get(KEY_LEVENSHTEIN_MENTION).get()))
.append(weightedLevenshteinDistance(e1), weightedLevenshteinDistance(e2))
// A high signature overlap score is preferred.
.append(e2.get(KEY_SIGNATURE_OVERLAP_SCORE).get(),
e1.get(KEY_SIGNATURE_OVERLAP_SCORE).get())
Expand All @@ -65,6 +62,32 @@ public class BaselineRankingStrategy
e2.getLabel().toLowerCase(e2.getLocale()))
.toComparison();

private static double weightedLevenshteinDistance(CandidateEntity aCandidate)
{
int query = aCandidate.get(KEY_LEVENSHTEIN_QUERY).get();
int mention = aCandidate.get(KEY_LEVENSHTEIN_MENTION).get();

if (query == Integer.MAX_VALUE && mention == Integer.MAX_VALUE) {
return Double.MAX_VALUE;
}

if (query == Integer.MAX_VALUE) {
return Math.sqrt(mention);
}

if (mention == Integer.MAX_VALUE) {
return Math.sqrt(query);
}

// If the distance of the query and mention is the same, then give the query a slight
// benefit so the user see's items matching the query he/she entered first
if (query > 0 && query == mention) {
query = query - 1;
}

return Math.sqrt(query * mention);
}

public static Comparator<CandidateEntity> getInstance()
{
return INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
</span>
<input style="width: 100%;" wicket:id="value" class="form-control" type="text"/>
</div>
<div class="col-sm-12" style="position: relative; height: 5em;" wicket:enclosure="description">
<div class="form-control" style="overflow-y: auto; width: unset; word-wrap: break-word; height: 5em; position: absolute; top: 0; left: 0px; right: 0px;" readonly>
<span class="small" wicket:id="description"/>
</div>
</div>
<div wicket:id="keyBindings" class="col-sm-12"/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,49 @@
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringBefore;
import static org.apache.wicket.event.Broadcast.BUBBLE;
import static org.apache.wicket.markup.head.JavaScriptHeaderItem.forReference;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.uima.cas.CAS;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.feedback.IFeedback;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.convert.IConverter;
import org.apache.wicket.util.string.StringValue;
import org.danekja.java.util.function.serializable.SerializableFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.wicket.jquery.core.JQueryBehavior;

import de.tudarmstadt.ukp.clarin.webanno.api.annotation.action.AnnotationActionHandler;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.feature.FeatureSupport;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.feature.FeatureSupportRegistry;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.feature.editor.FeatureEditor;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.feature.editor.KendoChoiceDescriptionScriptReference;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.feature.event.FeatureEditorValueChangedEvent;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.keybindings.KeyBindingsPanel;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.AnnotatorState;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.FeatureState;
Expand All @@ -72,7 +87,8 @@ public class ConceptFeatureEditor

private static final long serialVersionUID = 7763348613632105600L;

private FormComponent focusComponent;
private AutoCompleteField focusComponent;
private Label description;
private IriInfoBadge iriBadge;
private ExternalLink openIriLink;

Expand All @@ -97,7 +113,7 @@ public ConceptFeatureEditor(String aId, MarkupContainer aItem, IModel<FeatureSta

add(new DisabledKBWarning("disabledKBWarning", Model.of(getModelObject().feature)));

add(focusComponent = new KnowledgeBaseItemAutoCompleteField(MID_VALUE, _query ->
add(focusComponent = new AutoCompleteField(MID_VALUE, _query ->
getCandidates(aStateModel, aHandler, _query)));

AnnotationFeature feat = getModelObject().feature;
Expand All @@ -108,6 +124,11 @@ public ConceptFeatureEditor(String aId, MarkupContainer aItem, IModel<FeatureSta
// editor is used in a "normal" context and not e.g. in the keybindings
// configuration panel
.add(visibleWhen(() -> getLabelComponent().isVisible())));

description = new Label("description", LoadableDetachableModel.of(this::descriptionValue));
description.setOutputMarkupPlaceholderTag(true);
description.add(visibleWhen(() -> getLabelComponent().isVisible()));
add(description);
}

@Override
Expand All @@ -117,7 +138,17 @@ public void renderHead(IHeaderResponse aResponse)

aResponse.render(forReference(KendoChoiceDescriptionScriptReference.get()));
}


private String descriptionValue()
{
return getModel().map(FeatureState::getValue)
.map(value -> (KBHandle) value)
.map(KBHandle::getDescription)
.map(value -> StringUtils.abbreviate(value, 130))
.orElse("no description")
.getObject();
}

private String iriTooltipValue()
{
return getModel().map(FeatureState::getValue)
Expand Down Expand Up @@ -215,10 +246,191 @@ private ConceptFeatureTraits readFeatureTraits(AnnotationFeature aAnnotationFeat
ConceptFeatureTraits traits = fs.readTraits(aAnnotationFeature);
return traits;
}

@Override
public void addFeatureUpdateBehavior()
{
focusComponent.add(new AjaxFormComponentUpdatingBehavior("change")
{
private static final long serialVersionUID = -8944946839865527412L;

@Override
protected void updateAjaxAttributes(AjaxRequestAttributes aAttributes)
{
super.updateAjaxAttributes(aAttributes);
aAttributes.getDynamicExtraParameters()
.add(focusComponent.getIdentifierDynamicAttributeScript());
addDelay(aAttributes, 250);
}

@Override
protected void onUpdate(AjaxRequestTarget aTarget)
{
aTarget.add(description);
send(focusComponent, BUBBLE,
new FeatureEditorValueChangedEvent(ConceptFeatureEditor.this, aTarget));
}
});
}

@Override
public FormComponent getFocusComponent()
{
return focusComponent;
}

/**
* Special version of the {@link KnowledgeBaseItemAutoCompleteField} for used in the concept
* feature editor.
*/
public static class AutoCompleteField
extends KnowledgeBaseItemAutoCompleteField
{
private static final long serialVersionUID = 5461442869971269291L;

private IConverter<KBHandle> converter;
private List<KBHandle> choiceCache;
private boolean allowChoiceCache = false;

public AutoCompleteField(String aId,
SerializableFunction<String, List<KBHandle>> aChoiceProvider)
{
super(aId, aChoiceProvider);
converter = newConverter();
}

@Override
public void onConfigure(JQueryBehavior aBehavior)
{
super.onConfigure(aBehavior);

// We need to explicitly trigger the change event on the input element in order to
// trigger the Wicket AJAX update (if there is one). If we do not do this, then Kendo
// will "forget" to trigger a change event if the label of the newly selected item is
// the same as the label of the previously selected item!!!
// Using the default select behavior of AutoCompleteTextField which is coupled to the
// onSelected(AjaxRequestTarget aTarget) callback does unfortunatle not work well
// because onSelected does not tell us when the auto-complete field is CLEARED!
aBehavior.setOption("select", String.join(" ",
"function (e) {",
" e.sender.element.trigger('change');",
"}"));
}

@Override
protected List<KBHandle> getChoices(String aInput)
{
if (!allowChoiceCache || choiceCache == null) {
choiceCache = super.getChoices(aInput);
}
return choiceCache;
}

@Override
public String[] getInputAsArray()
{
// If the web request includes the additional "identifier" parameter which is supposed
// to contain the IRI of the selected item instead of its label, then we use that as the
// value.
WebRequest request = getWebRequest();
IRequestParameters requestParameters = request.getRequestParameters();
StringValue identifier = requestParameters
.getParameterValue(getInputName() + ":identifier");

if (!identifier.isEmpty()) {
return new String[] { identifier.toString() };
}

return super.getInputAsArray();
}

/**
* When using this input component with an {@link AjaxFormChoiceComponentUpdatingBehavior},
* it is necessary to request the identifier of the selected item as an additional dynamic
* attribute, otherwise no distinction can be made between two items with the same label!
*/
public String getIdentifierDynamicAttributeScript()
{
return String.join(" ",
"var item = $(attrs.event.target).data('kendoAutoComplete').dataItem();",
"if (item) {",
" return [{",
" 'name': '" + getInputName() + ":identifier', ",
" 'value': $(attrs.event.target).data('kendoAutoComplete').dataItem().identifier",
" }]",
"}",
"return [];");
}

@Override
public <C> IConverter<C> getConverter(Class<C> aType)
{
if (aType != null && aType.isAssignableFrom(this.getType())) {
return (IConverter<C>) converter;
}

return super.getConverter(aType);
}

private IConverter<KBHandle> newConverter()
{
return new IConverter<KBHandle>() {

private static final long serialVersionUID = 1L;

@Override
public KBHandle convertToObject(String value, Locale locale)
{
if (value == null) {
return null;
}

if (value.equals(getModelValue())) {
return getModelObject();
}

// Check choices only here since fetching choices can take some time. If we
// already have choices from a previous query, then we use them instead of
// reloading all the choices. This avoids having to load the choices when
// opening the dropdown AND when selecting one of the items from it.
List<KBHandle> choices;
try {
allowChoiceCache = true;
choices = getChoices(value);
}
finally {
allowChoiceCache = false;
}

if (choices.isEmpty()) {
return null;
}

// Check if we can find a match by the identifier. The identifier is unique
// while the same label may appear on multiple items
for (KBHandle handle : choices) {
if (value.equals(handle.getIdentifier())) {
return handle;
}
}

// // Check labels if there was no match on the identifier
// for (KBHandle handle : choices) {
// if (value.equals(getRenderer().getText(handle))) {
// return handle;
// }
// }

// If there was no match at all, return null
return null;
}

@Override
public String convertToString(KBHandle value, Locale locale)
{
return getRenderer().getText(value);
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ public boolean accepts(AnnotationFeature aFeature)
}

@Override
public String renderFeatureValue(AnnotationFeature aFeature, String aLabel)
public String renderFeatureValue(AnnotationFeature aFeature, String aIdentifier)
{
String renderValue = null;
if (aLabel != null) {
return labelCache.get(new Key(aFeature, aLabel)).getUiLabel();
if (aIdentifier != null) {
return labelCache.get(new Key(aFeature, aIdentifier)).getUiLabel();
}
return renderValue;
}
Expand Down Expand Up @@ -202,7 +202,10 @@ public KBHandle wrapFeatureValue(AnnotationFeature aFeature, CAS aCAS, Object aV
{
if (aValue instanceof String) {
String identifier = (String) aValue;
return new KBHandle(identifier, renderFeatureValue(aFeature, identifier));
String label = renderFeatureValue(aFeature, identifier);
String description = labelCache.get(new Key(aFeature, identifier)).getDescription();

return new KBHandle(identifier, label, description);
}
else if (aValue instanceof KBHandle) {
return (KBHandle) aValue;
Expand Down
Loading

0 comments on commit 000580b

Please sign in to comment.