You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ctakes.apache.org by se...@apache.org on 2017/11/08 14:46:21 UTC
svn commit: r1814583 - in
/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc: pretty/
pretty/html/ pretty/textspan/ property/plaintext/
Author: seanfinan
Date: Wed Nov 8 14:46:21 2017
New Revision: 1814583
URL: http://svn.apache.org/viewvc?rev=1814583&view=rev
Log:
Core Cas consumer updates, mostly for HtmlWriter
Added:
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/JsWriter.java
- copied, changed from r1811013, ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java
Modified:
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/SemanticGroup.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/DefaultTextSpan.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/TextSpan.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/property/plaintext/PropertyTextWriter.java
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/SemanticGroup.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/SemanticGroup.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/SemanticGroup.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/SemanticGroup.java Wed Nov 8 14:46:21 2017
@@ -2,6 +2,7 @@ package org.apache.ctakes.core.cc.pretty
import org.apache.ctakes.core.util.OntologyConceptUtil;
import org.apache.ctakes.typesystem.type.refsem.UmlsConcept;
+import org.apache.ctakes.typesystem.type.textsem.EntityMention;
import org.apache.ctakes.typesystem.type.textsem.EventMention;
import org.apache.ctakes.typesystem.type.textsem.IdentifiedAnnotation;
import org.apache.ctakes.typesystem.type.textsem.TimeMention;
@@ -14,6 +15,7 @@ import java.util.stream.Collectors;
* anatomical site, disease/disorder, finding (sign/symptom), test/procedure, and medication
*/
public enum SemanticGroup {
+ ///////// TODO
//////////////////////////// Similar is in org.apache.ctakes.dictionary.lookup2.util.SemanticUtil
//////////////////////////// and should be moved to core if this new class is taken up
// cTakes types
@@ -32,6 +34,8 @@ public enum SemanticGroup {
static public final String EVENT_CODE = "EVT";
static public final String TIMEX_SEMANTIC = "Time";
static public final String TIMEX_CODE = "TMX";
+ static public final String ENTITY_SEMANTIC = "Entity";
+ static public final String ENTITY_CODE = "ENT";
final private String _name;
final private String _code;
final private Collection<String> _tuis;
@@ -74,6 +78,8 @@ public enum SemanticGroup {
return Collections.singletonList( EVENT_SEMANTIC );
} else if ( annotation instanceof TimeMention ) {
return Collections.singletonList( TIMEX_SEMANTIC );
+ } else if ( annotation instanceof EntityMention ) {
+ return Collections.singletonList( ENTITY_SEMANTIC );
}
return Collections.emptyList();
}
@@ -101,6 +107,8 @@ public enum SemanticGroup {
return EVENT_SEMANTIC;
} else if ( annotation instanceof TimeMention ) {
return TIMEX_SEMANTIC;
+ } else if ( annotation instanceof EntityMention ) {
+ return ENTITY_SEMANTIC;
}
return getSimpleName( annotation );
}
@@ -126,13 +134,30 @@ public enum SemanticGroup {
* @return all semantic codes for the annotations
*/
static public Collection<String> getSemanticCodes( final Collection<IdentifiedAnnotation> annotations ) {
- return annotations.stream()
+ if ( annotations == null || annotations.isEmpty() ) {
+ return Collections.emptyList();
+ }
+ final Collection<String> umlsCodes = annotations.stream()
.map( OntologyConceptUtil::getUmlsConcepts )
.flatMap( Collection::stream )
.map( SemanticGroup::getSemanticCode )
.distinct()
.sorted()
.collect( Collectors.toList() );
+ if ( umlsCodes != null && !umlsCodes.isEmpty() ) {
+ return umlsCodes;
+ }
+ for ( IdentifiedAnnotation annotation : annotations ) {
+ final Class<? extends IdentifiedAnnotation> clazz = annotation.getClass();
+ if ( clazz.equals( EventMention.class ) ) {
+ return Collections.singletonList( EVENT_CODE );
+ } else if ( clazz.equals( TimeMention.class ) ) {
+ return Collections.singletonList( TIMEX_CODE );
+ } else if ( clazz.equals( EntityMention.class ) ) {
+ return Collections.singletonList( ENTITY_CODE );
+ }
+ }
+ return Collections.emptyList();
}
/**
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java Wed Nov 8 14:46:21 2017
@@ -31,12 +31,12 @@ final class CssWriter {
final File outputFile = new File( filePath );
outputFile.getParentFile().mkdirs();
try ( final BufferedWriter writer = new BufferedWriter( new FileWriter( outputFile ) ) ) {
-// writer.write( setBody() );
- writer.write( setUnderline( GENERIC, "gray", "solid", "0.10" ) );
- writer.write( setUnderline( AFFIRMED, "green", "solid", "0.15" ) );
+ writer.write( setLayout() );
+ writer.write( setUnderline( GENERIC, "gray", "solid", "0.12" ) );
+ writer.write( setUnderline( AFFIRMED, "green", "solid", "0.12" ) );
writer.write( setUnderline( UNCERTAIN, "gold", "dotted", "0.16" ) );
- writer.write( setUnderline( NEGATED, "red", "dashed", "0.16" ) );
- writer.write( setUnderline( UNCERTAIN_NEGATED, "orange", "dashed", "0.16" ) );
+ writer.write( setUnderline( NEGATED, "red", "dashed", "0.14" ) );
+ writer.write( setUnderline( UNCERTAIN_NEGATED, "orange", "dashed", "0.14" ) );
writer.write( setSuperColor( SemanticGroup.FINDING.getCode(), "magenta" ) );
writer.write( setSuperColor( SemanticGroup.DISORDER.getCode(), "black" ) );
@@ -44,18 +44,99 @@ final class CssWriter {
writer.write( setSuperColor( SemanticGroup.PROCEDURE.getCode(), "blue" ) );
writer.write( setSuperColor( SemanticGroup.ANATOMICAL_SITE.getCode(), "gray" ) );
writer.write( setSuperColor( SemanticGroup.UNKNOWN_SEMANTIC_CODE, "gray" ) );
+// writer.write( getListCss() );
writer.write( getToolTipCss() );
- writer.write( getRightDivCss() );
} catch ( IOException ioE ) {
LOGGER.error( "Could not not write css file " + outputFile.getPath() );
LOGGER.error( ioE.getMessage() );
}
}
-
- static private String setBody() {
- return "\nbody {\n" +
- " margin: 20px;\n" +
+ static private String setLayout() {
+ return ".flex-container {\n" +
+ " display: -webkit-flex;\n" +
+ " display: flex; \n" +
+ " -webkit-flex-flow: row wrap;\n" +
+ " flex-flow: row wrap;\n" +
+ " text-align: center;\n" +
+ "}\n" +
+ "\n" +
+ ".flex-container > * {\n" +
+ " padding: 15px;\n" +
+ " -webkit-flex: 1 100%;\n" +
+ " flex: 1 100%;\n" +
+ "}\n" +
+ "\n" +
+ ".article {\n" +
+ " text-align: left;\n" +
+ " line-height: 120%;\n" +
+ " word-spacing: 0.25em;\n" +
+ "}\n" +
+ "header {\n" +
+ " background: MidnightBlue;\n" +
+ " color: white;\n" +
+ " height: 30px;\n" +
+ "}\n" +
+ "header h1 {\n" +
+ " margin-top: 0px;\n" +
+ "}\n" +
+ "footer {\n" +
+ " background: SteelBlue;\n" +
+ " color: white;\n" +
+ " height: 10px;\n" +
+ "}\n" +
+ ".nav {\n" +
+ " display: flex;\n" +
+ " flex-direction: column;\n" +
+ " flex-shrink: 0;\n" +
+ " justify-content: space-between;\n" +
+ " max-width: 320px;\n" +
+ " background: PowderBlue;\n" +
+ "}\n" +
+ "@media all {\n" +
+ " .article {\n" +
+ " -webkit-flex: 5 0px;\n" +
+ " flex: 5 0px;\n" +
+ " -webkit-order: 1;\n" +
+ " order: 1;" +
+ " }\n" +
+ " .nav {\n" +
+ " text-align: left;\n" +
+ " -webkit-flex: 1 auto;\n" +
+ " flex: 1 auto;\n" +
+ " -webkit-order: 2;\n" +
+ " order: 2;\n" +
+ " }\n" +
+ " footer {\n" +
+ " -webkit-order: 3;\n" +
+ " order: 3;\n" +
+ " }\n" +
+ "}\n\n" +
+ "#ia {\n" +
+ " position: sticky;\n" +
+ " position: -webkit-sticky;\n" +
+ " top: 0;\n" +
+ " background: powderBlue;\n" +
+ " border-bottom: 2px solid navy;\n" +
+ " z-index: 10;\n" +
+ "}\n" +
+ ".legend {\n" +
+ " margin-left: auto;\n" +
+ " margin-right: auto;\n" +
+ " max-width: 300px;\n" +
+ " background: white;\n" +
+ " border: 2px solid navy;\n" +
+ " padding: 0 15px 15px 15px;\n" +
+ " z-index: 1;\n\n" +
+ "}\n" +
+ ".legend h3 {\n" +
+ " text-align: center;\n" +
+ "}\n" +
+ ".legend table {\n" +
+ " width: 100%;\n" +
+ "}\n" +
+ ".legend td {\n" +
+ " width: 50%;\n" +
"}\n";
}
@@ -67,6 +148,7 @@ final class CssWriter {
" position: relative;\n" +
" display: inline-block " + color + ";\n" +
" border-bottom: " + size + "em " + dashType + " " + color + ";\n" +
+ " border-radius: 5px;\n" +
"}\n";
}
@@ -82,6 +164,21 @@ final class CssWriter {
return "#" + idName + "{\n background-color: " + color + ";\n}\n";
}
+ static private String getListCss() {
+ return "\nul {\n" +
+ " list-style-type: none;\n" +
+ " margin: 0;\n" +
+ " padding: 0;\n" +
+ "}\n" +
+ "\nli {\n" +
+ " border: 1px solid lightgray;\n" +
+ " margin: 1px;\n" +
+ " margin-right: 5px;\n" +
+ " padding: 2px;\n" +
+ " padding-left: 5px;\n" +
+ "}\n";
+ }
+
static private String getToolTipCss() {
return
// position z
@@ -127,25 +224,5 @@ final class CssWriter {
"}\n";
}
-
- static private String getRightDivCss() {
- return "\ndiv#ia {\n" +
- " position: fixed;\n" +
- " top: 0;\n" +
- " right: 0;\n" +
- " width: 20%;\n" +
- " height: 100%;\n" +
- " padding: 10px;\n" +
- " overflow: auto;\n" +
- " background-color: lightgray;\n" +
- "}\n" +
- "\ndiv#content {\n" +
- " width: 79%;\n" +
- " height: 100%;\n" +
- " padding: 10px;\n" +
- " overflow: auto;\n" +
- "}\n";
- }
-
}
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java Wed Nov 8 14:46:21 2017
@@ -14,10 +14,10 @@ import org.apache.ctakes.typesystem.type
import org.apache.ctakes.typesystem.type.relation.BinaryTextRelation;
import org.apache.ctakes.typesystem.type.relation.CollectionTextRelation;
import org.apache.ctakes.typesystem.type.syntax.BaseToken;
-import org.apache.ctakes.typesystem.type.textsem.EventMention;
-import org.apache.ctakes.typesystem.type.textsem.IdentifiedAnnotation;
-import org.apache.ctakes.typesystem.type.textsem.TimeMention;
+import org.apache.ctakes.typesystem.type.syntax.ConllDependencyNode;
+import org.apache.ctakes.typesystem.type.textsem.*;
import org.apache.ctakes.typesystem.type.textspan.ListEntry;
+import org.apache.ctakes.typesystem.type.textspan.Paragraph;
import org.apache.ctakes.typesystem.type.textspan.Segment;
import org.apache.ctakes.typesystem.type.textspan.Sentence;
import org.apache.log4j.Logger;
@@ -34,9 +34,10 @@ import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import static org.apache.ctakes.core.cc.pretty.SemanticGroup.UNKNOWN_SEMANTIC_CODE;
+import static org.apache.ctakes.core.cc.pretty.SemanticGroup.*;
import static org.apache.ctakes.core.pipeline.PipeBitInfo.TypeProduct.*;
/**
@@ -57,7 +58,8 @@ final public class HtmlTextWriter extend
// TODO https://www.w3schools.com/html/tryit.asp?filename=tryhtml_layout_flexbox
// TODO https://www.w3schools.com/html/html5_new_elements.asp
-
+// TODO https://css-tricks.com/snippets/css/a-guide-to-flexbox/
+// TODO https://www.quackit.com/css/flexbox/tutorial/nested_flex_containers.cfm
static final String TOOL_TIP = "TIP";
@@ -66,16 +68,20 @@ final public class HtmlTextWriter extend
static final String UNCERTAIN = "UNC_";
static final String AFFIRMED = "AFF_";
static final String GENERIC = "GNR_";
- static private final String SPACER = "SPC_";
- static private final String NEWLINE = "NL_";
+ static final String SPACER = "SPC_";
+ static final String NEWLINE = "NL_";
+ static final String WIKI_BEGIN = "WIK_";
+ static final String WIKI_CENTER = "_WK_";
+ static final String WIKI_END = "_WIK";
static private final Logger LOGGER = Logger.getLogger( "HtmlTextWriter" );
static private final String PREFERRED_TERM_UNKNOWN = "Unknown Preferred Term";
-
+ static private final String CTAKES_VERSION = "4.0.1";
static private final String FILE_EXTENSION = ".pretty.html";
static private final String CSS_FILENAME = "ctakes.pretty.css";
+ static private final String JS_FILENAME = "ctakes.pretty.js";
private final Collection<String> _usedDirectories = new HashSet<>();
@@ -90,15 +96,22 @@ final public class HtmlTextWriter extend
if ( _usedDirectories.add( outputDir ) ) {
final String cssPath = outputDir + '/' + CSS_FILENAME;
CssWriter.writeCssFile( cssPath );
+ final String jsPath = outputDir + '/' + JS_FILENAME;
+ JsWriter.writeJsFile( jsPath );
}
final File htmlFile = new File( outputDir, fileName + FILE_EXTENSION );
LOGGER.info( "Writing HTML to " + htmlFile.getPath() + " ..." );
try ( final BufferedWriter writer = new BufferedWriter( new FileWriter( htmlFile ) ) ) {
final String title = DocumentIDAnnotationUtil.getDocumentID( jCas );
- writer.write( getHeader( title ) );
+ writer.write( startBody() );
writer.write( getCssLink( CSS_FILENAME ) );
- writeTitle( title, writer );
+ writer.write( getJsLink( JS_FILENAME ) );
+ writer.write( startContainer() );
+ writer.write( getHeader( title ) );
+ writer.write( getNav() );
+ writer.write( startArticle() );
+
final Collection<Segment> sections = JCasUtil.select( jCas, Segment.class );
final Map<Segment, Collection<org.apache.ctakes.typesystem.type.textspan.List>> lists
= JCasUtil.indexCovered( jCas, Segment.class, org.apache.ctakes.typesystem.type.textspan.List.class );
@@ -111,52 +124,266 @@ final public class HtmlTextWriter extend
final Map<Sentence, Collection<BaseToken>> sentenceTokens
= JCasUtil.indexCovered( jCas, Sentence.class, BaseToken.class );
final Collection<BinaryTextRelation> relations = JCasUtil.select( jCas, BinaryTextRelation.class );
+ // TODO at each paragraph end index add a newline unless it is the end of a section
+ final Collection<Paragraph> paragraphs = JCasUtil.select( jCas, Paragraph.class );
+ cullAnnotations( sentenceAnnotations.values() );
+
final Collection<CollectionTextRelation> corefRelations = JCasUtil.select( jCas, CollectionTextRelation.class );
+ final Map<Markable, TextSpan> markableSpans = mapMarkableSpans( jCas, corefRelations );
+ final Map<TextSpan, Collection<Integer>> corefSpans = createCorefSpans( corefRelations, markableSpans );
- final Map<Integer, Collection<Integer>> corefEnds = createCorefEnds( corefRelations );
+ writeSections( sections, paragraphs, lists, listEntries, sectionSentences, sentenceAnnotations, sentenceTokens, relations, corefSpans, writer );
+ writer.write( endArticle() );
+
+ writer.write( getFooter() );
+ writer.write( endContainer() );
- writeSections( sections, lists, listEntries, sectionSentences, sentenceAnnotations, sentenceTokens, relations, corefEnds, writer );
- writeInfoPane( writer );
writer.write( startJavascript() );
- writer.write( getSwapInfoScript() );
if ( !corefRelations.isEmpty() ) {
writeCorefInfos( corefRelations, writer );
}
writer.write( endJavascript() );
- writer.write( getFooter() );
+
+ writer.write( endBody() );
}
LOGGER.info( "Finished Writing" );
}
+ static private void cullAnnotations( final Collection<Collection<IdentifiedAnnotation>> sentenceAnnotations ) {
+ final java.util.function.Predicate<IdentifiedAnnotation> keep = a -> EventMention.class.isInstance( a )
+ || TimeMention.class.isInstance( a ) || EntityMention.class.isInstance( a );
+ final Collection<IdentifiedAnnotation> keepers = new HashSet<>();
+ for ( Collection<IdentifiedAnnotation> annotations : sentenceAnnotations ) {
+ annotations.stream().filter( keep ).forEach( keepers::add );
+ annotations.retainAll( keepers );
+ keepers.clear();
+ }
+ }
+
/**
* @param corefRelations coreference chains
* @return a map of markable text span ends to chain numbers
*/
- static private Map<Integer, Collection<Integer>> createCorefEnds( final Collection<CollectionTextRelation> corefRelations ) {
+ static private Map<TextSpan, Collection<Integer>> createCorefSpans( final Collection<CollectionTextRelation> corefRelations,
+ final Map<Markable, TextSpan> markableSpans ) {
if ( corefRelations == null || corefRelations.isEmpty() ) {
return Collections.emptyMap();
}
- final Map<Integer, Collection<Integer>> corefEnds = new HashMap<>();
+ final Map<TextSpan, Collection<Integer>> corefSpans = new HashMap<>();
int index = 1;
for ( CollectionTextRelation corefRelation : corefRelations ) {
final FSList chainHead = corefRelation.getMembers();
- final Collection<IdentifiedAnnotation> markables
- = FSCollectionFactory.create( chainHead, IdentifiedAnnotation.class );
- for ( IdentifiedAnnotation markable : markables ) {
- corefEnds.putIfAbsent( markable.getEnd(), new ArrayList<>() );
- corefEnds.get( markable.getEnd() ).add( index );
+ final Collection<Markable> markables = FSCollectionFactory.create( chainHead, Markable.class );
+ for ( Markable markable : markables ) {
+ final TextSpan span = markableSpans.get( markable );
+ corefSpans.putIfAbsent( span, new ArrayList<>() );
+ corefSpans.get( span ).add( index );
}
index++;
}
- return corefEnds;
+ return corefSpans;
}
/**
- * @param title normally the document title
- * @return html to set the header
+ * This is a bit messy, but necessary.
+ * @param jCas -
+ * @param corefRelations -
+ * @return map of markable to identified annotation
*/
- static private String getHeader( final String title ) {
- return "<!DOCTYPE html>\n<html>\n<head>\n <title>" + getSafeText( title ) + " Output</title>\n</head>\n<body>\n";
+ static private Map<Markable, TextSpan> mapMarkableSpans( final JCas jCas,
+ final Collection<CollectionTextRelation> corefRelations ) {
+ if ( corefRelations == null || corefRelations.isEmpty() ) {
+ return Collections.emptyMap();
+ }
+ final Map<Markable, Collection<ConllDependencyNode>> markableNodes = JCasUtil.indexCovered( jCas, Markable.class, ConllDependencyNode.class );
+ final Map<ConllDependencyNode, Collection<IdentifiedAnnotation>> nodeAnnotations
+ = JCasUtil.indexCovering( jCas, ConllDependencyNode.class, IdentifiedAnnotation.class );
+ cullAnnotations( nodeAnnotations.values() );
+ final Map<Markable, TextSpan> spanMap = new HashMap<>();
+ for ( CollectionTextRelation coref : corefRelations ) {
+ final Collection<Markable> markables = JCasUtil.select( coref.getMembers(), Markable.class );
+ for ( Markable markable : markables ) {
+ final Collection<ConllDependencyNode> nodes = markableNodes.get( markable );
+ if ( nodes == null || nodes.isEmpty() ) {
+ continue;
+ }
+ final ConllDependencyNode headNode = getNominalHeadNode( new ArrayList<>( nodes ) );
+ final Collection<IdentifiedAnnotation> annotations = nodeAnnotations.get( headNode );
+ if ( annotations == null || annotations.isEmpty() ) {
+ spanMap.put( markable, new DefaultTextSpan( headNode.getBegin(), headNode.getEnd() ) );
+ continue;
+ }
+ TextSpan bestSpan = null;
+ int bestLength = 0;
+ for ( IdentifiedAnnotation annotation : annotations ) {
+ if ( !EventMention.class.equals( annotation.getClass() )
+ && annotation.getBegin() == markable.getBegin() && annotation.getEnd() == markable.getEnd() ) {
+ // Prefer an exact non-event match over the longest match
+ bestSpan = new DefaultTextSpan( annotation.getBegin(), annotation.getEnd() );
+ break;
+ }
+ if ( annotation.getEnd() - annotation.getBegin() > bestLength ) {
+ bestLength = annotation.getEnd() - annotation.getBegin();
+ bestSpan = new DefaultTextSpan( annotation.getBegin(), annotation.getEnd() );
+ }
+ }
+ if ( bestSpan != null ) {
+ spanMap.put( markable, bestSpan );
+ } else {
+ spanMap.put( markable, new DefaultTextSpan( headNode.getBegin(), headNode.getEnd() ) );
+ }
+ }
+ }
+ return spanMap;
+ }
+
+ /**
+ * Finds the head node out of a few ConllDependencyNodes. Biased toward nouns.
+ **/
+ public static ConllDependencyNode getNominalHeadNode(
+ List<ConllDependencyNode> nodes ) {
+ ArrayList<ConllDependencyNode> anodes = new ArrayList<ConllDependencyNode>( nodes );
+ Boolean[][] matrixofheads = new Boolean[ anodes.size() ][ anodes.size() ];
+ List<ConllDependencyNode> outnodes = new ArrayList<ConllDependencyNode>();
+
+ // Remove root from consideration
+ for ( int i = 0; i < anodes.size(); i++ ) {
+ if ( anodes.get( i ).getId() == 0 ) {
+ anodes.remove( i );
+ }
+ }
+
+ // Create a dependency matrix
+ for ( int id1 = 0; id1 < anodes.size(); id1++ ) {
+ for ( int id2 = 0; id2 < anodes.size(); id2++ ) {
+ // no head-dependency relationship between id1 and id2
+ if ( id1 == id2 || anodes.get( id1 ).getId() != anodes.get( id2 ).getHead().getId() ) {
+ matrixofheads[ id2 ][ id1 ] = false;
+ }
+ // a match
+ else {
+ matrixofheads[ id2 ][ id1 ] = true;
+ }
+ }
+ }
+
+ // Search the dependency matrix for the head
+ for ( int idhd = 0; idhd < anodes.size(); idhd++ ) {
+ boolean occupiedCol = false;
+ for ( int row = 0; row < anodes.size(); row++ ) {
+ if ( matrixofheads[ row ][ idhd ] ) {
+ occupiedCol = true;
+ }
+ }
+ if ( occupiedCol ) {
+ boolean occupiedRow = false;
+ for ( int col = 0; col < anodes.size(); col++ ) {
+ if ( matrixofheads[ idhd ][ col ] ) {
+ occupiedRow = true;
+ }
+ }
+ if ( !occupiedRow ) {
+ outnodes.add( anodes.get( idhd ) );
+ }
+ }
+ }
+
+ // Unheaded phrases
+ if ( outnodes.isEmpty() ) {
+ // pick a noun from the left, if there is one
+ for ( int i = 0; i < anodes.size(); i++ ) {
+ if ( Pattern.matches( "N..?", anodes.get( i ).getPostag() ) ) {
+ return anodes.get( i );
+ }
+ }
+ // default to picking the rightmost node
+ return anodes.get( anodes.size() - 1 );
+ }
+ // Headed phrases
+ else {
+ // pick a noun from the left, if there is one
+ for ( int i = 0; i < outnodes.size(); i++ ) {
+ if ( Pattern.matches( "N..?", outnodes.get( i ).getPostag() ) ) {
+ return outnodes.get( i );
+ }
+ }
+ // otherwise, pick the rightmost node with dependencies
+ return outnodes.get( outnodes.size() - 1 );
+ }
+ }
+
+ // The assumption is that any given span can only have one exact EventMention.
+ static private IdentifiedAnnotation getEvent( final Collection<IdentifiedAnnotation> annotations ) {
+ return annotations.stream().filter( a -> EventMention.class.equals( a.getClass() ) ).findAny().orElse( null );
+ }
+
+ /**
+ * @param annotationMap -
+ * @return map of umls annotations to events
+ */
+ static private Map<IdentifiedAnnotation, IdentifiedAnnotation> getAnnotationEvents( final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap ) {
+ final Map<IdentifiedAnnotation, IdentifiedAnnotation> annotationEvents = new HashMap<>();
+ final Map<TextSpan, IdentifiedAnnotation> unusedEvents = new HashMap<>();
+ for ( Map.Entry<TextSpan, Collection<IdentifiedAnnotation>> entry : annotationMap.entrySet() ) {
+ final Collection<IdentifiedAnnotation> annotations = entry.getValue();
+ final IdentifiedAnnotation event = getEvent( annotations );
+ if ( event != null ) {
+ if ( annotations.size() > 1 ) {
+ final int pre = annotationEvents.size();
+ annotations.stream()
+ .filter( EventMention.class::isInstance )
+ .filter( a -> !event.equals( a ) )
+ .forEach( a -> annotationEvents.put( a, event ) );
+ if ( annotationEvents.size() > pre ) {
+ annotations.remove( event );
+ } else {
+ unusedEvents.put( entry.getKey(), event );
+ }
+ } else {
+ unusedEvents.put( entry.getKey(), event );
+ }
+ }
+ }
+ if ( unusedEvents.isEmpty() ) {
+ return annotationEvents;
+ }
+ final Map<TextSpan, IdentifiedAnnotation> usedEvents = new HashMap<>();
+ for ( Map.Entry<TextSpan, Collection<IdentifiedAnnotation>> entry : annotationMap.entrySet() ) {
+ final TextSpan span = entry.getKey();
+ TextSpan usedEventSpan = null;
+ for ( Map.Entry<TextSpan, IdentifiedAnnotation> unusedEvent : unusedEvents.entrySet() ) {
+ if ( !span.equals( unusedEvent.getKey() ) && span.contains( unusedEvent.getKey() ) ) {
+ entry.getValue().stream()
+ .filter( EventMention.class::isInstance )
+ .forEach( a -> annotationEvents.put( a, unusedEvent.getValue() ) );
+ usedEventSpan = unusedEvent.getKey();
+ usedEvents.put( usedEventSpan, unusedEvent.getValue() );
+ break;
+ }
+ }
+ if ( usedEventSpan != null ) {
+ unusedEvents.remove( usedEventSpan );
+ if ( unusedEvents.isEmpty() ) {
+ break;
+ }
+ }
+ }
+ usedEvents.forEach( ( s, e ) -> annotationMap.get( s ).remove( e ) );
+ final Collection<TextSpan> emptySpans = annotationMap.entrySet().stream()
+ .filter( e -> e.getValue().isEmpty() )
+ .map( Map.Entry::getKey )
+ .collect( Collectors.toList() );
+ annotationMap.keySet().removeAll( emptySpans );
+ return annotationEvents;
+ }
+
+ /**
+ * @return html to start the body
+ */
+ static private String startBody() {
+ return "<!DOCTYPE html>\n" +
+ "<html>\n" +
+ "<body>\n";
}
/**
@@ -168,19 +395,61 @@ final public class HtmlTextWriter extend
}
/**
- * Write html for document title and job completion time for document
- *
- * @param title normally document title, such as filename
- * @param writer writer to which pretty html for the section should be written
- * @throws IOException if the writer has issues
+ * @param filePath path to the js file
+ * @return html to link to js
*/
- static private void writeTitle( final String title, final BufferedWriter writer ) throws IOException {
- if ( !title.isEmpty() ) {
- writer.write( "\n<h2>" + getSafeText( title ) + "</h2>\n " );
- }
+ static private String getJsLink( final String filePath ) {
+ return "<script type=\"text/javascript\" src=\"ctakes.pretty.js\"></script>\n";
+ }
+
+ static private String startJavascript() {
+ return "<script type=\"text/javascript\">";
+ }
+
+ static private String endJavascript() {
+ return "</script>";
+ }
+
+ static private String startContainer() {
+ return "<div class=\"flex-container\">\n";
+ }
+
+ static private String getHeader( final String title ) {
+ return "<header>\n" +
+ " <h1>" + title + "</h1>\n" +
+ "</header>\n";
+ }
+
+ /**
+ * html for right-hand annotation information panel
+ */
+ static private String getNav() {
+ return "<nav class=\"nav\">\n" +
+ " <div id=\"ia\">\n" +
+ " Annotation Information\n" +
+ " </div>\n" +
+ getLegend() +
+ "</nav>\n";
+ }
+
+ static private String startArticle() {
+ return "<article class=\"article\">\n";
+ }
+
+ static private String endArticle() {
+ return "</article>\n";
+ }
+
+ static private String getFooter() {
final LocalDateTime time = LocalDateTime.now();
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "L dd yyyy, HH:mm:ss" );
- writer.write( "<i>Text processing finished on: " + formatter.format( time ) + "</i>\n<hr>\n" );
+ return "<footer>\n" +
+ "Processed by Apache cTAKES<sup>©</sup> on " + formatter.format( time ) + "\n" +
+ "</footer>\n";
+ }
+
+ static private String endContainer() {
+ return "</div>\n";
}
/**
@@ -190,17 +459,18 @@ final public class HtmlTextWriter extend
* @param sentenceAnnotations map of sentences and their contained annotations
* @param sentenceTokens map of sentences and their contained base tokens
* @param relations all relations
- * @param corefEnds map of text span ends to coreference chain indices
+ * @param corefSpans map of text spans to coreference chain indices
* @param writer writer to which pretty html for the section should be written
* @throws IOException if the writer has issues
*/
static private void writeSections( final Map<Segment, Collection<Sentence>> sectionSentences,
+ final Collection<Paragraph> paragraphs,
final Map<Sentence, Collection<IdentifiedAnnotation>> sentenceAnnotations,
final Map<Sentence, Collection<BaseToken>> sentenceTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
- writer.write( "\n<div id=\"content\">\n" );
+ final Collection<Integer> paragraphBegins = paragraphs.stream().map( Annotation::getBegin ).collect( Collectors.toList() );
final List<Segment> sections = new ArrayList<>( sectionSentences.keySet() );
sections.sort( Comparator.comparingInt( Segment::getBegin ) );
for ( Segment section : sections ) {
@@ -211,11 +481,13 @@ final public class HtmlTextWriter extend
for ( Sentence sentence : sentences ) {
final Collection<IdentifiedAnnotation> annotations = sentenceAnnotations.get( sentence );
final Collection<BaseToken> tokens = sentenceTokens.get( sentence );
- writeSentence( sentence, annotations, tokens, relations, corefEnds, writer );
+ writeSentence( sentence, annotations, tokens, relations, corefSpans, writer );
+ if ( paragraphBegins.contains( sentence.getEnd() ) ) {
+ writer.write( "\n</p>\n<p>\n" );
+ }
}
writer.write( "\n</p>\n" );
}
- writer.write( "\n</div>\n" );
}
/**
@@ -225,24 +497,25 @@ final public class HtmlTextWriter extend
* @param sentenceAnnotations map of sentences and their contained annotations
* @param sentenceTokens map of sentences and their contained base tokens
* @param relations all relations
- * @param corefEnds map of text span ends to coreference chain indices
+ * @param corefSpans map of text span ends to coreference chain indices
* @param writer writer to which pretty html for the section should be written
* @throws IOException if the writer has issues
*/
static private void writeSections( final Collection<Segment> sectionSet,
+ final Collection<Paragraph> paragraphs,
final Map<Segment, Collection<org.apache.ctakes.typesystem.type.textspan.List>> lists,
final Map<org.apache.ctakes.typesystem.type.textspan.List, Collection<ListEntry>> listEntries,
final Map<Segment, Collection<Sentence>> sectionSentences,
final Map<Sentence, Collection<IdentifiedAnnotation>> sentenceAnnotations,
final Map<Sentence, Collection<BaseToken>> sentenceTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
if ( lists.isEmpty() ) {
- writeSections( sectionSentences, sentenceAnnotations, sentenceTokens, relations, corefEnds, writer );
+ writeSections( sectionSentences, paragraphs, sentenceAnnotations, sentenceTokens, relations, corefSpans, writer );
return;
}
- writer.write( "\n<div id=\"content\">\n" );
+ final Collection<Integer> paragraphBegins = paragraphs.stream().map( Annotation::getBegin ).collect( Collectors.toList() );
final List<Segment> sections = new ArrayList<>( sectionSet );
sections.sort( Comparator.comparingInt( Segment::getBegin ) );
final Map<Integer, Integer> enclosers = new HashMap<>();
@@ -268,14 +541,14 @@ final public class HtmlTextWriter extend
if ( end != null ) {
freshEntry = true;
if ( currentEnd < 0 ) {
- startList( sentence, annotations, tokens, relations, corefEnds, writer );
+ startList( sentence, annotations, tokens, relations, corefSpans, writer );
currentEnd = end;
} else {
- writeListEntry( sentence, annotations, tokens, relations, corefEnds, writer );
+ writeListEntry( sentence, annotations, tokens, relations, corefSpans, writer );
}
} else {
if ( currentEnd >= 0 && sentence.getBegin() > currentEnd ) {
- endList( sentence, annotations, tokens, relations, corefEnds, writer );
+ endList( sentence, annotations, tokens, relations, corefSpans, writer );
currentEnd = -1;
freshEntry = false;
continue;
@@ -284,7 +557,10 @@ final public class HtmlTextWriter extend
freshEntry = false;
writer.write( "\n<br>\n" );
}
- writeSentence( sentence, annotations, tokens, relations, corefEnds, writer );
+ writeSentence( sentence, annotations, tokens, relations, corefSpans, writer );
+ if ( paragraphBegins.contains( sentence.getEnd() ) ) {
+ writer.write( "\n</p>\n<p>\n" );
+ }
}
}
if ( currentEnd >= 0 ) {
@@ -292,7 +568,6 @@ final public class HtmlTextWriter extend
}
writer.write( "\n</p>\n" );
}
- writer.write( "\n</div>\n" );
}
/**
@@ -301,16 +576,17 @@ final public class HtmlTextWriter extend
* @param annotations identified annotations in the section
* @param baseTokenMap baseTokens in the section
* @param relations all relations
- * @param corefEnds map of text span ends to coreference chain indices
+ * @param corefSpans map of text span ends to coreference chain indices
* @return marked up text
*/
static private String createLineText( final Sentence sentence,
final Collection<IdentifiedAnnotation> annotations,
final Map<TextSpan, String> baseTokenMap,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds ) {
+ final Map<TextSpan, Collection<Integer>> corefSpans ) {
final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap = createAnnotationMap( sentence, annotations );
- final Map<Integer, String> tags = createTags( sentence.getBegin(), annotationMap, relations, corefEnds );
+ final Map<IdentifiedAnnotation, IdentifiedAnnotation> annotationEvents = getAnnotationEvents( annotationMap );
+ final Map<Integer, String> tags = createTags( sentence, annotationMap, annotationEvents, relations, corefSpans );
final StringBuilder sb = new StringBuilder();
int previousIndex = -1;
for ( Map.Entry<TextSpan, String> entry : baseTokenMap.entrySet() ) {
@@ -338,7 +614,7 @@ final public class HtmlTextWriter extend
final Collection<IdentifiedAnnotation> annotations,
final Collection<BaseToken> baseTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
if ( baseTokens.isEmpty() ) {
return;
@@ -349,7 +625,7 @@ final public class HtmlTextWriter extend
return;
}
writer.write( "\n<ul>\n<li>" );
- final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefEnds );
+ final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefSpans );
writer.write( lineText );
}
@@ -367,7 +643,7 @@ final public class HtmlTextWriter extend
final Collection<IdentifiedAnnotation> annotations,
final Collection<BaseToken> baseTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
if ( baseTokens.isEmpty() ) {
return;
@@ -378,7 +654,7 @@ final public class HtmlTextWriter extend
return;
}
writer.write( "</li>\n<li>" );
- final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefEnds );
+ final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefSpans );
writer.write( lineText );
}
@@ -386,7 +662,7 @@ final public class HtmlTextWriter extend
final Collection<IdentifiedAnnotation> annotations,
final Collection<BaseToken> baseTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
if ( baseTokens.isEmpty() ) {
return;
@@ -396,7 +672,7 @@ final public class HtmlTextWriter extend
if ( baseTokenMap.isEmpty() ) {
return;
}
- final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefEnds );
+ final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefSpans );
writer.write( lineText + "</li>\n</ul>\n" );
}
@@ -439,7 +715,7 @@ final public class HtmlTextWriter extend
* @param annotations identified annotations in the section
* @param baseTokens baseTokens in the section
* @param relations all relations
- * @param corefEnds map of text span ends to coreference chain indices
+ * @param corefSpans map of text span ends to coreference chain indices
* @param writer writer to which pretty html for the section should be written
* @throws IOException if the writer has issues
*/
@@ -447,7 +723,7 @@ final public class HtmlTextWriter extend
final Collection<IdentifiedAnnotation> annotations,
final Collection<BaseToken> baseTokens,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds,
+ final Map<TextSpan, Collection<Integer>> corefSpans,
final BufferedWriter writer ) throws IOException {
if ( baseTokens.isEmpty() ) {
return;
@@ -457,7 +733,7 @@ final public class HtmlTextWriter extend
if ( baseTokenMap.isEmpty() ) {
return;
}
- final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefEnds );
+ final String lineText = createLineText( sentence, annotations, baseTokenMap, relations, corefSpans );
writer.write( lineText + "\n<br>\n" );
}
@@ -641,39 +917,59 @@ final public class HtmlTextWriter extend
return spanAnnotations;
}
+ static private Map<TextSpan, Collection<Integer>> getSentenceCorefs( final Sentence sentence,
+ final Map<TextSpan, Collection<Integer>> corefSpans ) {
+ final Map<TextSpan, Collection<Integer>> sentenceCorefs = new HashMap<>();
+ final int sentenceBegin = sentence.getBegin();
+ final int sentenceEnd = sentence.getEnd();
+ for ( Map.Entry<TextSpan, Collection<Integer>> entry : corefSpans.entrySet() ) {
+ final int entryBegin = entry.getKey().getBegin();
+ if ( entryBegin >= sentenceBegin && entryBegin < sentenceEnd ) {
+ sentenceCorefs.put(
+ new DefaultTextSpan( entryBegin - sentenceBegin, entry.getKey().getEnd() - sentenceBegin ),
+ entry.getValue() );
+ }
+ }
+ return sentenceCorefs;
+ }
+
/**
- * @param sentenceBegin begin offset of sentence
+ * @param sentence begin offset of sentence
* @param annotationMap map of all annotations within or overlapping the small span elements
* @param relations all relations
- * @param corefEnds map of text span ends to coreference chain indices
+ * @param corefSpans map of text span ends to coreference chain indices
* @return html for span elements
*/
- static private Map<Integer, String> createTags( final int sentenceBegin,
+ static private Map<Integer, String> createTags( final Sentence sentence,
final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap,
+ final Map<IdentifiedAnnotation, IdentifiedAnnotation> annotationEvents,
final Collection<BinaryTextRelation> relations,
- final Map<Integer, Collection<Integer>> corefEnds ) {
+ final Map<TextSpan, Collection<Integer>> corefSpans
+ ) {
if ( annotationMap.isEmpty() ) {
return Collections.emptyMap();
}
- final Map<Integer, Character> indexMap = createIndexMap( annotationMap.keySet() );
+ final Collection<TextSpan> spans = new HashSet<>( annotationMap.keySet() );
+ // TODO move coref adjustment uphill
+ final Map<TextSpan, Collection<Integer>> sentenceCorefs = getSentenceCorefs( sentence, corefSpans );
+ spans.addAll( sentenceCorefs.keySet() );
+ final Map<Integer, Character> indexMap = createIndexMap( spans );
final Collection<TextSpan> adjustedSpans = createAdjustedSpans( indexMap );
final List<TextSpan> adjustedList = new ArrayList<>( adjustedSpans );
adjustedList.sort( TEXT_SPAN_COMPARATOR );
final Map<TextSpan, Collection<IdentifiedAnnotation>> adjustedAnnotations
= createAdjustedAnnotations( adjustedList, annotationMap );
+ final int sentenceBegin = sentence.getBegin();
final Map<Integer, String> indexTags = new HashMap<>();
for ( TextSpan adjustedSpan : adjustedList ) {
final StringBuilder sb = new StringBuilder( "<span" );
final Collection<IdentifiedAnnotation> annotations = adjustedAnnotations.get( adjustedSpan );
- if ( annotations.isEmpty() ) {
- continue;
- }
final String polarityClasses = createPolaritiesText( annotations );
if ( !polarityClasses.isEmpty() ) {
sb.append( " class=\"" ).append( polarityClasses ).append( '\"' );
}
- final String clickInfo = createClickInfo( annotations, relations );
+ final String clickInfo = createClickInfo( annotations, annotationEvents, relations );
if ( !clickInfo.isEmpty() ) {
sb.append( " onClick=\"iaf(\'" ).append( clickInfo ).append( "\')\"" );
}
@@ -682,31 +978,36 @@ final public class HtmlTextWriter extend
sb.append( " " + TOOL_TIP + "=\"" ).append( tip ).append( '\"' );
}
sb.append( '>' );
-
// coref chain
+ final int adjustedEnd = sentenceBegin + adjustedSpan.getEnd();
final StringBuilder sb2 = new StringBuilder();
- final Collection<IdentifiedAnnotation> endAnnotations = annotations.stream()
- .filter( a -> a.getEnd() == (sentenceBegin + adjustedSpan.getEnd()) )
- .collect( Collectors.toSet() );
+ final Collection<IdentifiedAnnotation> endAnnotations = getEndAnnotations( annotations, adjustedEnd );
final Collection<String> semanticCodes = SemanticGroup.getSemanticCodes( endAnnotations );
- final Collection<Integer> chains = corefEnds.get( sentenceBegin + adjustedSpan.getEnd() );
+ String semantic = semanticCodes.stream().findAny().orElse( ENTITY_CODE );
+ if ( annotations != null && endAnnotations.size() != annotations.size() ) {
+ semantic += " " + polarityClasses;
+ }
+ final Collection<Integer> chains = sentenceCorefs.get( adjustedSpan );
if ( chains != null && !chains.isEmpty() ) {
- String semantic = semanticCodes.stream().findAny().orElse( UNKNOWN_SEMANTIC_CODE );
- if ( endAnnotations.size() != annotations.size() ) {
- semantic += " " + polarityClasses;
- }
for ( Integer chain : chains ) {
sb2.append( "<span class=\"" ).append( semantic ).append( "\"" );
sb2.append( " onClick=\"crf" ).append( chain ).append( "()\">" );
sb2.append( "<sup>" ).append( chain ).append( "</sup></span>" );
}
} else {
- for ( String semantic : semanticCodes ) {
- sb2.append( "<span class=\"" ).append( semantic );
+ for ( String semanticCode : semanticCodes ) {
+ if ( semanticCode.equals( EVENT_CODE )
+ || semanticCode.equals( TIMEX_CODE )
+ || semanticCode.equals( ENTITY_CODE )
+ || semanticCode.equals( UNKNOWN_SEMANTIC_CODE ) ) {
+ continue;
+ }
+ sb2.append( "<span class=\"" ).append( semanticCode );
if ( endAnnotations.size() != annotations.size() ) {
sb2.append( " " ).append( polarityClasses );
}
- sb2.append( "\"><sup>" ).append( "•" ).append( "</sup></span>" );
+// sb2.append( "\"><sup>•</sup></span>" );
+ sb2.append( "\"><sup>&#" ).append( getSemanticSymbol( semanticCode ) ).append( ";</sup></span>" );
}
}
@@ -718,17 +1019,32 @@ final public class HtmlTextWriter extend
return indexTags;
}
+ static private Collection<IdentifiedAnnotation> getEndAnnotations( final Collection<IdentifiedAnnotation> annotations,
+ final int adjustedEnd ) {
+ if ( annotations == null || annotations.isEmpty() ) {
+ return Collections.emptyList();
+ }
+ return annotations.stream()
+ .filter( a -> a.getEnd() == adjustedEnd )
+ .collect( Collectors.toSet() );
+ }
+
/**
* @param annotations -
* @return html with annotation information: polarity, semantic, cui, text, pref text
*/
static private String createClickInfo( final Collection<IdentifiedAnnotation> annotations,
+ final Map<IdentifiedAnnotation, IdentifiedAnnotation> annotationEvents,
final Collection<BinaryTextRelation> relations ) {
+ if ( annotations == null || annotations.isEmpty() ) {
+ return "";
+ }
final Map<String, Map<String, Collection<String>>> polarInfoMap = new HashMap<>();
for ( IdentifiedAnnotation annotation : annotations ) {
final String polarity = createPolarity( annotation );
polarInfoMap.putIfAbsent( polarity, new HashMap<>() );
- final Map<String, Collection<String>> infoMap = createInfoMap( annotation, relations );
+ final IdentifiedAnnotation event = annotationEvents.get( annotation );
+ final Map<String, Collection<String>> infoMap = createInfoMap( annotation, event, relations );
for ( Map.Entry<String, Collection<String>> infoEntry : infoMap.entrySet() ) {
polarInfoMap.get( polarity ).putIfAbsent( infoEntry.getKey(), new HashSet<>() );
polarInfoMap.get( polarity ).get( infoEntry.getKey() ).addAll( infoEntry.getValue() );
@@ -759,16 +1075,21 @@ final public class HtmlTextWriter extend
* @return map of semantic to text for annotations
*/
static private Map<String, Collection<String>> createInfoMap( final IdentifiedAnnotation annotation,
+ final IdentifiedAnnotation event,
final Collection<BinaryTextRelation> relations ) {
final Collection<UmlsConcept> concepts = OntologyConceptUtil.getUmlsConcepts( annotation );
final Map<String, Collection<String>> semanticMap = new HashMap<>();
final String coveredText = getCoveredText( annotation );
final String safeText = getSafeText( coveredText );
- final String relationText = getRelationText( annotation, relations );
+ String relationText = getRelationText( annotation, relations );
+ if ( event != null ) {
+ relationText += getRelationText( event, relations );
+ }
for ( UmlsConcept concept : concepts ) {
final String semanticCode = SemanticGroup.getSemanticCode( concept );
semanticMap.putIfAbsent( semanticCode, new HashSet<>() );
- String text = safeText + NEWLINE + getCodes( concept ) + getPreferredText( coveredText, concept ) + relationText;
+ final String prefText = getPreferredText( coveredText, concept );
+ String text = getWikiText( safeText, prefText ) + NEWLINE + getCodes( concept ) + getCodedPrefText( prefText ) + relationText;
if ( annotation instanceof EventMention ) {
text += getDocTimeRel( (EventMention) annotation );
}
@@ -782,6 +1103,8 @@ final public class HtmlTextWriter extend
postText = getDocTimeRel( (EventMention) annotation );
} else if ( annotation instanceof TimeMention ) {
semanticCode = SemanticGroup.TIMEX_CODE;
+ } else if ( annotation instanceof EntityMention ) {
+ semanticCode = SemanticGroup.ENTITY_CODE;
}
if ( !semanticCode.isEmpty() ) {
semanticMap.putIfAbsent( semanticCode, new HashSet<>() );
@@ -791,6 +1114,10 @@ final public class HtmlTextWriter extend
return semanticMap;
}
+ static private String createWikiLink( final String coveredText, final String wikiText ) {
+ return WIKI_BEGIN + wikiText + WIKI_CENTER + coveredText + WIKI_END;
+ }
+
/**
* @param concept -
* @return cui if it exists and any codes if they exist
@@ -828,11 +1155,28 @@ final public class HtmlTextWriter extend
&& !preferredText.equalsIgnoreCase( coveredText )
&& !preferredText.equalsIgnoreCase( coveredText + 's' )
&& !coveredText.equalsIgnoreCase( preferredText + 's' ) ) {
- return SPACER + "[" + getSafeText( preferredText ) + "]" + NEWLINE;
+ return getSafeText( preferredText );
}
return "";
}
+ static private String getCodedPrefText( final String preferredText ) {
+ if ( !preferredText.isEmpty() ) {
+ return SPACER + "[" + preferredText + "]" + NEWLINE;
+ }
+ return "";
+ }
+
+ static private String getWikiText( final String coveredText, final String preferredText ) {
+ String wikiText = coveredText;
+ // oddly enough, searches more frequently have the covered text instead of the preferred text
+// if ( preferredText != null && !preferredText.isEmpty() && !preferredText.contains( "," ) ) {
+// wikiText = preferredText;
+// }
+// return WIKI_BEGIN + wikiText.replace( ' ', '_' ).toLowerCase() + WIKI_CENTER + coveredText + WIKI_END; // wikipedia
+// return WIKI_BEGIN + wikiText.replace( ' ', '%' ).toLowerCase() + WIKI_CENTER + coveredText + WIKI_END; // webmd
+ return WIKI_BEGIN + wikiText.replace( ' ', '+' ).toLowerCase() + WIKI_CENTER + coveredText + WIKI_END; // most sites
+ }
/**
* @param eventMention -
@@ -859,6 +1203,9 @@ final public class HtmlTextWriter extend
* @return polarity representation for all provided annotations
*/
static private String createPolaritiesText( final Collection<IdentifiedAnnotation> annotations ) {
+ if ( annotations == null || annotations.isEmpty() ) {
+ return GENERIC;
+ }
return annotations.stream()
.map( HtmlTextWriter::createPolarity )
.distinct()
@@ -871,7 +1218,7 @@ final public class HtmlTextWriter extend
* @return polarity for a single annotation
*/
static private String createPolarity( final IdentifiedAnnotation annotation ) {
- if ( annotation instanceof TimeMention ) {
+ if ( annotation instanceof TimeMention || annotation instanceof EntityMention ) {
return GENERIC;
}
if ( annotation.getPolarity() < 0 ) {
@@ -892,6 +1239,9 @@ final public class HtmlTextWriter extend
* @return tooltip text with semantic names for given annotations
*/
static private String createTipText( final Collection<IdentifiedAnnotation> annotations ) {
+ if ( annotations == null || annotations.isEmpty() ) {
+ return "";
+ }
final Map<String, Integer> semanticCounts = getSemanticCounts( annotations );
final List<String> semantics = new ArrayList<>( semanticCounts.keySet() );
Collections.sort( semantics );
@@ -907,13 +1257,15 @@ final public class HtmlTextWriter extend
return sb.toString();
}
- static private String getRelationText( final IdentifiedAnnotation annotation, final Collection<BinaryTextRelation> relations ) {
+ static private String getRelationText( final IdentifiedAnnotation annotation,
+ final Collection<BinaryTextRelation> relations ) {
return relations.stream()
.map( r -> getRelationText( annotation, r ) )
.collect( Collectors.joining() );
}
- static private String getRelationText( final IdentifiedAnnotation annotation, final BinaryTextRelation relation ) {
+ static private String getRelationText( final IdentifiedAnnotation annotation,
+ final BinaryTextRelation relation ) {
if ( relation.getArg1().getArgument().equals( annotation ) ) {
return SPACER + "[" + relation.getCategory() + "] " + getSafeText( relation.getArg2().getArgument() ) + NEWLINE;
} else if ( relation.getArg2().getArgument().equals( annotation ) ) {
@@ -959,16 +1311,23 @@ final public class HtmlTextWriter extend
return semanticCounts;
}
- /**
- * writes html for right-hand annotation information panel
- * @param writer writer to which pretty html for the section should be written
- * @throws IOException if the writer has issues
- */
- static private void writeInfoPane( final BufferedWriter writer ) throws IOException {
- writer.write( "\n<div id=\"ia\"> Annotation Information </div>\n" );
+ static private int getSemanticSymbol( final String semanticCode ) {
+ if ( semanticCode.equals( SemanticGroup.ANATOMICAL_SITE.getCode() ) ) {
+ return 9673; // fisheye / target
+ } else if ( semanticCode.equals( SemanticGroup.FINDING.getCode() ) ) {
+ return 8226; // round bullet
+ } else if ( semanticCode.equals( SemanticGroup.PROCEDURE.getCode() ) ) {
+ return 9670; // diamond
+ } else if ( semanticCode.equals( SemanticGroup.DISORDER.getCode() ) ) {
+ return 9661; // down triangle
+ } else if ( semanticCode.equals( SemanticGroup.MEDICATION.getCode() ) ) {
+ return 9651; // up triangle
+ }
+ return 9726; // filled square
}
/**
+ * This method needs to be in this class so that it can properly link the coref chain numbers
* @param corefRelations -
* @param writer writer to which pretty html for the section should be written
* @throws IOException if the writer has issues
@@ -993,55 +1352,58 @@ final public class HtmlTextWriter extend
}
}
- /**
- * A javascript function is used to expand annotation tooltips into formatted html
- * @return javascript
- */
- static private String getSwapInfoScript() {
- return " function iaf(txt) {\n" +
- " var aff=txt.replace( /" + AFFIRMED + "/g,\"<br><h3>Affirmed</h3>\" );\n" +
- " var neg=aff.replace( /" + NEGATED + "/g,\"<br><h3>Negated</h3>\" );\n" +
- " var unc=neg.replace( /" + UNCERTAIN + "/g,\"<br><h3>Uncertain</h3>\" );\n" +
- " var unn=unc.replace( /" + UNCERTAIN_NEGATED + "/g,\"<br><h3>Uncertain, Negated</h3>\" );\n" +
- " var ant=unn.replace( /" + SemanticGroup.ANATOMICAL_SITE.getCode() + "/g,\"<b>Anatomical Site</b>\" );\n" +
- " var dis=ant.replace( /" + SemanticGroup.DISORDER.getCode() + "/g,\"<b>Disease/ Disorder</b>\" );\n" +
- " var fnd=dis.replace( /" + SemanticGroup.FINDING.getCode() + "/g,\"<b>Sign/ Symptom</b>\" );\n" +
- " var prc=fnd.replace( /" + SemanticGroup.PROCEDURE.getCode() + "/g,\"<b>Procedure</b>\" );\n" +
- " var drg=prc.replace( /" + SemanticGroup.MEDICATION.getCode() + "/g,\"<b>Medication</b>\" );\n" +
- " var evt=drg.replace( /" + SemanticGroup.EVENT_CODE + "/g,\"<b>Event</b>\" );\n" +
- " var tmx=evt.replace( /" + SemanticGroup.TIMEX_CODE + "/g,\"<b>Time</b>\" );\n" +
- " var unk=tmx.replace( /" + SemanticGroup.UNKNOWN_SEMANTIC_CODE + "/g,\"<b>Unknown</b>\" );\n" +
- " var spc=unk.replace( /" + SPACER + "/g,\" \" );\n" +
- " var prf1=spc.replace( /\\[/g,\"<i>\" );\n" +
- " var prf2=prf1.replace( /\\]/g,\"</i>\" );\n" +
- " var nl=prf2.replace( /" + NEWLINE + "/g,\"<br>\" );\n" +
- " document.getElementById(\"ia\").innerHTML = nl;\n" +
- " }\n";
+ static private String getLegend() {
+ return "<div class=\"legend\"><h3>Legend</h3>\n" +
+ " <hr>\n" +
+ " <table style=\"line-height: 120%\">\n" +
+ " <tr>\n" +
+ " <td><span class=\"AFF_\">Affirmed Event</span></td>\n" +
+ " <td><span class=\"NEG_\">Negated Event</span></td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td><span class=\"UNC_\">Uncertain Event</span></td>\n" +
+ " <td><span class=\"UNN_\">Uncertain Negated</span></td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td><span class=\"GNR_\">Time or Generic</span></td>\n" +
+ " </tr>\n" +
+ " </table>\n" +
+ " <hr>\n" +
+ " <table>\n" +
+ " <tr>\n" +
+ " <td>Sign / Symptom<span class=\"FND\"><sup>&#"
+ + getSemanticSymbol( SemanticGroup.FINDING.getCode() ) + ";</sup></span></td>\n" +
+ " <td>Procedure<span class=\"PRC\"><sup>&#"
+ + getSemanticSymbol( SemanticGroup.PROCEDURE.getCode() ) + ";</sup></span></td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td>Disease / Disorder<span class=\"DIS\"><sup>&#"
+ + getSemanticSymbol( SemanticGroup.DISORDER.getCode() ) + ";</sup></span></td>\n" +
+ " <td>Medication<span class=\"DRG\"><sup>&#"
+ + getSemanticSymbol( SemanticGroup.MEDICATION.getCode() ) + ";</sup></span></td>\n" +
+ " </tr>\n" +
+ " <tr>\n" +
+ " <td>Anatomical Site<span class=\"ANT\"><sup>&#"
+ + getSemanticSymbol( SemanticGroup.ANATOMICAL_SITE.getCode() ) + ";</sup></span></td>\n" +
+ " </tr>\n" +
+ " </table>\n" +
+ " <hr>\n" +
+ " <table>\n" +
+ " <tr>\n" +
+ " <td>Coreference Element<span class=\"ENT\"><sup>1</sup></span></td>\n" +
+ " </tr>\n" +
+ " </table>\n" +
+ "</div>\n";
}
/**
*
- * @return html to write footer
+ * @return html to end body
*/
- static private String getFooter() {
+ static private String endBody() {
return "</body>\n" +
"</html>\n";
}
- /**
- *
- * @return html to start javascript section
- */
- static private String startJavascript() {
- return "<script type=\"text/javascript\">\n";
- }
-
- /**
- *
- * @return html to end javascript section
- */
- static private String endJavascript() {
- return "</script>";
- }
}
Copied: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/JsWriter.java (from r1811013, ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java)
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/JsWriter.java?p2=ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/JsWriter.java&p1=ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java&r1=1811013&r2=1814583&rev=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/JsWriter.java Wed Nov 8 14:46:21 2017
@@ -16,136 +16,72 @@ import static org.apache.ctakes.core.cc.
* @version %I%
* @since 10/15/2016
*/
-final class CssWriter {
+final class JsWriter {
- static private final Logger LOGGER = Logger.getLogger( "CssWriter" );
+ static private final Logger LOGGER = Logger.getLogger( "JsWriter" );
- private CssWriter() {
+ private JsWriter() {
}
/**
* @param filePath path to css file
*/
- static void writeCssFile( final String filePath ) {
+ static void writeJsFile( final String filePath ) {
final File outputFile = new File( filePath );
outputFile.getParentFile().mkdirs();
try ( final BufferedWriter writer = new BufferedWriter( new FileWriter( outputFile ) ) ) {
-// writer.write( setBody() );
- writer.write( setUnderline( GENERIC, "gray", "solid", "0.10" ) );
- writer.write( setUnderline( AFFIRMED, "green", "solid", "0.15" ) );
- writer.write( setUnderline( UNCERTAIN, "gold", "dotted", "0.16" ) );
- writer.write( setUnderline( NEGATED, "red", "dashed", "0.16" ) );
- writer.write( setUnderline( UNCERTAIN_NEGATED, "orange", "dashed", "0.16" ) );
-
- writer.write( setSuperColor( SemanticGroup.FINDING.getCode(), "magenta" ) );
- writer.write( setSuperColor( SemanticGroup.DISORDER.getCode(), "black" ) );
- writer.write( setSuperColor( SemanticGroup.MEDICATION.getCode(), "red" ) );
- writer.write( setSuperColor( SemanticGroup.PROCEDURE.getCode(), "blue" ) );
- writer.write( setSuperColor( SemanticGroup.ANATOMICAL_SITE.getCode(), "gray" ) );
- writer.write( setSuperColor( SemanticGroup.UNKNOWN_SEMANTIC_CODE, "gray" ) );
- writer.write( getToolTipCss() );
- writer.write( getRightDivCss() );
+ writer.write( getSwapInfoScript() );
} catch ( IOException ioE ) {
- LOGGER.error( "Could not not write css file " + outputFile.getPath() );
+ LOGGER.error( "Could not not write js file " + outputFile.getPath() );
LOGGER.error( ioE.getMessage() );
}
}
-
- static private String setBody() {
- return "\nbody {\n" +
- " margin: 20px;\n" +
- "}\n";
- }
-
- // dashType is solid or dashed or double or dotted size is relative: 0.1 or 0.2 for 10%, 20%
- // See https://css-tricks.com/styling-underlines-web/ shadow for another possibility
- static private String setUnderline( final String className, final String color, final String dashType,
- final String size ) {
- return "\n." + className + " {\n" +
- " position: relative;\n" +
- " display: inline-block " + color + ";\n" +
- " border-bottom: " + size + "em " + dashType + " " + color + ";\n" +
- "}\n";
- }
-
- static private String setSuperColor( final String className, final String color ) {
- return "\n." + className + " {\n" +
- " color: " + color + ";\n" +
- "}\n";
- }
-
-
- static private String setHighlight( final String idName, final String color ) {
- // PowderBlue
- return "#" + idName + "{\n background-color: " + color + ";\n}\n";
- }
-
- static private String getToolTipCss() {
- return
- // position z
- "\n[" + TOOL_TIP + "] {\n" +
- " position: relative;\n" +
- " z-index: 2;\n" +
- " cursor: pointer;\n" +
- "}\n" +
- // invisible
- "[" + TOOL_TIP + "]::before,\n" +
- "[" + TOOL_TIP + "]::after {\n" +
- " visibility: hidden;\n" +
- " -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";\n" +
- " filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=0);\n" +
- " opacity: 0;\n" +
- " pointer-events: none;\n" +
- "}\n" +
- // position & sketch
- "[" + TOOL_TIP + "]::before {\n" +
- " position: absolute;\n" +
- " bottom: 0%;\n" +
- " left: 100%;\n" +
- " margin-bottom: 5px;\n" +
- " padding: 7px;\n" +
- " -webkit-border-radius: 3px;\n" +
- " -moz-border-radius: 3px;\n" +
- " border-radius: 3px;\n" +
- " background-color: #000;\n" +
- " background-color: hsla(0, 0%, 20%, 0.9);\n" +
- " color: #fff;\n" +
- " content: attr(" + TOOL_TIP + ");\n" +
- " text-align: center;\n" +
- " font-size: 14px;\n" +
- " line-height: 1.2;\n" +
- "}\n" +
- // hover show
- "[" + TOOL_TIP + "]:hover::before,\n" +
- "[" + TOOL_TIP + "]:hover::after {\n" +
- " visibility: visible;\n" +
- " -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)\";\n" +
- " filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=100);\n" +
- " opacity: 1;\n" +
- "}\n";
- }
-
-
- static private String getRightDivCss() {
- return "\ndiv#ia {\n" +
- " position: fixed;\n" +
- " top: 0;\n" +
- " right: 0;\n" +
- " width: 20%;\n" +
- " height: 100%;\n" +
- " padding: 10px;\n" +
- " overflow: auto;\n" +
- " background-color: lightgray;\n" +
- "}\n" +
- "\ndiv#content {\n" +
- " width: 79%;\n" +
- " height: 100%;\n" +
- " padding: 10px;\n" +
- " overflow: auto;\n" +
- "}\n";
- }
+ /**
+ * A javascript function is used to expand annotation tooltips into formatted html
+ *
+ * @return javascript
+ */
+ static private String getSwapInfoScript() {
+ return " function iaf(txt) {\n" +
+ " var aff=txt.replace( /" + AFFIRMED + "/g,\"<br><h3>Affirmed</h3>\" );\n" +
+ " var neg=aff.replace( /" + NEGATED + "/g,\"<br><h3>Negated</h3>\" );\n" +
+ " var unc=neg.replace( /" + UNCERTAIN + "/g,\"<br><h3>Uncertain</h3>\" );\n" +
+ " var unn=unc.replace( /" + UNCERTAIN_NEGATED + "/g,\"<br><h3>Uncertain, Negated</h3>\" );\n" +
+ " var gnr=unn.replace( /" + GENERIC + "/g,\"\" );\n" +
+
+ " var wik1=gnr.replace( /" + WIKI_BEGIN
+ + "/g,\"<a href=\\\"https://vsearch.nlm.nih.gov/vivisimo/cgi-bin/query-meta?v%3Aproject=medlineplus&v%3Asources=medlineplus-bundle&query=\" );\n" +
+ " var wik2=wik1.replace( /" + WIKI_CENTER + "/g,\"\\\" target=\\\"_blank\\\">\" );\n" +
+ " var wik3=wik2.replace( /" + WIKI_END + "/g,\"</a>\" );\n" +
+
+ " var ant=wik3.replace( /" + SemanticGroup.ANATOMICAL_SITE.getCode() + "/g,\"<b>Anatomical Site</b>\" );\n" +
+ " var dis=ant.replace( /" + SemanticGroup.DISORDER.getCode() + "/g,\"<b>Disease/ Disorder</b>\" );\n" +
+ " var fnd=dis.replace( /" + SemanticGroup.FINDING.getCode() + "/g,\"<b>Sign/ Symptom</b>\" );\n" +
+ " var prc=fnd.replace( /" + SemanticGroup.PROCEDURE.getCode() + "/g,\"<b>Procedure</b>\" );\n" +
+ " var drg=prc.replace( /" + SemanticGroup.MEDICATION.getCode() + "/g,\"<b>Medication</b>\" );\n" +
+ " var evt=drg.replace( /" + SemanticGroup.EVENT_CODE + "/g,\"<b>Event</b>\" );\n" +
+ " var tmx=evt.replace( /" + SemanticGroup.TIMEX_CODE + "/g,\"<b>Time</b>\" );\n" +
+ " var unk=tmx.replace( /" + SemanticGroup.UNKNOWN_SEMANTIC_CODE + "/g,\"<b>Unknown</b>\" );\n" +
+ " var spc=unk.replace( /" + SPACER + "/g,\" \" );\n" +
+ " var prf1=spc.replace( /\\[/g,\"<i>\" );\n" +
+ " var prf2=prf1.replace( /\\]/g,\"</i>\" );\n" +
+ " var nl=prf2.replace( /" + NEWLINE + "/g,\"<br>\" );\n" +
+ " document.getElementById(\"ia\").innerHTML = nl;\n" +
+ " }\n";
+ }
+
+ // Available decent search engines:
+ // https://en.wikipedia.org/wiki/ct_scan
+ // http://www.merckmanuals.com/home/SearchResults?query=ct+scan
+ // https://www.omim.org/search/?search=ct+scan
+ // https://medical-dictionary.thefreedictionary.com/ct+scan
+ // https://vsearch.nlm.nih.gov/vivisimo/cgi-bin/query-meta?v%3Aproject=medlineplus&v%3Asources=medlineplus-bundle&query=ct+scan
+ // https://www.medicinenet.com/script/main/srchcont.asp?src=ct+scan
+ // https://www.webmd.com/search/search_results/default.aspx?query=ct%20scan
+ // https://vsearch.nlm.nih.gov/vivisimo/cgi-bin/query-meta?query=ct+scan&v%3Aproject=nlm-main-website
+ // Make a list? https://www.w3schools.com/howto/howto_css_dropdown.asp
}
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/DefaultTextSpan.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/DefaultTextSpan.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/DefaultTextSpan.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/DefaultTextSpan.java Wed Nov 8 14:46:21 2017
@@ -67,6 +67,14 @@ public final class DefaultTextSpan imple
}
/**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean contains( TextSpan textSpan ) {
+ return _begin <= textSpan.getBegin() && textSpan.getEnd() <= _end;
+ }
+
+ /**
* @return a representation of the text span offsets as: begin,end
*/
@Override
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/TextSpan.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/TextSpan.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/TextSpan.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/textspan/TextSpan.java Wed Nov 8 14:46:21 2017
@@ -28,4 +28,10 @@ public interface TextSpan {
*/
boolean overlaps( TextSpan textSpan );
+ /**
+ * @param textSpan another text span
+ * @return true if this text span contains the other
+ */
+ boolean contains( TextSpan textSpan );
+
}
Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/property/plaintext/PropertyTextWriter.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/property/plaintext/PropertyTextWriter.java?rev=1814583&r1=1814582&r2=1814583&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/property/plaintext/PropertyTextWriter.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/property/plaintext/PropertyTextWriter.java Wed Nov 8 14:46:21 2017
@@ -157,14 +157,9 @@ public class PropertyTextWriter {
continue;
}
usedCuis.add( umlsConcept.getCui() );
- final String tui = umlsConcept.getTui();
- final String semanticName = SemanticGroup.getSemanticName( tui );
- Collection<UmlsConcept> concepts = semanticConcepts.get( semanticName );
- if ( concepts == null ) {
- concepts = new HashSet<>();
- semanticConcepts.put( semanticName, concepts );
- }
- concepts.add( umlsConcept );
+ final String semanticName = SemanticGroup.getSemanticName( identifiedAnnotation, umlsConcept );
+ semanticConcepts.putIfAbsent( semanticName, new HashSet<>() );
+ semanticConcepts.get( semanticName ).add( umlsConcept );
}
return semanticConcepts;
}