我在将自定义批注文档转换为UIMA CASes,然后将它们序列化为XMI以便通过UIMA批注查看器GUI查看批注时遇到问题。
我正在使用uimaFIT来构建我的组件,因为它更易于控制,测试和调试。管道由3个组件构成:CollectionReader
组件使用原始文本读取文件。Annotator
组件,用于将注释从自定义文档转换为UIMA注释CasConsumer
组件,将CASes序列化为XMI
我的管道可以工作并在最后输出XMI文件,但没有注释。我不太清楚CAS对象如何在组件之间传递的。注释器逻辑包括对某些端点进行RESTful调用,并使用我试图转换注释模型的服务提供的客户端SDK。 Annotator
组件的转换逻辑部分如下所示:
public class CustomDocumentToUimaCasConverter implements UimaCasConverter {
private TypeSystemDescription tsd;
private AnnotatedDocument startDocument;
private ArrayFS annotationFeatureStructures;
private int featureStructureArrayCapacity;
public AnnotatedDocument getStartDocument() {
return startDocument;
}
public CustomDocumentToUimaCasConverter(AnnotatedDocument startDocument) {
try {
this.tsd = TypeSystemDescriptionFactory.createTypeSystemDescription();
} catch (ResourceInitializationException e) {
LOG.error("Error when creating default type system", e);
}
this.startDocument = startDocument;
}
public TypeSystemDescription getTypeSystemDescription() {
return this.tsd;
}
@Override
public void convertAnnotations(CAS cas) {
Map<String, List<Annotation>> entities = this.startDocument.entities;
int featureStructureArrayIndex = 0;
inferCasTypeSystem(entities.keySet());
try {
/*
* This is a hack allowing the CAS object to have an updated type system.
* We are creating a new CAS by passing the new TypeSystemDescription which actually
* should have been updated by an internal call of typeSystemInit(cas.getTypeSystem())
* originally part of the CasInitializer interface that is now deprecated and the CollectionReader
* is calling it internally in its implementation. The problem consists in the fact that now the
* the typeSystemInit method of the CasInitializer_ImplBase has an empty implementation and
* nothing changes!
*/
LOG.info("Creating new CAS with updated typesystem...");
cas = CasCreationUtils.createCas(tsd, null, null);
} catch (ResourceInitializationException e) {
LOG.info("Error creating new CAS!", e);
}
TypeSystem typeSystem = cas.getTypeSystem();
this.featureStructureArrayCapacity = entities.size();
this.annotationFeatureStructures = cas.createArrayFS(featureStructureArrayCapacity);
for (Map.Entry<String, List<Annotation>> entityEntry : entities.entrySet()) {
String annotationName = entityEntry.getKey();
annotationName = UIMA_ANNOTATION_TYPES_PACKAGE + removeDashes(annotationName);
Type type = typeSystem.getType(annotationName);
List<Annotation> annotations = entityEntry.getValue();
LOG.info("Get Type -> " + type);
for (Annotation ann : annotations) {
AnnotationFS afs = cas.createAnnotation(type, (int) ann.startOffset, (int) ann.endOffset);
cas.addFsToIndexes(afs);
if (featureStructureArrayIndex + 1 == featureStructureArrayCapacity) {
resizeArrayFS(featureStructureArrayCapacity * 2, annotationFeatureStructures, cas);
}
annotationFeatureStructures.set(featureStructureArrayIndex++, afs);
}
}
cas.removeFsFromIndexes(annotationFeatureStructures);
cas.addFsToIndexes(annotationFeatureStructures);
}
@Override
public void inferCasTypeSystem(Iterable<String> originalTypes) {
for (String typeName : originalTypes) {
//UIMA Annotations are not allowed to contain dashes
typeName = removeDashes(typeName);
tsd.addType(UIMA_ANNOTATION_TYPES_PACKAGE + typeName,
"Automatically generated type for " + typeName, "uima.tcas.Annotation");
LOG.info("Inserted new type -> " + typeName);
}
}
/**
* Removes dashes from UIMA Annotations because they are not allowed to contain dashes.
*
* @param typeName the annotation name of the current annotation of the source document
* @return the transformed annotation name suited for the UIMA typesystem
*/
private String removeDashes(String typeName) {
if (typeName.contains("-")) {
typeName = typeName.replaceAll("-", "_");
}
return typeName;
}
@Override
public void setSourceDocumentText(CAS cas) {
cas.setSofaDataString(startDocument.text, "text/plain");
}
private void resizeArrayFS(int newCapacity, ArrayFS originalArray, CAS cas) {
ArrayFS biggerArrayFS = cas.createArrayFS(newCapacity);
biggerArrayFS.copyFromArray(originalArray.toArray(), 0, 0, originalArray.size());
this.annotationFeatureStructures = biggerArrayFS;
this.featureStructureArrayCapacity = annotationFeatureStructures.size();
}
}
`
如果有人处理过将注释转换为UIMA类型的问题,我将不胜感激。
最佳答案
我认为您对CASes和注解的理解可能是错误的:
从
* This is a hack allowing the CAS object to have an updated type system.
和
LOG.info("Creating new CAS with updated typesystem...");
cas = CasCreationUtils.createCas(tsd, null, null);
我收集到您尝试在Annotator的process()方法中创建一个新的CAS(我假设您发布的代码在那里执行)。除非您要实现CAS乘法器,否则这不是做到这一点的方法。通常,collectionreader会摄取原始数据并在其getNext()方法中为您创建一个CAS。该CAS沿整个UIMA管道传递,您要做的就是向其中添加UIMA批注。
对于您要添加的每个注释,UIMA应该知道类型系统。如果使用JCasGen及其生成的代码,则应该没有问题。确保可以按照以下说明自动检测您的类型:http://uima.apache.org/d/uimafit-current/tools.uimafit.book.html#d5e531)。
这使您可以使用Java对象(而不是使用低级Fs调用)实例化注释。以下代码段在整个文档文本上添加了注释。在文本中的令牌及其摄取的(非UIMA)注释(使用Web服务)上添加迭代逻辑应该是微不足道的。
@Override
public void process(JCas aJCas) throws AnalysisEngineProcessException {
String text = aJCas.getDocumentText();
SomeAnnotation a = new SomeAnnotation(aJCas);
// set the annotation properties
// for each property, JCasGen should have
// generated a setter
a.setSomePropertyValue(someValue);
// add your annotation to the indexes
a.setBegin(0);
a.setEnd(text.length());
a.addToIndexes(aJCas);
}
为了避免弄乱String索引的开始和结束,我建议您使用一些Token注释(例如,来自DKPro Core的https://dkpro.github.io/dkpro-core/),您可以将其用作自定义注释的锚点。