From 9f626b38de15c50361f4bd5a279f1ef950291b22 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Wed, 20 Sep 2023 22:00:02 +0200 Subject: [PATCH 1/9] #4196 - Save memory by not creating empty arrays in CAS - Adjust link features and multi-value features to not store empty arrays --- .../feature/multistring/MultiValueStringFeatureSupport.java | 2 +- .../ukp/inception/schema/feature/FeatureUtil.java | 5 +++++ .../ui/kb/feature/MultiValueConceptFeatureSupport.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java index 193c7d3c59d..6009aa47e8a 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java @@ -137,7 +137,7 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, FeatureStructure fs = getFS(aCas, aFeature, aAddress); List values = unwrapFeatureValue(aFeature, fs.getCAS(), aValue); - if (values == null) { + if (values == null || values.isEmpty()) { FSUtil.setFeature(fs, aFeature.getName(), (Collection) null); return; } diff --git a/inception/inception-api-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/feature/FeatureUtil.java b/inception/inception-api-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/feature/FeatureUtil.java index be5dd5c63c0..b230822aeed 100644 --- a/inception/inception-api-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/feature/FeatureUtil.java +++ b/inception/inception-api-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/feature/FeatureUtil.java @@ -147,6 +147,11 @@ private static void setLinkFeature(FeatureStructure aFS, AnnotationFeature aFeat public static void setLinkFeatureValue(FeatureStructure aFS, Feature aFeature, List linkFSes) { + if (linkFSes == null || linkFSes.isEmpty()) { + aFS.setFeatureValue(aFeature, null); + return; + } + // Create a new array if size differs otherwise re-use existing one var array = (ArrayFS) ICasUtil.getFeatureFS(aFS, aFeature.getShortName()); if (array == null || (array.size() != linkFSes.size())) { diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/MultiValueConceptFeatureSupport.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/MultiValueConceptFeatureSupport.java index ac5f923bbfe..e759337033d 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/MultiValueConceptFeatureSupport.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/MultiValueConceptFeatureSupport.java @@ -282,7 +282,7 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, FeatureStructure fs = getFS(aCas, aFeature, aAddress); List values = unwrapFeatureValue(aFeature, fs.getCAS(), aValue); - if (values == null) { + if (values == null || values.isEmpty()) { FSUtil.setFeature(fs, aFeature.getName(), (Collection) null); return; } From ab30b22a98266e231ba401aa702ef232fa69989c Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Wed, 20 Sep 2023 22:05:01 +0200 Subject: [PATCH 2/9] #4198 - Show layer name instead of no-label - Use the layer name if available --- .../main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts index 10511853a2f..2cacfc13beb 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts +++ b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts @@ -309,7 +309,7 @@ export class ApacheAnnotatorVisualizer { const attributes = { 'data-iaa-id': `${span.vid}`, - 'data-iaa-label': `${span.label || NO_LABEL}`, + 'data-iaa-label': `${span.label || `[${span.layer.name}]` || NO_LABEL}`, class: classList.join(' '), style: styleList.join('; ') } From 014da5b518432c00588a1eb47deced4e60ac5e49 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Wed, 20 Sep 2023 22:29:11 +0200 Subject: [PATCH 3/9] #4199 - Jumping to the end of a long annotation sometimes does not work - Fix issue (hopefully) - Also fix another occasional issue when keying through the ADEP --- .../ApacheAnnotatorVisualizer.ts | 32 +++++++++++-------- .../detail/AnnotationDetailEditorPanel.js | 9 +++--- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts index feffbdcb7fc..a408d4778f3 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts +++ b/inception/inception-html-apache-annotator-editor/src/main/ts/src/apache-annotator/ApacheAnnotatorVisualizer.ts @@ -36,8 +36,9 @@ export class ApacheAnnotatorVisualizer { private showInlineLabels = false private showEmptyHighlights = false - private removePingMarkers: (() => void)[] = [] - private removePingMarkersTimeout: number | undefined = undefined + private removeTransientMarkers: (() => void)[] = [] + private removeTransientMarkersTimeout: number | undefined = undefined + private alpha = '55' constructor (element: Element, ajax: DiamAjax) { @@ -110,6 +111,7 @@ export class ApacheAnnotatorVisualizer { if (doc.spans) { console.log(`Loaded ${doc.spans.size} span annotations`) doc.spans.forEach(span => this.renderSpanAnnotation(doc, span)) + this.removeSpuriousZeroWidthHighlights() if (!this.showEmptyHighlights) { this.removeWhitepaceOnlyHighlights() @@ -159,7 +161,7 @@ export class ApacheAnnotatorVisualizer { const vhl = this.root.ownerDocument.createElement('div') vhl.classList.add('iaa-vertical-marker-focus') - vhl.style.top = `${top - scrollerContainerRect.top + this.root.scrollTop}px` + vhl.style.top = `${top - scrollerContainerRect.top + (this.root.scrollTop || 0)}px` vhl.style.height = `${bottom - top}px` this.root.appendChild(vhl) @@ -206,8 +208,8 @@ export class ApacheAnnotatorVisualizer { /** * Some highliths may only contain whitepace. This method removes such highlights. */ - private removeWhitepaceOnlyHighlights () { - this.getAllHighlights().forEach(e => { + private removeWhitepaceOnlyHighlights (selector: string = '.iaa-highlighted') { + this.root.querySelectorAll(selector).forEach(e => { if (!e.classList.contains('iaa-zero-width') && !e.textContent?.trim()) { e.after(...e.childNodes) e.remove() @@ -338,16 +340,21 @@ export class ApacheAnnotatorVisualizer { const range = offsetToRange(this.root, args.offset, args.offset) if (!range) return - window.clearTimeout(this.removePingMarkersTimeout) - this.removePingMarkers.forEach(remove => remove()) + window.clearTimeout(this.removeTransientMarkersTimeout) + this.removeTransientMarkers.forEach(remove => remove()) const removeScrollMarker = highlightText(range, 'mark', { id: 'iaa-scroll-marker' }) - this.removePingMarkers = [] + this.removeTransientMarkers = [removeScrollMarker] for (const pingOffset of args.pingRanges || []) { const pingRange = offsetToRange(this.root, pingOffset[0], pingOffset[1]) if (!pingRange) continue - this.removePingMarkers.push(highlightText(pingRange, 'mark', { class: 'iaa-ping-marker' })) + this.removeTransientMarkers.push(highlightText(pingRange, 'mark', { class: 'iaa-ping-marker' })) } + + if (!this.showEmptyHighlights) { + this.removeWhitepaceOnlyHighlights('.iaa-ping-marker') + } + this.root.querySelectorAll('.iaa-ping-marker').forEach(e => { if (!e.textContent) e.remove() }) @@ -371,10 +378,9 @@ export class ApacheAnnotatorVisualizer { scrollTarget.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'nearest' }) } - removeScrollMarker() - this.removePingMarkersTimeout = window.setTimeout(() => { - this.removePingMarkers.forEach(remove => remove()) - console.log('ping removed') + this.removeTransientMarkersTimeout = window.setTimeout(() => { + this.removeTransientMarkers.forEach(remove => remove()) + this.removeTransientMarkers = [] }, 2000) } diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.js b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.js index cd5fa34af45..cb799a593ea 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.js +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.js @@ -20,13 +20,14 @@ $(document).ready(function() { // http://stackoverflow.com/questions/2335553/jquery-how-to-catch-enter-key-and-change-event-to-tab function moveToNextInput(current) { - var inputs = $(current).parents("#annotationFeatureForm").eq(0).find(":input"); - var idx = inputs.index(current); + let inputs = $(current).parents("#annotationFeatureForm").eq(0).find(":input"); + let idx = inputs.index(current); if (idx == inputs.length - 1) { inputs[idx].blur(); } else { - inputs[idx + 1].focus(); // handles submit buttons - inputs[idx + 1].select(); + let input = inputs[idx + 1]; + if (input.focus) input.focus(); // handles submit buttons + if (input.select) input.select(); } } From be605274f73aa8f0a232f0cf476081556a53d917 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Wed, 20 Sep 2023 22:46:54 +0200 Subject: [PATCH 4/9] #4201 - Layer export as JSON does not include coloring rules - Align JSON layer export again to general project export code --- inception/inception-export/pom.xml | 22 ++++-- .../export/LayerImportExportUtils.java | 74 +++++++++++-------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/inception/inception-export/pom.xml b/inception/inception-export/pom.xml index 17acbdd558e..2cf07d59896 100644 --- a/inception/inception-export/pom.xml +++ b/inception/inception-export/pom.xml @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 de.tudarmstadt.ukp.inception.app @@ -78,7 +80,7 @@ org.apache.uima uimafit-core - + org.apache.commons commons-lang3 @@ -91,7 +93,7 @@ it.unimi.dsi fastutil - + org.springframework spring-core @@ -112,7 +114,7 @@ org.springframework.boot spring-boot - + org.dkpro.core dkpro-core-api-metadata-asl @@ -121,18 +123,22 @@ org.dkpro.core dkpro-core-api-segmentation-asl - + + org.dkpro.core + dkpro-core-api-lexmorph-asl + + org.slf4j slf4j-api - + com.fasterxml.jackson.core jackson-databind - - + + de.tudarmstadt.ukp.inception.app diff --git a/inception/inception-export/src/main/java/de/tudarmstadt/ukp/inception/export/LayerImportExportUtils.java b/inception/inception-export/src/main/java/de/tudarmstadt/ukp/inception/export/LayerImportExportUtils.java index 2f76eec785d..c9ad5a6f181 100644 --- a/inception/inception-export/src/main/java/de/tudarmstadt/ukp/inception/export/LayerImportExportUtils.java +++ b/inception/inception-export/src/main/java/de/tudarmstadt/ukp/inception/export/LayerImportExportUtils.java @@ -19,13 +19,15 @@ import static de.tudarmstadt.ukp.clarin.webanno.model.OverlapMode.ANY_OVERLAP; import static de.tudarmstadt.ukp.clarin.webanno.model.OverlapMode.OVERLAP_ONLY; +import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.CHAIN_TYPE; +import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.COREFERENCE_RELATION_FEATURE; +import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.COREFERENCE_TYPE_FEATURE; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -46,7 +48,9 @@ import de.tudarmstadt.ukp.clarin.webanno.model.ValidationMode; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.support.JSONUtil; -import de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst; +import de.tudarmstadt.ukp.dkpro.core.api.lexmorph.type.morph.MorphologicalFeatures; +import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Lemma; +import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService; /** @@ -158,6 +162,7 @@ private static void setLayer(AnnotationSchemaService aAnnotationService, Annotat aLayer.setName(aExLayer.getName()); aLayer.setProject(aProject); aLayer.setType(aExLayer.getType()); + aLayer.setTraits(aExLayer.getTraits()); aAnnotationService.createOrUpdateLayer(aLayer); } @@ -167,28 +172,27 @@ public static AnnotationLayer importLayerFile(AnnotationSchemaService annotation { String text = IOUtils.toString(aIS, "UTF-8"); - ExportedAnnotationLayer[] exLayers = JSONUtil.getObjectMapper().readValue(text, - ExportedAnnotationLayer[].class); + var exLayers = JSONUtil.getObjectMapper().readValue(text, ExportedAnnotationLayer[].class); // First import the layers but without setting the attach-layers/features Map exLayersMap = new HashMap<>(); Map layersMap = new HashMap<>(); - for (ExportedAnnotationLayer exLayer : exLayers) { - AnnotationLayer layer = createLayer(annotationService, project, exLayer); + for (var exLayer : exLayers) { + var layer = createLayer(annotationService, project, exLayer); layersMap.put(layer.getName(), layer); exLayersMap.put(layer.getName(), exLayer); } // Second fill in the attach-layer and attach-feature information - for (AnnotationLayer layer : layersMap.values()) { - ExportedAnnotationLayer exLayer = exLayersMap.get(layer.getName()); + for (var layer : layersMap.values()) { + var exLayer = exLayersMap.get(layer.getName()); if (exLayer.getAttachType() != null) { layer.setAttachType(layersMap.get(exLayer.getAttachType().getName())); } if (exLayer.getAttachFeature() != null) { - AnnotationLayer attachLayer = annotationService.findLayer(project, + var attachLayer = annotationService.findLayer(project, exLayer.getAttachType().getName()); - AnnotationFeature attachFeature = annotationService + var attachFeature = annotationService .getFeature(exLayer.getAttachFeature().getName(), attachLayer); layer.setAttachFeature(attachFeature); } @@ -213,8 +217,8 @@ private static AnnotationLayer createLayer(AnnotationSchemaService annotationSer setLayer(annotationService, layer, aExLayer, project); } - for (ExportedAnnotationFeature exfeature : aExLayer.getFeatures()) { - ExportedTagSet exTagset = exfeature.getTagSet(); + for (var exfeature : aExLayer.getFeatures()) { + var exTagset = exfeature.getTagSet(); TagSet tagSet = null; if (exTagset != null && annotationService.existsTagSet(exTagset.getName(), project)) { tagSet = annotationService.getTagSet(exTagset.getName(), project); @@ -231,7 +235,7 @@ else if (exTagset != null) { setFeature(annotationService, feature, exfeature, project); continue; } - AnnotationFeature feature = new AnnotationFeature(); + var feature = new AnnotationFeature(); feature.setLayer(layer); feature.setTagset(tagSet); setFeature(annotationService, feature, exfeature, project); @@ -249,16 +253,21 @@ private static void setFeature(AnnotationSchemaService aAnnotationService, aFeature.setVisible(aExFeature.isVisible()); aFeature.setUiName(aExFeature.getUiName()); aFeature.setProject(aProject); - aFeature.setLayer(aFeature.getLayer()); - boolean isItChainedLayer = aFeature.getLayer().getType().equals(WebAnnoConst.CHAIN_TYPE); - if (isItChainedLayer && (aExFeature.getName().equals(WebAnnoConst.COREFERENCE_TYPE_FEATURE) - || aExFeature.getName().equals(WebAnnoConst.COREFERENCE_RELATION_FEATURE))) { + aFeature.setName(aExFeature.getName()); + boolean isItChainedLayer = CHAIN_TYPE.equals(aFeature.getLayer().getType()); + if (isItChainedLayer && (COREFERENCE_TYPE_FEATURE.equals(aExFeature.getName()) + || COREFERENCE_RELATION_FEATURE.equals(aExFeature.getName()))) { aFeature.setType(CAS.TYPE_NAME_STRING); } + else if (Token._TypeName.equals(aFeature.getLayer().getName()) + && Token._FeatName_morph.equals(aExFeature.getName()) + && Lemma._TypeName.equals(aExFeature.getType())) { + // See https://github.com/inception-project/inception/issues/3080 + aFeature.setType(MorphologicalFeatures._TypeName); + } else { aFeature.setType(aExFeature.getType()); } - aFeature.setName(aExFeature.getName()); aFeature.setRemember(aExFeature.isRemember()); aFeature.setRequired(aExFeature.isRequired()); aFeature.setHideUnconstraintFeature(aExFeature.isHideUnconstraintFeature()); @@ -268,7 +277,8 @@ private static void setFeature(AnnotationSchemaService aAnnotationService, aFeature.setLinkTypeRoleFeatureName(aExFeature.getLinkTypeRoleFeatureName()); aFeature.setLinkTypeTargetFeatureName(aExFeature.getLinkTypeTargetFeatureName()); aFeature.setTraits(aExFeature.getTraits()); - + aFeature.setCuratable(aExFeature.isCuratable()); + aFeature.setRank(aExFeature.getRank()); aAnnotationService.createFeature(aFeature); } @@ -278,15 +288,15 @@ public static ExportedAnnotationLayer exportLayerDetails( Map aFeatureToExFeature, AnnotationLayer aLayer, AnnotationSchemaService aAnnotationService) { - ExportedAnnotationLayer exLayer = new ExportedAnnotationLayer(); + var exLayer = new ExportedAnnotationLayer(); exLayer.setAllowStacking(aLayer.isAllowStacking()); + exLayer.setLockToTokenOffset(AnchoringMode.SINGLE_TOKEN.equals(aLayer.getAnchoringMode())); + exLayer.setMultipleTokens(AnchoringMode.TOKENS.equals(aLayer.getAnchoringMode())); exLayer.setBuiltIn(aLayer.isBuiltIn()); exLayer.setReadonly(aLayer.isReadonly()); exLayer.setCrossSentence(aLayer.isCrossSentence()); exLayer.setDescription(aLayer.getDescription()); exLayer.setEnabled(aLayer.isEnabled()); - exLayer.setLockToTokenOffset(AnchoringMode.SINGLE_TOKEN.equals(aLayer.getAnchoringMode())); - exLayer.setMultipleTokens(AnchoringMode.TOKENS.equals(aLayer.getAnchoringMode())); exLayer.setOverlapMode(aLayer.getOverlapMode()); exLayer.setAnchoringMode(aLayer.getAnchoringMode()); exLayer.setValidationMode(aLayer.getValidationMode()); @@ -295,14 +305,15 @@ public static ExportedAnnotationLayer exportLayerDetails( exLayer.setProjectName(aLayer.getProject().getName()); exLayer.setType(aLayer.getType()); exLayer.setUiName(aLayer.getUiName()); + exLayer.setTraits(aLayer.getTraits()); if (aLayerToExLayer != null) { aLayerToExLayer.put(aLayer, exLayer); } - List exFeatures = new ArrayList<>(); - for (AnnotationFeature feature : aAnnotationService.listAnnotationFeature(aLayer)) { - ExportedAnnotationFeature exFeature = new ExportedAnnotationFeature(); + var exFeatures = new ArrayList(); + for (var feature : aAnnotationService.listAnnotationFeature(aLayer)) { + var exFeature = new ExportedAnnotationFeature(); exFeature.setDescription(feature.getDescription()); exFeature.setEnabled(feature.isEnabled()); exFeature.setRemember(feature.isRemember()); @@ -319,18 +330,20 @@ public static ExportedAnnotationLayer exportLayerDetails( exFeature.setLinkTypeRoleFeatureName(feature.getLinkTypeRoleFeatureName()); exFeature.setLinkTypeTargetFeatureName(feature.getLinkTypeTargetFeatureName()); exFeature.setTraits(feature.getTraits()); + exFeature.setCuratable(feature.isCuratable()); + exFeature.setRank(feature.getRank()); if (feature.getTagset() != null) { - TagSet tagSet = feature.getTagset(); - ExportedTagSet exTagSet = new ExportedTagSet(); + var tagSet = feature.getTagset(); + var exTagSet = new ExportedTagSet(); exTagSet.setDescription(tagSet.getDescription()); exTagSet.setLanguage(tagSet.getLanguage()); exTagSet.setName(tagSet.getName()); exTagSet.setCreateTag(tagSet.isCreateTag()); - List exportedTags = new ArrayList<>(); - for (Tag tag : aAnnotationService.listTags(tagSet)) { - ExportedTag exTag = new ExportedTag(); + var exportedTags = new ArrayList(); + for (var tag : aAnnotationService.listTags(tagSet)) { + var exTag = new ExportedTag(); exTag.setDescription(tag.getDescription()); exTag.setName(tag.getName()); exportedTags.add(exTag); @@ -343,6 +356,7 @@ public static ExportedAnnotationLayer exportLayerDetails( aFeatureToExFeature.put(feature, exFeature); } } + exLayer.setFeatures(exFeatures); return exLayer; } From 693280a2ef76aabdc845d0f17b9250a004fb7bc0 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Thu, 21 Sep 2023 18:32:32 +0200 Subject: [PATCH 5/9] #4192 - Upgrade dependencies - wicketstuff 9.14.0 -> 9.15.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aa093ac053c..75aec53b9ff 100644 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,7 @@ 6.2.5.Final 9.15.0 - 9.14.0 + 9.15.0 9.12.0 6.0.4 3.0.6 From d3fb2220a544705615d40fe361954eecc9367413 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sat, 23 Sep 2023 09:27:22 +0200 Subject: [PATCH 6/9] #4158 - Exception when annotating something after a longer pause - Replace calls to SelectFS with calls to CASUtil which does not create temporary annotations --- .../annotation/layer/span/SpanRenderer.java | 38 +++++++++++++++-- .../casdiff/span/SpanDiffAdapter.java | 41 +++++++++++++++++-- ...ationsStartAndEndWithinSentencesCheck.java | 18 ++++++-- .../inception/io/bioc/model/CasToBioC.java | 21 +++++++--- .../io/bioc/xml/Cas2BioCSaxEvents.java | 8 +++- .../pdfeditor2/format/VisualPdfReader.java | 7 +++- .../RecommendationEditorExtension.java | 4 +- .../service/RecommendationServiceImpl.java | 36 ++++++++++------ .../detail/AnnotationDetailEditorPanel.java | 13 ++++-- 9 files changed, 150 insertions(+), 36 deletions(-) diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java index 050b2d41c57..35254744d36 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java @@ -30,6 +30,7 @@ import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.uima.cas.FSIterator; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.Type; import org.apache.uima.cas.TypeSystem; @@ -92,9 +93,40 @@ protected boolean typeSystemInit(TypeSystem aTypeSystem) @Override public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, int aWindowEnd) { - return aCas.select(type).coveredBy(0, aWindowEnd).includeAnnotationsWithEndBeyondBounds() - .map(fs -> (AnnotationFS) fs) - .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) + // https://github.com/apache/uima-uimaj/issues/345 + // return aCas.select(type).coveredBy(0, aWindowEnd).includeAnnotationsWithEndBeyondBounds() + // .map(fs -> (AnnotationFS) fs) + // .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) + // .collect(toList()); + + List list = new ArrayList(); + + // withSnapshotIterators() not needed here since we copy the FSes to a list anyway + FSIterator it = aCas.getAnnotationIndex(type).iterator(); + + // Skip annotations whose start is before the start parameter. + while (it.isValid() && (it.get()).getBegin() < aWindowBegin) { + it.moveToNext(); + } + + boolean strict = false; + while (it.isValid()) { + AnnotationFS a = it.get(); + // If the start of the current annotation is past the end parameter, we're done. + if (a.getBegin() > aWindowEnd) { + break; + } + it.moveToNext(); + if (strict && a.getEnd() > aWindowEnd) { + continue; + } + + list.add(a); + } + + return list.stream() // + .map(fs -> (AnnotationFS) fs) // + .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) // .collect(toList()); } diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java index 4ad640c22bc..e17a522228e 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java @@ -20,14 +20,17 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.uima.cas.CAS; +import org.apache.uima.cas.FSIterator; import org.apache.uima.cas.FeatureStructure; 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.fit.util.FSUtil; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil; @@ -71,9 +74,41 @@ public SpanDiffAdapter(String aType, Set aLabelFeatures) @Override public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, int aWindowEnd) { - return aCas.select(getType()).coveredBy(0, aWindowEnd) - .includeAnnotationsWithEndBeyondBounds().map(fs -> (AnnotationFS) fs) - .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) + // https://github.com/apache/uima-uimaj/issues/345 + // return aCas.select(type).coveredBy(0, aWindowEnd).includeAnnotationsWithEndBeyondBounds() + // .map(fs -> (AnnotationFS) fs) + // .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) + // .collect(toList()); + + List list = new ArrayList(); + + // withSnapshotIterators() not needed here since we copy the FSes to a list anyway + FSIterator it = aCas.getAnnotationIndex(CasUtil.getType(aCas, getType())) + .iterator(); + + // Skip annotations whose start is before the start parameter. + while (it.isValid() && (it.get()).getBegin() < aWindowBegin) { + it.moveToNext(); + } + + boolean strict = false; + while (it.isValid()) { + AnnotationFS a = it.get(); + // If the start of the current annotation is past the end parameter, we're done. + if (a.getBegin() > aWindowEnd) { + break; + } + it.moveToNext(); + if (strict && a.getEnd() > aWindowEnd) { + continue; + } + + list.add(a); + } + + return list.stream() // + .map(fs -> (AnnotationFS) fs) // + .filter(ann -> AnnotationPredicates.overlapping(ann, aWindowBegin, aWindowEnd)) // .collect(toList()); } diff --git a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/AllAnnotationsStartAndEndWithinSentencesCheck.java b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/AllAnnotationsStartAndEndWithinSentencesCheck.java index f17ba3e1434..d68d344e0cf 100644 --- a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/AllAnnotationsStartAndEndWithinSentencesCheck.java +++ b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/AllAnnotationsStartAndEndWithinSentencesCheck.java @@ -27,6 +27,7 @@ import org.apache.uima.cas.CAS; import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.fit.util.CasUtil; import org.springframework.util.CollectionUtils; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; @@ -76,10 +77,19 @@ public boolean check(Project aProject, CAS aCas, List aMessages) } for (AnnotationFS ann : select(aCas, type)) { - var startsOutside = aCas.select(Sentence._TypeName) - .covering(ann.getBegin(), ann.getBegin()).isEmpty(); - var endsOutside = aCas.select(Sentence._TypeName) - .covering(ann.getEnd(), ann.getEnd()).isEmpty(); + // https://github.com/apache/uima-uimaj/issues/345 + // var startsOutside = aCas.select(Sentence._TypeName) + // .covering(ann.getBegin(), ann.getBegin()).isEmpty(); + var startsOutside = CasUtil + .selectCovering(ann.getCAS(), CasUtil.getType(ann.getCAS(), Sentence.class), + ann.getBegin(), ann.getBegin()) + .isEmpty(); + // https://github.com/apache/uima-uimaj/issues/345 + // var endsOutside = aCas.select(Sentence._TypeName) + // .covering(ann.getEnd(), ann.getEnd()).isEmpty(); + var endsOutside = CasUtil.selectCovering(ann.getCAS(), + CasUtil.getType(ann.getCAS(), Sentence.class), ann.getEnd(), ann.getEnd()) + .isEmpty(); if (!startsOutside && !endsOutside) { continue; diff --git a/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/model/CasToBioC.java b/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/model/CasToBioC.java index 4aa35a4a7c7..8e389b28220 100644 --- a/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/model/CasToBioC.java +++ b/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/model/CasToBioC.java @@ -40,6 +40,7 @@ import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.FSUtil; +import org.apache.uima.fit.util.JCasUtil; import org.apache.uima.jcas.JCas; import org.apache.uima.jcas.tcas.Annotation; import org.dkpro.core.api.xml.type.XmlElement; @@ -81,18 +82,24 @@ public void convert(JCas aJCas, BioCCollection aCollection) bioCPassage.addInfon(I_TYPE, div.getDivType()); } - var sentences = aJCas.select(Sentence.class).coveredBy(div).asList(); + // https://github.com/apache/uima-uimaj/issues/345 + // var sentences = aJCas.select(Sentence.class).coveredBy(div).asList(); + var sentences = JCasUtil.selectCovered(Sentence.class, div); + // https://github.com/apache/uima-uimaj/issues/345 + // var annotations = aJCas.select(Annotation.class).coveredBy(div); + var annotations = JCasUtil.selectCovered(Annotation.class, div); if (sentences.isEmpty()) { bioCPassage.setText(div.getCoveredText()); - processAnnotations(bioCPassage, bioCPassage.getOffset(), - aJCas.select(Annotation.class).coveredBy(div)); + processAnnotations(bioCPassage, bioCPassage.getOffset(), annotations); } else { var bioCSentences = processSentences(div.getBegin(), sentences); bioCPassage.setSentences(bioCSentences); processAnnotations(bioCPassage, bioCPassage.getOffset(), - aJCas.select(Annotation.class).coveredBy(div) - .filter(a -> aJCas.select(Sentence.class).covering(a).isEmpty()) + annotations.stream().filter(a -> + // https://github.com/apache/uima-uimaj/issues/345 + // aJCas.select(Sentence.class).covering(a).isEmpty() + JCasUtil.selectCovering(Sentence.class, a).isEmpty()) .collect(Collectors.toList())); } } @@ -129,7 +136,9 @@ private List processSentences(int aPassageOffset, List s bioCSentence.setText(sentence.getCoveredText()); processAnnotations(bioCSentence, sentence.getBegin(), - sentence.getCAS().select(Annotation.class).coveredBy(sentence)); + // https://github.com/apache/uima-uimaj/issues/345 + // sentence.getCAS().select(Annotation.class).coveredBy(sentence) + JCasUtil.selectCovered(Annotation.class, sentence)); bioCSentences.add(bioCSentence); } diff --git a/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/xml/Cas2BioCSaxEvents.java b/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/xml/Cas2BioCSaxEvents.java index a97c4d815c0..d2a7351539c 100644 --- a/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/xml/Cas2BioCSaxEvents.java +++ b/inception/inception-io-bioc/src/main/java/de/tudarmstadt/ukp/inception/io/bioc/xml/Cas2BioCSaxEvents.java @@ -58,6 +58,7 @@ import org.apache.uima.cas.CAS; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.FSUtil; +import org.apache.uima.fit.util.JCasUtil; import org.apache.uima.jcas.JCas; import org.apache.uima.jcas.tcas.Annotation; import org.dkpro.core.api.xml.type.XmlElement; @@ -139,8 +140,11 @@ private void processSentenceElement(XmlElement aSentenceElement) throws SAXExcep return; } - for (var annotation : aSentenceElement.getCAS().select(Annotation.class) - .coveredBy(aSentenceElement)) { + // https://github.com/apache/uima-uimaj/issues/345 + // var annotations = aSentenceElement.getCAS().select(Annotation.class) + // .coveredBy(aSentenceElement); + var annotations = JCasUtil.selectCovered(Annotation.class, aSentenceElement); + for (var annotation : annotations) { serializeAnnotation(sentenceTextElement.get().getBegin(), annotation); } } diff --git a/inception/inception-pdf-editor2/src/main/java/de/tudarmstadt/ukp/inception/pdfeditor2/format/VisualPdfReader.java b/inception/inception-pdf-editor2/src/main/java/de/tudarmstadt/ukp/inception/pdfeditor2/format/VisualPdfReader.java index 43fcf64c132..318683c6fa1 100644 --- a/inception/inception-pdf-editor2/src/main/java/de/tudarmstadt/ukp/inception/pdfeditor2/format/VisualPdfReader.java +++ b/inception/inception-pdf-editor2/src/main/java/de/tudarmstadt/ukp/inception/pdfeditor2/format/VisualPdfReader.java @@ -27,6 +27,7 @@ import org.apache.uima.cas.CAS; import org.apache.uima.collection.CollectionException; import org.apache.uima.fit.descriptor.ConfigurationParameter; +import org.apache.uima.fit.util.CasUtil; import org.apache.uima.jcas.JCas; import org.apache.uima.jcas.cas.FloatArray; import org.apache.uima.jcas.cas.IntegerArray; @@ -117,7 +118,11 @@ public static VModel visualModelFromCas(CAS cas, List pdfPages) List vPages = new ArrayList<>(); for (PdfPage pdfPage : pdfPages) { List vChunks = new ArrayList<>(); - for (var pdfChunk : cas.select(PdfChunk.class).coveredBy(pdfPage)) { + // https://github.com/apache/uima-uimaj/issues/345 + // SelectFSs coveredBy = cas.select(PdfChunk.class).coveredBy(pdfPage); + List coveredBy = (List) CasUtil + .selectCovered(CasUtil.getType(cas, PdfChunk.class), pdfPage); + for (var pdfChunk : coveredBy) { float d = pdfChunk.getD(); List vGlyphs = new ArrayList<>(); IntegerArray charWidths = pdfChunk.getC(); diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java index de66ed47c9a..a6f4003e684 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java @@ -226,7 +226,7 @@ private void actionAcceptSpanRecommendation(AjaxRequestTarget aTarget, page.writeEditorCas(aCas); // Set selection to the accepted annotation and select it and load it into the detail editor - adapter.select(VID.of(span), span); + aState.getSelection().set(adapter.select(VID.of(span), span)); // Send a UI event that the suggestion has been accepted page.send(page, BREADTH, @@ -251,7 +251,7 @@ private void actionAcceptRelationRecommendation(AjaxRequestTarget aTarget, page.writeEditorCas(aCas); // Set selection to the accepted annotation and select it and load it into the detail editor - adapter.select(aVID, relation); + aState.getSelection().set(adapter.select(aVID, relation)); // Send a UI event that the suggestion has been accepted page.send(page, BREADTH, new AjaxRecommendationAcceptedEvent(aTarget, aState, aVID)); diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java index fa7e72568d6..c13152f343a 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java @@ -1081,9 +1081,12 @@ private AnnotationFS acceptOrCorrectSuggestion(String aSessionOwner, SourceDocum var aEnd = aSuggestion.getEnd(); var aValue = aSuggestion.getLabel(); - var candidates = aCas. select(aAdapter.getAnnotationTypeName()) // - .at(aBegin, aEnd) // - .asList(); + // https://github.com/apache/uima-uimaj/issues/345 + // var candidates = aCas. select(aAdapter.getAnnotationTypeName()) // + // .at(aBegin, aEnd) // + // .asList(); + var candidates = CasUtil.selectAt(aCas, + CasUtil.getType(aCas, aAdapter.getAnnotationTypeName()), aBegin, aEnd); var candidateWithEmptyLabel = candidates.stream() // .filter(c -> aAdapter.getFeatureValue(aFeature, c) == null) // @@ -2084,9 +2087,12 @@ private static Optional getOffsetsAnchoredOnSingleTokens(CAS aOriginalCa Annotation aPredictedAnnotation) { Type tokenType = getType(aOriginalCas, Token.class); - var tokens = aOriginalCas. select(tokenType) // - .coveredBy(aPredictedAnnotation) // - .limit(2).asList(); + // https://github.com/apache/uima-uimaj/issues/345 + // var tokens = aOriginalCas. select(tokenType) // + // .coveredBy(aPredictedAnnotation) // + // .limit(2).asList(); + var tokens = CasUtil.selectCovered(tokenType, aPredictedAnnotation).stream() // + .limit(2).collect(toList()); if (tokens.isEmpty()) { // This can happen if a recommender uses different token boundaries (e.g. if a @@ -2113,9 +2119,12 @@ private static Optional getOffsetsAnchoredOnSingleTokens(CAS aOriginalCa private static Optional getOffsetsAnchoredOnSentences(CAS aOriginalCas, Annotation aPredictedAnnotation) { - var sentences = aOriginalCas.select(Sentence.class) // - .coveredBy(aPredictedAnnotation) // - .asList(); + // https://github.com/apache/uima-uimaj/issues/345 + // var sentences = aOriginalCas.select(Sentence.class) // + // .coveredBy(aPredictedAnnotation) // + // .asList(); + var sentences = CasUtil.selectCovered(CasUtil.getType(aOriginalCas, Sentence.class), + aPredictedAnnotation); if (sentences.isEmpty()) { // This can happen if a recommender uses different token boundaries (e.g. if a @@ -2134,9 +2143,12 @@ private static Optional getOffsetsAnchoredOnSentences(CAS aOriginalCas, static Optional getOffsetsAnchoredOnTokens(CAS aOriginalCas, Annotation aPredictedAnnotation) { - var tokens = aOriginalCas.select(Token.class) // - .coveredBy(aPredictedAnnotation) // - .asList(); + // https://github.com/apache/uima-uimaj/issues/345 + // var tokens = aOriginalCas.select(Token.class) // + // .coveredBy(aPredictedAnnotation) // + // .asList(); + var tokens = CasUtil.selectCovered(CasUtil.getType(aOriginalCas, Token.class), + aPredictedAnnotation); if (tokens.isEmpty()) { if (aPredictedAnnotation.getBegin() == aPredictedAnnotation.getEnd()) { diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.java index bb97e38facc..5c6344ed73b 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/detail/AnnotationDetailEditorPanel.java @@ -1235,13 +1235,20 @@ public void onBulkAnnotationEvent(BulkAnnotationEvent aEvent) try { var selection = getModelObject().getSelection(); int id = selection.getAnnotation().getId(); - boolean annotationStillExists = getEditorCas().select(Annotation.class) // - .at(selection.getBegin(), selection.getEnd()) // + // https://github.com/apache/uima-uimaj/issues/345 + // boolean annotationStillExists = getEditorCas().select(Annotation.class) // + // .at(selection.getBegin(), selection.getEnd()) // + // .anyMatch(ann -> ann._id() == id); + var cas = getEditorCas(); + boolean annotationStillExists = CasUtil + .selectAt(cas, CasUtil.getType(cas, Annotation.class), selection.getBegin(), + selection.getEnd()) + .stream() // .anyMatch(ann -> ann._id() == id); + if (!annotationStillExists) { selection.clear(); refresh(aEvent.getRequestTarget()); - } } catch (Exception e) { From 03719c5ff5550325c3130e49ca9df7d4a9b67f25 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sat, 23 Sep 2023 13:44:23 +0200 Subject: [PATCH 7/9] #4158 - Exception when annotating something after a longer pause - Added new CAS Doctor check for unreachable feature structures - Updated UpgradeCasRepair to report garbarge-collected feature structures - Updated description of UpgradeCasRepair --- .../checks/UnreachableAnnotationsCheck.java | 100 ++++++++++++++++++ .../config/CasDoctorAutoConfiguration.java | 7 ++ .../diag/repairs/UpgradeCasRepair.java | 40 ++++++- .../asciidoc/user-guide/casdoctor.adoc | 18 +++- 4 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/UnreachableAnnotationsCheck.java diff --git a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/UnreachableAnnotationsCheck.java b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/UnreachableAnnotationsCheck.java new file mode 100644 index 00000000000..3f006d4bac8 --- /dev/null +++ b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/checks/UnreachableAnnotationsCheck.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.clarin.webanno.diag.checks; + +import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.getRealCas; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; +import static org.apache.uima.cas.impl.Serialization.deserializeCASComplete; +import static org.apache.uima.cas.impl.Serialization.serializeCASComplete; + +import java.util.List; +import java.util.Map; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.impl.CASImpl; +import org.apache.uima.resource.ResourceInitializationException; + +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil; +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.clarin.webanno.support.logging.LogMessage; + +public class UnreachableAnnotationsCheck + implements Check +{ + @Override + public boolean check(Project aProject, CAS aCas, List aMessages) + { + var casImpl = (CASImpl) getRealCas(aCas); + + var annotationCountsBefore = countFeatureStructures(casImpl); + + // Disable forced retaining of all assigned annotations so that during serialization, + // any temporary annotations that got potentially stuck in the CAS can be released. + var dummy = makeDummyCas(); + try (var ctx = casImpl.ll_enableV2IdRefs(false); + var ctx1 = dummy.ll_enableV2IdRefs(false)) { + var data = serializeCASComplete(casImpl); + deserializeCASComplete(data, dummy); + } + + var annotationCountsAfter = countFeatureStructures(dummy); + + var diffTypes = 0; + var totalDiff = 0; + for (var typeName : annotationCountsBefore.keySet().stream().sorted() + .toArray(String[]::new)) { + var before = annotationCountsBefore.getOrDefault(typeName, 0l); + var after = annotationCountsAfter.getOrDefault(typeName, 0l); + var diff = before - after; + totalDiff += diff; + if (diff > 0) { + diffTypes++; + aMessages.add(LogMessage.info(this, "Type [%s] has [%d] unreachable instances", + typeName, diff)); + } + } + + if (totalDiff > 0) { + if (diffTypes > 1) { + aMessages.add(LogMessage.info(this, + "A total of [%d] unreachable instances that were found", totalDiff)); + } + } + + return true; + } + + public static CASImpl makeDummyCas() + { + try { + return (CASImpl) WebAnnoCasUtil.getRealCas(WebAnnoCasUtil.createCas()); + } + catch (ResourceInitializationException e) { + throw new IllegalStateException(e); + } + } + + public static Map countFeatureStructures(CASImpl casImpl) + { + return WebAnnoCasUtil.findAllFeatureStructures(casImpl).stream() // + .map(fs -> fs.getType().getName()) // + .collect(groupingBy(identity(), counting())); + } +} diff --git a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/config/CasDoctorAutoConfiguration.java b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/config/CasDoctorAutoConfiguration.java index 861bdae1b09..81955c278fd 100644 --- a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/config/CasDoctorAutoConfiguration.java +++ b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/config/CasDoctorAutoConfiguration.java @@ -43,6 +43,7 @@ import de.tudarmstadt.ukp.clarin.webanno.diag.checks.RelationOffsetsCheck; import de.tudarmstadt.ukp.clarin.webanno.diag.checks.TokensAndSententencedDoNotOverlapCheck; import de.tudarmstadt.ukp.clarin.webanno.diag.checks.UniqueDocumentAnnotationCheck; +import de.tudarmstadt.ukp.clarin.webanno.diag.checks.UnreachableAnnotationsCheck; import de.tudarmstadt.ukp.clarin.webanno.diag.repairs.CoverAllTextInSentencesRepair; import de.tudarmstadt.ukp.clarin.webanno.diag.repairs.ReattachFeatureAttachedSpanAnnotationsAndDeleteExtrasRepair; import de.tudarmstadt.ukp.clarin.webanno.diag.repairs.ReattachFeatureAttachedSpanAnnotationsRepair; @@ -229,4 +230,10 @@ public TokensAndSententencedDoNotOverlapCheck tokensAndSententencedDoNotOverlapC { return new TokensAndSententencedDoNotOverlapCheck(); } + + @Bean + public UnreachableAnnotationsCheck unreachableAnnotationsCheck() + { + return new UnreachableAnnotationsCheck(); + } } diff --git a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/repairs/UpgradeCasRepair.java b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/repairs/UpgradeCasRepair.java index cf7d4299dc0..b5e3216f57e 100644 --- a/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/repairs/UpgradeCasRepair.java +++ b/inception/inception-diag/src/main/java/de/tudarmstadt/ukp/clarin/webanno/diag/repairs/UpgradeCasRepair.java @@ -17,11 +17,15 @@ */ package de.tudarmstadt.ukp.clarin.webanno.diag.repairs; +import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.getRealCas; +import static de.tudarmstadt.ukp.clarin.webanno.diag.checks.UnreachableAnnotationsCheck.countFeatureStructures; + import java.io.IOException; import java.util.List; import org.apache.uima.UIMAException; import org.apache.uima.cas.CAS; +import org.apache.uima.cas.impl.CASImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,12 +55,46 @@ public UpgradeCasRepair(AnnotationSchemaService aAnnotationService) public void repair(Project aProject, CAS aCas, List aMessages) { try { + var casImpl = (CASImpl) getRealCas(aCas); + + var annotationCountsBefore = countFeatureStructures(casImpl); + annotationService.upgradeCas(aCas, aProject); aMessages.add(LogMessage.info(this, "CAS upgraded.")); + + var annotationCountsAfter = countFeatureStructures(casImpl); + + var diffTypes = 0; + var totalDiff = 0; + var totalBefore = 0; + var totalAfter = 0; + for (var typeName : annotationCountsBefore.keySet().stream().sorted() + .toArray(String[]::new)) { + var before = annotationCountsBefore.getOrDefault(typeName, 0l); + var after = annotationCountsAfter.getOrDefault(typeName, 0l); + var diff = before - after; + totalDiff += diff; + totalBefore += before; + totalAfter += after; + if (diff > 0) { + diffTypes++; + aMessages.add(LogMessage.info(this, + "Type [%s] had [%d] unreachable instances that were removed (before: [%d], after: [%d])", + typeName, diff, before, after)); + } + } + + if (totalDiff > 0) { + if (diffTypes > 1) { + aMessages.add(LogMessage.info(this, + "A total of [%d] unreachable instances that were removed (before: [%d], after: [%d])", + totalDiff, totalBefore, totalAfter)); + } + } } catch (UIMAException | IOException e) { log.error("Unabled to access CAS", e); - aMessages.add(LogMessage.error(this, "Unabled to access CAS", e.getMessage())); + aMessages.add(LogMessage.error(this, "Unabled to access CAS: %s", e.getMessage())); } } } diff --git a/inception/inception-diag/src/main/resources/META-INF/asciidoc/user-guide/casdoctor.adoc b/inception/inception-diag/src/main/resources/META-INF/asciidoc/user-guide/casdoctor.adoc index bebcb37b867..e67d37c257f 100644 --- a/inception/inception-diag/src/main/resources/META-INF/asciidoc/user-guide/casdoctor.adoc +++ b/inception/inception-diag/src/main/resources/META-INF/asciidoc/user-guide/casdoctor.adoc @@ -173,9 +173,10 @@ dependent. ID:: `CASMetadataTypeIsPresentCheck` Related repairs:: <> -Checks if the ìnternal type `CASMetadata is defined in the type system of this CAS. If this is +Checks if the internal type `CASMetadata` is defined in the type system of this CAS. If this is not the case, then the application may not be able to detect concurrent modifications. + [[check_DanglingRelationsCheck]] === Dangling relations [horizontal] @@ -210,6 +211,16 @@ TSV or CoNLL formats will not include any text and annotations of parts of the d not covered by sentences or may produce errors during export. +[[check_UnreachableAnnotationsCheck]] +=== Unreachable annotations check +[horizontal] +ID:: `UnreachableAnnotationsCheck` +Related repairs:: <> + +Checks if there are any unreachable feature structures. Such feature structures take up memory, but +they are not regularly accessible. Such feature structures may be created as a result of bugs. +Removing them is harmless and reduces memory and disk space usage. + [[sect_repairs]] == Repairs @@ -332,6 +343,9 @@ ID:: `UpgradeCasRepair` Ensures that the CAS is up-to-date with the project type system. It performs the same operation which is regularly performed when a user opens a document for annotation/curation. +This repair also removes any unreachable feature structures. Such feature structures may be created as a result of bugs. +Removing them is harmless and reduces memory and disk space usage. + This is considered to be safe repair action as it only garbage-collects data from the CAS that is no longer reachable anyway. @@ -354,4 +368,4 @@ ID:: `CoverAllTextInSentencesRepair` This repair checks if there is any text not covered by sentences. If there is, it creates a new sentence annotation on this text starting at the end of the last sentence before it (or the start -of the document text) and the begin of the next sentence (or the end of the document text). \ No newline at end of file +of the document text) and the begin of the next sentence (or the end of the document text). From 3f55f2d70a8638ad318ea5c2e435592c3457682c Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sat, 23 Sep 2023 13:55:26 +0200 Subject: [PATCH 8/9] No issue: Enable parallel builds --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index ec7a44ba322..d4564e138e3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ config = [ agentLabel: '', maven: 'Maven 3', jdk: 'Zulu 11', - extraMavenArguments: '', + extraMavenArguments: '-Ddkpro.core.testCachePath="${WORKSPACE}/cache/dkpro-core-datasets" -T 4', wipeWorkspaceBeforeBuild: true, wipeWorkspaceAfterBuild: true ] From 70e7091e7be69a8a3d5d9adb3b2d7c9eee951683 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sat, 23 Sep 2023 14:57:46 +0200 Subject: [PATCH 9/9] #4158 - Exception when annotating something after a longer pause - Fix bad calls to CASUtil --- .../service/RecommendationServiceImpl.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java index c13152f343a..7d6ba5421c2 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java @@ -2091,7 +2091,10 @@ private static Optional getOffsetsAnchoredOnSingleTokens(CAS aOriginalCa // var tokens = aOriginalCas. select(tokenType) // // .coveredBy(aPredictedAnnotation) // // .limit(2).asList(); - var tokens = CasUtil.selectCovered(tokenType, aPredictedAnnotation).stream() // + var tokens = CasUtil + .selectCovered(aOriginalCas, tokenType, aPredictedAnnotation.getBegin(), + aPredictedAnnotation.getEnd()) + .stream() // .limit(2).collect(toList()); if (tokens.isEmpty()) { @@ -2123,8 +2126,9 @@ private static Optional getOffsetsAnchoredOnSentences(CAS aOriginalCas, // var sentences = aOriginalCas.select(Sentence.class) // // .coveredBy(aPredictedAnnotation) // // .asList(); - var sentences = CasUtil.selectCovered(CasUtil.getType(aOriginalCas, Sentence.class), - aPredictedAnnotation); + var sentences = CasUtil.selectCovered(aOriginalCas, + CasUtil.getType(aOriginalCas, Sentence.class), aPredictedAnnotation.getBegin(), + aPredictedAnnotation.getEnd()); if (sentences.isEmpty()) { // This can happen if a recommender uses different token boundaries (e.g. if a @@ -2147,8 +2151,8 @@ static Optional getOffsetsAnchoredOnTokens(CAS aOriginalCas, // var tokens = aOriginalCas.select(Token.class) // // .coveredBy(aPredictedAnnotation) // // .asList(); - var tokens = CasUtil.selectCovered(CasUtil.getType(aOriginalCas, Token.class), - aPredictedAnnotation); + var tokens = CasUtil.selectCovered(aOriginalCas, CasUtil.getType(aOriginalCas, Token.class), + aPredictedAnnotation.getBegin(), aPredictedAnnotation.getEnd()); if (tokens.isEmpty()) { if (aPredictedAnnotation.getBegin() == aPredictedAnnotation.getEnd()) {