hanbinChen commited on
Commit
2981176
·
1 Parent(s): e81f16d
Files changed (4) hide show
  1. README.md +103 -26
  2. app.py +245 -61
  3. dev.json +0 -0
  4. mockData.json +0 -138
README.md CHANGED
@@ -11,38 +11,115 @@ pinned: false
11
 
12
  # Medical Knowledge Graph Construction (medKGC)
13
 
14
- ## English Version
 
15
 
16
- medKGC is a Streamlit-based application for medical text analysis and knowledge graph construction. It demonstrates the process of entity recognition and relation extraction from medical texts, visualizing the results in an interactive graph.
17
 
18
- ### Features:
19
- 1. Text Input: Users can input medical text for analysis.
20
- 2. Entity Recognition: The app identifies and highlights various medical entities such as diseases, procedures, anatomy, etc.
21
- 3. Relation Extraction: It extracts relationships between the identified entities.
22
- 4. Interactive Visualization: Utilizes streamlit-agraph to create an interactive graph representation of entities and their relationships.
23
- 5. Labeled Text Display: Shows the input text with highlighted entities using streamlit_text_label.
24
 
25
- ### How to Use:
26
- 1. Enter medical text in the provided text area.
27
- 2. Click "Recognize Entities" to process the text.
28
- 3. View the recognized entities, extracted relations, and the entity relationship graph.
 
 
 
29
 
30
- Note: Currently, the app uses mock data for demonstration purposes. Integration with actual NLP models is planned for future development.
 
 
 
31
 
32
- ## 中文版本
 
 
 
33
 
34
- medKGC 是一个基于 Streamlit 的医疗文本分析和知识图谱构建应用。它演示了从医疗文本中进行实体识别和关系提取的过程,并将结果以交互式图形可视化。
35
 
36
- ### 功能特点:
37
- 1. 文本输入:用户可以输入医疗文本进行分析。
38
- 2. 实体识别:应用程序识别并高亮显示各种医疗实体,如疾病、医疗程序、解剖结构等。
39
- 3. 关系提取:提取识别出的实体之间的关系。
40
- 4. 交互式可视化:使用 streamlit-agraph 创建实体及其关系的交互式图形表示。
41
- 5. 标记文本显示:使用 streamlit_text_label 显示带有高亮实体的输入文本。
 
 
 
 
 
 
 
 
 
42
 
43
- ### 使用方法:
44
- 1. 在提供的文本区域输入医疗文本。
45
- 2. 点击"识别实体"按钮处理文本。
46
- 3. 查看识别出的实体、提取的关系以及实体关系图。
 
 
 
 
 
47
 
48
- 注意:目前,应用程序使用模拟数据进行演示。未来开发计划将集成实际的自然语言处理模型。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # Medical Knowledge Graph Construction (medKGC)
13
 
14
+ ## Overview
15
+ medKGC is a medical text knowledge graph construction and review system. It supports entity recognition, relation extraction, and visualization of medical reports, providing a convenient review interface.
16
 
17
+ ## Core Features
18
 
19
+ ### 1. Data Processing
20
+ - **Position Conversion**: Support word-level and char-level position conversion
21
+ - **Entity Conversion**: Convert between JSON format and Selection objects
22
+ - **Relation Extraction**: Entity ID-based relation mapping and reconstruction
 
 
23
 
24
+ ### 2. Entity Annotation
25
+ - **Label Types**:
26
+ - OBS-DP: Observation definitely present (Red)
27
+ - ANAT-DP: Anatomy definitely present (Cyan)
28
+ - OBS-U: Observation uncertain (Yellow)
29
+ - OBS-DA: Observation definitely absent (Gray)
30
+ - **Interactive Annotation**: Support entity selection and annotation
31
 
32
+ ### 3. Relation Visualization
33
+ - **Node Merging**: Automatically merge entities with same text
34
+ - **Color Coding**: Different colors for different entity types
35
+ - **Dynamic Updates**: Support real-time graph updates
36
 
37
+ ### 4. Review Process
38
+ - **Report Selection**: Display pending and reviewed reports separately
39
+ - **Status Saving**: Automatically save review status and modifications
40
+ - **Batch Processing**: Support continuous review of multiple reports
41
 
42
+ ## Technical Implementation
43
 
44
+ ### Data Structures
45
+ 1. **Entity Data**
46
+ ```json
47
+ {
48
+ "entities": {
49
+ "1": {
50
+ "tokens": "entity text",
51
+ "label": "entity type",
52
+ "start_ix": "word-level start position",
53
+ "end_ix": "word-level end position",
54
+ "relations": [["relation type", "target entity ID"]]
55
+ }
56
+ }
57
+ }
58
+ ```
59
 
60
+ 2. **Selection Object**
61
+ ```python
62
+ @dataclass
63
+ class Selection:
64
+ start: int # char-level start position
65
+ end: int # char-level end position
66
+ text: str # entity text
67
+ labels: List[str] # entity type list
68
+ ```
69
 
70
+ ### Core Algorithms
71
+ 1. **Position Conversion**
72
+ ```python
73
+ def word_to_char_span(text, start_ix, end_ix):
74
+ """Convert word-level position to character-level range"""
75
+ ```
76
+
77
+ 2. **Relation Reconstruction**
78
+ ```python
79
+ def find_relations_with_entities(entities, entities_data):
80
+ """Rebuild relations based on entity text matching"""
81
+ ```
82
+
83
+ ## Deployment
84
+
85
+ ### Requirements
86
+ - Python 3.7+
87
+ - Streamlit 1.39.0+
88
+ - streamlit_text_label
89
+ - streamlit_agraph
90
+
91
+ ### Installation
92
+ 1. Clone repository
93
+ ```bash
94
+ git clone https://github.com/your-repo/medKGC.git
95
+ ```
96
+
97
+ 2. Install dependencies
98
+ ```bash
99
+ pip install -r requirements.txt
100
+ ```
101
+
102
+ 3. Run application
103
+ ```bash
104
+ streamlit run app.py
105
+ ```
106
+
107
+ ## Future Plans
108
+ 1. [ ] Add relation editing functionality
109
+ 2. [ ] Support custom entity types
110
+ 3. [ ] Add data export functionality
111
+ 4. [ ] Integrate machine learning models
112
+ 5. [ ] Add annotation functionality
113
+
114
+ ## Contributing
115
+ Welcome to contribute through:
116
+ 1. Submit Issues for bug reports or suggestions
117
+ 2. Submit Pull Requests to improve code
118
+ 3. Improve documentation and comments
119
+
120
+ ## License
121
+ MIT License
122
+
123
+ ---
124
+
125
+ [Chinese version above]
app.py CHANGED
@@ -1,91 +1,275 @@
1
  import streamlit as st
 
2
  from streamlit_text_label import label_select, Selection
3
  from streamlit_agraph import agraph, Node, Edge, Config
4
-
5
- # Import Relation class
6
  from dataclasses import dataclass
7
 
 
8
  @dataclass
9
  class Relation:
10
  source: Selection
11
  target: Selection
12
  label: str
13
 
14
- def mock_entity_recognition(text):
15
- # Simulate entity recognition functionality
16
- entities = [
17
- Selection(start=0, end=12, text="FINAL REPORT",
18
- labels=["REPORT_TYPE"]),
19
- Selection(start=22, end=33, text="Pseudomonas", labels=["DISEASE"]),
20
- Selection(start=39, end=49, text="intubation", labels=["PROCEDURE"]),
21
- Selection(start=62, end=67, text="study", labels=["EXAM"]),
22
- Selection(start=116, end=126, text="Substantial", labels=["SEVERITY"]),
23
- Selection(start=127, end=145, text="bilateral pleural",
24
- labels=["ANATOMY"]),
25
- Selection(start=146, end=155, text="effusions", labels=["OBSERVATION"])
26
- ]
27
- return entities
28
 
 
 
 
 
29
 
30
- def mock_relation_extraction(entities):
31
- # Simulate relation extraction functionality
32
- relations = [
33
- Relation(source=entities[0],
34
- target=entities[1], label="DISEASE_CAUSE"),
35
- Relation(source=entities[1], target=entities[2],
36
- label="PROCEDURE_EFFECT"),
37
- Relation(source=entities[2], target=entities[3], label="EXAM_RESULT"),
38
- Relation(source=entities[3], target=entities[4],
39
- label="SEVERITY_LEVEL"),
40
- Relation(source=entities[4], target=entities[5],
41
- label="ANATOMY_LOCATION"),
42
- ]
43
 
44
- return relations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
 
47
  def create_graph(entities, relations):
48
- nodes = [Node(id=e.text, label=e.text, size=25, color=f"#{hash(e.labels[0]) % 0xFFFFFF:06x}") for e in entities]
49
- edges = [Edge(source=r.source.text, target=r.target.text, label=r.label) for r in relations]
50
-
51
- config = Config(width=750, height=500, directed=True, physics=True, hierarchical=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  return agraph(nodes=nodes, edges=edges, config=config)
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def main():
56
- st.title("Medical Text Entity Recognition")
57
-
58
- # 1. Text input
59
- text = st.text_area("Enter medical text:", value="FINAL REPORT HISTORY : Pseudomonas with intubation . FINDINGS : In comparison with the study of ___ , there is little change in the monitoring and support devices . Substantial bilateral pleural effusions , more prominent on the right with bibasilar atelectasis .")
60
-
61
- if st.button("Recognize Entities"):
62
- # 2. Call the simulated entity recognition function
63
- entities = mock_entity_recognition(text)
64
-
65
- # 3. Use streamlit_text_label to highlight recognized entities
66
- st.subheader("Recognition Results:")
67
- labeled_text = label_select(
68
- body=text,
69
- labels=["REPORT_TYPE", "DISEASE", "PROCEDURE",
70
- "EXAM", "SEVERITY", "ANATOMY", "OBSERVATION"],
71
- selections=entities
 
 
 
 
 
72
  )
73
 
74
- st.write("Recognized entities:")
75
- for entity in entities:
76
- st.write(f"{entity.text} ({entity.labels[0]})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- # 4. Call the simulated relation extraction function
79
- relations = mock_relation_extraction(entities)
80
 
81
- # 5. Display relations
82
- st.subheader("Extracted Relations:")
83
- for relation in relations:
84
- st.write(f"{relation.source.text} --{relation.label}--> {relation.target.text}")
85
 
86
- # 6. Create and display graph using streamlit-agraph
 
 
 
 
 
 
 
 
 
 
87
  st.subheader("Entity Relationship Graph:")
88
- create_graph(entities, relations)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  if __name__ == "__main__":
91
  main()
 
1
  import streamlit as st
2
+ import json
3
  from streamlit_text_label import label_select, Selection
4
  from streamlit_agraph import agraph, Node, Edge, Config
 
 
5
  from dataclasses import dataclass
6
 
7
+
8
  @dataclass
9
  class Relation:
10
  source: Selection
11
  target: Selection
12
  label: str
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ def load_data():
16
+ """Load data from dev.json"""
17
+ with open('dev.json', 'r') as f:
18
+ return json.load(f)
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ def save_data(data):
22
+ """Save data to dev.json"""
23
+ with open('dev.json', 'w') as f:
24
+ json.dump(data, f, indent=4)
25
+
26
+
27
+ def get_label_color(label):
28
+ """Return color based on label type"""
29
+ color_map = {
30
+ 'OBS-DP': '#FF6B6B', # Red - Observation definitely present
31
+ 'ANAT-DP': '#4ECDC4', # Cyan - Anatomy definitely present
32
+ 'OBS-U': '#FFD93D', # Yellow - Observation uncertain
33
+ 'OBS-DA': '#95A5A6', # Gray - Observation definitely absent
34
+ }
35
+ return color_map.get(label, '#666666') # Default color
36
 
37
 
38
  def create_graph(entities, relations):
39
+ """Create entity relationship graph, merge nodes with same text"""
40
+ # Track created nodes using dict, key is entity text
41
+ nodes_dict = {}
42
+ nodes = []
43
+
44
+ # First create all unique nodes
45
+ for entity in entities:
46
+ if entity.text not in nodes_dict:
47
+ # Create new node
48
+ node = Node(
49
+ id=entity.text,
50
+ label=f"{entity.text}\n({entity.labels[0]})",
51
+ size=25,
52
+ color=get_label_color(entity.labels[0])
53
+ )
54
+ nodes.append(node)
55
+ nodes_dict[entity.text] = node
56
+
57
+ # Create edges using node text as source and target
58
+ edges = []
59
+ for relation in relations:
60
+ # Check if source and target exist
61
+ if relation.source.text in nodes_dict and relation.target.text in nodes_dict:
62
+ edge = Edge(
63
+ source=relation.source.text,
64
+ target=relation.target.text,
65
+ label=relation.label,
66
+ color="#666666" # Unified edge color
67
+ )
68
+ edges.append(edge)
69
+
70
+ config = Config(
71
+ width=750,
72
+ height=500,
73
+ directed=True,
74
+ physics=True,
75
+ hierarchical=False,
76
+ nodeHighlightBehavior=True,
77
+ highlightColor="#F7A7A6",
78
+ )
79
+
80
  return agraph(nodes=nodes, edges=edges, config=config)
81
 
82
 
83
+ def word_to_char_position(text, word_index):
84
+ """Convert word position to character position"""
85
+ words = text.split()
86
+ char_start = 0
87
+
88
+ # If word_index out of range, return text end
89
+ if word_index >= len(words):
90
+ return len(text)
91
+
92
+ # Traverse all words before target word
93
+ for i in range(word_index):
94
+ char_start += len(words[i]) + 1 # +1 for space
95
+
96
+ return char_start
97
+
98
+
99
+ def word_to_char_span(text, start_ix, end_ix):
100
+ """Convert word start and end positions to character span"""
101
+ char_start = word_to_char_position(text, start_ix)
102
+ # If start equals end, it's a single word
103
+ if start_ix == end_ix:
104
+ char_end = char_start + len(text.split()[start_ix])
105
+ else:
106
+ # If multiple words, calculate to end position
107
+ char_end = word_to_char_position(
108
+ text, end_ix) + len(text.split()[end_ix])
109
+ return char_start, char_end
110
+
111
+
112
+ def entities2Selection(text, entities_data):
113
+ """Convert entities data to Selection objects list"""
114
+ selections = []
115
+ for entity_id, entity in entities_data.items():
116
+ # Convert word positions to char positions
117
+ char_start, char_end = word_to_char_span(
118
+ text,
119
+ entity['start_ix'],
120
+ entity['end_ix']
121
+ )
122
+
123
+ selection = Selection(
124
+ start=char_start,
125
+ end=char_end,
126
+ text=entity['tokens'],
127
+ labels=[entity['label']],
128
+ )
129
+ selections.append(selection)
130
+ return selections
131
+
132
+
133
+ def selection2entities(selections):
134
+ """Convert Selection objects list to entities data"""
135
+ entities = {}
136
+ for i, selection in enumerate(selections, 1):
137
+ entities[str(i)] = {
138
+ "tokens": selection.text,
139
+ "label": selection.labels[0],
140
+ "start_ix": selection.start,
141
+ "end_ix": selection.end,
142
+ "relations": [] # Initialize empty relations list
143
+ }
144
+ return entities
145
+
146
+
147
+ def find_relations_with_entities(entities, entities_data):
148
+ """Find relations between current entities based on original entities_data"""
149
+ # Create text to entity mapping
150
+ text_to_entity = {e.text: e for e in entities}
151
+
152
+ # Create tokens to entity_id mapping
153
+ tokens_to_id = {entity['tokens']: entity_id
154
+ for entity_id, entity in entities_data.items()}
155
+
156
+ # Create id to tokens mapping
157
+ id_to_tokens = {entity_id: entity['tokens']
158
+ for entity_id, entity in entities_data.items()}
159
+
160
+ relations = []
161
+ # Iterate through each entity in current entities
162
+ for source_text, source_entity in text_to_entity.items():
163
+ # Find corresponding entity ID in original data
164
+ for entity_id, entity in entities_data.items():
165
+ if entity['tokens'] == source_text:
166
+ # Iterate through all relations of this entity
167
+ for relation in entity.get('relations', []):
168
+ target_id = relation[1]
169
+ # Get target entity text
170
+ target_text = id_to_tokens.get(target_id)
171
+ # If target entity exists in current entities
172
+ if target_text and target_text in text_to_entity:
173
+ relations.append(Relation(
174
+ source=source_entity,
175
+ target=text_to_entity[target_text],
176
+ label=relation[0]
177
+ ))
178
+
179
+ return relations
180
+
181
+
182
  def main():
183
+ """Main application"""
184
+ st.title("Medical Report Review System")
185
+
186
+ # Load data
187
+ if 'data' not in st.session_state:
188
+ st.session_state.data = load_data()
189
+
190
+ # Create two columns layout
191
+ col1, col2 = st.columns(2)
192
+
193
+ with col1:
194
+ st.subheader("Reports to Review")
195
+ # Get unreviewed reports list
196
+ unreviewed_reports = [
197
+ report_id for report_id, content in st.session_state.data.items()
198
+ if 'reviewed' not in content
199
+ ]
200
+ selected_report = st.selectbox(
201
+ "Select Report",
202
+ unreviewed_reports,
203
+ key="unreviewed"
204
  )
205
 
206
+ with col2:
207
+ st.subheader("Reviewed Reports")
208
+ # Get reviewed reports list
209
+ reviewed_reports = [
210
+ report_id for report_id, content in st.session_state.data.items()
211
+ if content.get('reviewed', False)
212
+ ]
213
+ st.selectbox(
214
+ "Completed Reports",
215
+ reviewed_reports if reviewed_reports else ['None'],
216
+ key="reviewed"
217
+ )
218
+
219
+ if selected_report:
220
+ report_data = st.session_state.data[selected_report]
221
+
222
+ # Display report text
223
+ st.subheader("Report Content:")
224
+ st.markdown(report_data['text'])
225
 
226
+ # Display entities and relations
227
+ entities_data = report_data['entities']
228
 
229
+ # Convert entities data to Selection objects
230
+ entities = entities2Selection(report_data['text'], entities_data)
 
 
231
 
232
+ # Display annotation results
233
+ st.subheader("Entity Annotation:")
234
+ selections = label_select(
235
+ body=report_data['text'],
236
+ labels=list(set(e.labels[0] for e in entities)),
237
+ selections=entities,
238
+ )
239
+ # Display selections in text format
240
+ st.write(selections)
241
+
242
+ # Display relationship graph
243
  st.subheader("Entity Relationship Graph:")
244
+
245
+ # Add update graph button
246
+ if st.button("Update Graph"):
247
+ # Update relations using current selections
248
+ relations = find_relations_with_entities(selections, entities_data)
249
+ create_graph(selections, relations)
250
+ else:
251
+ # Display original graph
252
+ relations = find_relations_with_entities(entities, entities_data)
253
+ create_graph(entities, relations)
254
+
255
+ # Add review functionality
256
+ if st.button("Mark as Reviewed"):
257
+ # Convert selections back to entities data
258
+ updated_entities = selection2entities(selections)
259
+
260
+ # Keep original relations
261
+ for entity_id, entity in updated_entities.items():
262
+ if entity_id in entities_data:
263
+ entity['relations'] = entities_data[entity_id]['relations']
264
+
265
+ st.session_state.data[selected_report]['reviewed'] = {
266
+ 'entities': updated_entities
267
+ }
268
+
269
+ save_data(st.session_state.data)
270
+ st.success("Review status saved!")
271
+ st.rerun()
272
+
273
 
274
  if __name__ == "__main__":
275
  main()
dev.json ADDED
The diff for this file is too large to render. See raw diff
 
mockData.json DELETED
@@ -1,138 +0,0 @@
1
- {'data_source': 'MIMIC-CXR',
2
- 'data_split': 'train',
3
- 'entities': {'1': {'end_ix': 29,
4
- 'label': 'OBS-DP',
5
- 'relations': [['modify', '4']],
6
- 'start_ix': 29,
7
- 'tokens': 'Substantial'},
8
- '10': {'end_ix': 44,
9
- 'label': 'OBS-DP',
10
- 'relations': [['suggestive_of', '16']],
11
- 'start_ix': 44,
12
- 'tokens': 'configuration'},
13
- '11': {'end_ix': 47,
14
- 'label': 'OBS-DP',
15
- 'relations': [['modify', '12']],
16
- 'start_ix': 47,
17
- 'tokens': 'collection'},
18
- '12': {'end_ix': 49,
19
- 'label': 'OBS-DP',
20
- 'relations': [['located_at', '14'],
21
- ['suggestive_of', '16']],
22
- 'start_ix': 49,
23
- 'tokens': 'opacification'},
24
- '13': {'end_ix': 52,
25
- 'label': 'ANAT-DP',
26
- 'relations': [['modify', '14']],
27
- 'start_ix': 52,
28
- 'tokens': 'left'},
29
- '14': {'end_ix': 53,
30
- 'label': 'ANAT-DP',
31
- 'relations': [],
32
- 'start_ix': 53,
33
- 'tokens': 'base'},
34
- '15': {'end_ix': 59,
35
- 'label': 'OBS-U',
36
- 'relations': [['modify', '16']],
37
- 'start_ix': 59,
38
- 'tokens': 'loculated'},
39
- '16': {'end_ix': 60,
40
- 'label': 'OBS-U',
41
- 'relations': [],
42
- 'start_ix': 60,
43
- 'tokens': 'fluid'},
44
- '17': {'end_ix': 67,
45
- 'label': 'OBS-DP',
46
- 'relations': [['modify', '20']],
47
- 'start_ix': 67,
48
- 'tokens': 'increased'},
49
- '18': {'end_ix': 68,
50
- 'label': 'ANAT-DP',
51
- 'relations': [['modify', '19']],
52
- 'start_ix': 68,
53
- 'tokens': 'pulmonary'},
54
- '19': {'end_ix': 69,
55
- 'label': 'ANAT-DP',
56
- 'relations': [],
57
- 'start_ix': 69,
58
- 'tokens': 'venous'},
59
- '2': {'end_ix': 30,
60
- 'label': 'ANAT-DP',
61
- 'relations': [['modify', '3']],
62
- 'start_ix': 30,
63
- 'tokens': 'bilateral'},
64
- '20': {'end_ix': 70,
65
- 'label': 'OBS-DP',
66
- 'relations': [['located_at', '19']],
67
- 'start_ix': 70,
68
- 'tokens': 'pressure'},
69
- '21': {'end_ix': 79,
70
- 'label': 'ANAT-DP',
71
- 'relations': [['modify', '23']],
72
- 'start_ix': 79,
73
- 'tokens': 'left'},
74
- '22': {'end_ix': 80,
75
- 'label': 'ANAT-DP',
76
- 'relations': [['modify', '23']],
77
- 'start_ix': 80,
78
- 'tokens': 'upper'},
79
- '23': {'end_ix': 81,
80
- 'label': 'ANAT-DP',
81
- 'relations': [],
82
- 'start_ix': 81,
83
- 'tokens': 'zone'},
84
- '24': {'end_ix': 87,
85
- 'label': 'ANAT-DP',
86
- 'relations': [],
87
- 'start_ix': 87,
88
- 'tokens': 'cavitary'},
89
- '25': {'end_ix': 88,
90
- 'label': 'OBS-DP',
91
- 'relations': [['located_at', '24']],
92
- 'start_ix': 88,
93
- 'tokens': 'process'},
94
- '3': {'end_ix': 31,
95
- 'label': 'ANAT-DP',
96
- 'relations': [],
97
- 'start_ix': 31,
98
- 'tokens': 'pleural'},
99
- '4': {'end_ix': 32,
100
- 'label': 'OBS-DP',
101
- 'relations': [['located_at', '3']],
102
- 'start_ix': 32,
103
- 'tokens': 'effusions'},
104
- '5': {'end_ix': 35,
105
- 'label': 'OBS-DP',
106
- 'relations': [['located_at', '6']],
107
- 'start_ix': 35,
108
- 'tokens': 'prominent'},
109
- '6': {'end_ix': 38,
110
- 'label': 'ANAT-DP',
111
- 'relations': [['modify', '3']],
112
- 'start_ix': 38,
113
- 'tokens': 'right'},
114
- '7': {'end_ix': 40,
115
- 'label': 'ANAT-DP',
116
- 'relations': [],
117
- 'start_ix': 40,
118
- 'tokens': 'bibasilar'},
119
- '8': {'end_ix': 41,
120
- 'label': 'OBS-DP',
121
- 'relations': [['located_at', '6'], ['located_at', '7']],
122
- 'start_ix': 41,
123
- 'tokens': 'atelectasis'},
124
- '9': {'end_ix': 43,
125
- 'label': 'OBS-DP',
126
- 'relations': [['modify', '10']],
127
- 'start_ix': 43,
128
- 'tokens': 'Unusual'}},
129
- 'text': 'FINAL REPORT HISTORY : Pseudomonas with intubation . FINDINGS : In '
130
- 'comparison with the study of ___ , there is little change in the '
131
- 'monitoring and support devices . Substantial bilateral pleural '
132
- 'effusions , more prominent on the right with bibasilar atelectasis . '
133
- 'Unusual configuration to the collection of opacification at the left '
134
- 'base raises the possibility of some loculated fluid . There is again '
135
- 'evidence of increased pulmonary venous pressure . Overlapping '
136
- 'structures somewhat obscure visualization of the left upper zone and '
137
- 'simulate the appearance of cavitary process . This area should be '
138
- 'closely checked on subsequent radiographs .'}