cxrfescore 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cxrfescore-0.2.2/.gitignore +23 -0
- cxrfescore-0.2.2/CITATION.cff +42 -0
- cxrfescore-0.2.2/LICENSE +21 -0
- cxrfescore-0.2.2/PKG-INFO +237 -0
- cxrfescore-0.2.2/README.md +184 -0
- cxrfescore-0.2.2/examples/colab_smoke.md +169 -0
- cxrfescore-0.2.2/examples/minimal_biovil_t.md +18 -0
- cxrfescore-0.2.2/examples/minimal_cxr_bert_specialized.md +18 -0
- cxrfescore-0.2.2/examples/minimal_cxrfe.md +15 -0
- cxrfescore-0.2.2/examples/minimal_extract_embed.md +24 -0
- cxrfescore-0.2.2/examples/minimal_srr_bert_leaves.md +20 -0
- cxrfescore-0.2.2/pyproject.toml +67 -0
- cxrfescore-0.2.2/src/cxrfescore/__init__.py +7 -0
- cxrfescore-0.2.2/src/cxrfescore/cache_utils.py +129 -0
- cxrfescore-0.2.2/src/cxrfescore/metric.py +864 -0
- cxrfescore-0.2.2/src/cxrfescore/nltk_setup.py +29 -0
- cxrfescore-0.2.2/src/cxrfescore/text_utils.py +71 -0
- cxrfescore-0.2.2/tests/__init__.py +0 -0
- cxrfescore-0.2.2/tests/test_cache_utils.py +55 -0
- cxrfescore-0.2.2/tests/test_metric_integration.py +47 -0
- cxrfescore-0.2.2/tests/test_metric_unit.py +232 -0
- cxrfescore-0.2.2/tests/test_text_utils.py +43 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
*.egg
|
|
15
|
+
|
|
16
|
+
# Editor / OS
|
|
17
|
+
.DS_Store
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
|
|
21
|
+
# Local caches / notebooks checkpoints
|
|
22
|
+
.ipynb_checkpoints/
|
|
23
|
+
*.pkl
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use CXRFEScore, please cite the following paper."
|
|
3
|
+
title: "CXRFEScore"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Messina
|
|
7
|
+
given-names: Pablo
|
|
8
|
+
- family-names: Vidal
|
|
9
|
+
given-names: Rene
|
|
10
|
+
- family-names: Parra
|
|
11
|
+
given-names: Denis
|
|
12
|
+
- family-names: Soto
|
|
13
|
+
given-names: Alvaro
|
|
14
|
+
- family-names: Araujo
|
|
15
|
+
given-names: Vladimir
|
|
16
|
+
preferred-citation:
|
|
17
|
+
type: conference-paper
|
|
18
|
+
title: "Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation"
|
|
19
|
+
authors:
|
|
20
|
+
- family-names: Messina
|
|
21
|
+
given-names: Pablo
|
|
22
|
+
- family-names: Vidal
|
|
23
|
+
given-names: Rene
|
|
24
|
+
- family-names: Parra
|
|
25
|
+
given-names: Denis
|
|
26
|
+
- family-names: Soto
|
|
27
|
+
given-names: Alvaro
|
|
28
|
+
- family-names: Araujo
|
|
29
|
+
given-names: Vladimir
|
|
30
|
+
collection-title: "Findings of the Association for Computational Linguistics: ACL 2024"
|
|
31
|
+
month: 8
|
|
32
|
+
year: 2024
|
|
33
|
+
publisher: "Association for Computational Linguistics"
|
|
34
|
+
address: "Bangkok, Thailand"
|
|
35
|
+
url: "https://aclanthology.org/2024.findings-acl.236/"
|
|
36
|
+
doi: "10.18653/v1/2024.findings-acl.236"
|
|
37
|
+
pages: "3955-3986"
|
|
38
|
+
identifiers:
|
|
39
|
+
- type: doi
|
|
40
|
+
value: "10.18653/v1/2024.findings-acl.236"
|
|
41
|
+
repository-code: "https://github.com/PabloMessina/CXRFEScore"
|
|
42
|
+
license: MIT
|
cxrfescore-0.2.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Pablo Messina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cxrfescore
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Fact-level embedding metric for evaluating chest X-ray radiology report generation.
|
|
5
|
+
Project-URL: Homepage, https://github.com/PabloMessina/CXRFEScore
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/PabloMessina/CXRFEScore/issues
|
|
7
|
+
Project-URL: Paper, https://aclanthology.org/2024.findings-acl.236/
|
|
8
|
+
Author-email: Pablo Messina <pablo.messina.g@gmail.com>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2024 Pablo Messina
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Classifier: Intended Audience :: Science/Research
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
37
|
+
Requires-Python: >=3.10
|
|
38
|
+
Requires-Dist: nltk>=3.8.0
|
|
39
|
+
Requires-Dist: numpy>=1.24.0
|
|
40
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
41
|
+
Requires-Dist: scikit-learn>=1.3.0
|
|
42
|
+
Requires-Dist: torch>=2.0.0
|
|
43
|
+
Requires-Dist: tqdm>=4.65.0
|
|
44
|
+
Requires-Dist: transformers>=4.40.0
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: matplotlib>=3.7.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
50
|
+
Provides-Extra: viz
|
|
51
|
+
Requires-Dist: matplotlib>=3.7.0; extra == 'viz'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# CXRFEScore
|
|
55
|
+
|
|
56
|
+
Fact-level embedding metric for evaluating chest X-ray radiology report generation.
|
|
57
|
+
|
|
58
|
+
CXRFEScore extracts factual statements from reports with a T5 fact extractor, embeds them with CXRFE (or compatible CXR-BERT models), and scores hypothesis/reference pairs via soft bipartite matching of fact embeddings.
|
|
59
|
+
|
|
60
|
+
Based on:
|
|
61
|
+
|
|
62
|
+
> Messina et al., *Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation*, Findings of ACL 2024.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install cxrfescore
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Optional visualization support:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install cxrfescore[viz]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Python:** 3.10+
|
|
77
|
+
|
|
78
|
+
### Runtime dependencies (installed automatically)
|
|
79
|
+
|
|
80
|
+
| Package | Role |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `torch` | Model inference |
|
|
83
|
+
| `transformers` | Load Hugging Face models |
|
|
84
|
+
| `numpy` | Arrays / aggregation |
|
|
85
|
+
| `scikit-learn` | Cosine similarity |
|
|
86
|
+
| `nltk` | Sentence splitting |
|
|
87
|
+
| `tqdm` | Progress bars |
|
|
88
|
+
| `platformdirs` | Default cache directory |
|
|
89
|
+
|
|
90
|
+
Optional: `matplotlib` via `cxrfescore[viz]` for `visualize_fact_similarity`.
|
|
91
|
+
|
|
92
|
+
### Additional requirements (not pip packages)
|
|
93
|
+
|
|
94
|
+
On first use the metric downloads:
|
|
95
|
+
|
|
96
|
+
- **Encoder (default):** [`pamessina/CXRFE`](https://huggingface.co/pamessina/CXRFE)
|
|
97
|
+
- **Fact extractor:** [`pamessina/T5FactExtractor`](https://huggingface.co/pamessina/T5FactExtractor)
|
|
98
|
+
- **NLTK** `punkt` / `punkt_tab` (auto-downloaded)
|
|
99
|
+
|
|
100
|
+
Supported alternate encoders:
|
|
101
|
+
|
|
102
|
+
- `microsoft/BiomedVLP-CXR-BERT-specialized`
|
|
103
|
+
- `microsoft/BiomedVLP-BioViL-T`
|
|
104
|
+
- `StanfordAIMI/SRR-BERT-Leaves` (CLS embeddings only; label head unused)
|
|
105
|
+
|
|
106
|
+
A GPU is recommended but not required. CPU works and is slower.
|
|
107
|
+
|
|
108
|
+
Known-good stack from development: PyTorch 2.x + `transformers` 4.x / 5.x.
|
|
109
|
+
|
|
110
|
+
## Quick start
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from cxrfescore import CXRFEScore
|
|
114
|
+
|
|
115
|
+
metric = CXRFEScore(device="cuda") # or "cpu"
|
|
116
|
+
|
|
117
|
+
hyps = [
|
|
118
|
+
"There is a small right pleural effusion. The heart size is normal.",
|
|
119
|
+
]
|
|
120
|
+
refs = [
|
|
121
|
+
"Small right pleural effusion. Normal heart size.",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
result = metric(hyps, refs)
|
|
125
|
+
print(result["mean_similarity"])
|
|
126
|
+
print(result["per_pair_similarity"])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Extract facts or embeddings only
|
|
130
|
+
|
|
131
|
+
`extract_facts` treats each input string as a **full report**: sentence-split → T5 fact extraction → unique-fact aggregation (same pipeline as scoring).
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
facts_per_report = metric.extract_facts(hyps)
|
|
135
|
+
embeddings = metric.embed_facts(facts_per_report[0]) # shape (num_facts, dim)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Caching
|
|
139
|
+
|
|
140
|
+
With `use_cache=True` (default), results are kept **in memory** during the session:
|
|
141
|
+
|
|
142
|
+
- Sentence→facts: `{cache_dir}/sent_to_facts.pkl` (shared across encoders)
|
|
143
|
+
- Fact→embedding: `{cache_dir}/embeddings/<encoder_name>/fact_to_embedding.pkl` (per encoder)
|
|
144
|
+
|
|
145
|
+
Disk writes are **not** automatic. Call `save_cache()` explicitly after a large batch or at the end of a job:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
metric = CXRFEScore(use_cache=True, cache_dir="/path/to/cache")
|
|
149
|
+
# ... score / extract / embed ...
|
|
150
|
+
metric.save_cache()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Disable with `use_cache=False`.
|
|
154
|
+
|
|
155
|
+
Inspect what is already on disk:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from cxrfescore import inspect_cache
|
|
159
|
+
|
|
160
|
+
inspect_cache() # default cache location
|
|
161
|
+
# or: metric.inspect_cache() # uses this instance's cache_dir
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Visualization
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# requires: pip install cxrfescore[viz]
|
|
168
|
+
metric.visualize_fact_similarity(ref_report=refs[0], cand_report=hyps[0])
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
| File | Purpose |
|
|
174
|
+
|---|---|
|
|
175
|
+
| [`examples/colab_smoke.md`](examples/colab_smoke.md) | Full Colab smoke + adversarial pairs |
|
|
176
|
+
| [`examples/minimal_cxrfe.md`](examples/minimal_cxrfe.md) | Default CXRFE encoder |
|
|
177
|
+
| [`examples/minimal_cxr_bert_specialized.md`](examples/minimal_cxr_bert_specialized.md) | CXR-BERT specialized |
|
|
178
|
+
| [`examples/minimal_biovil_t.md`](examples/minimal_biovil_t.md) | BioViL-T |
|
|
179
|
+
| [`examples/minimal_srr_bert_leaves.md`](examples/minimal_srr_bert_leaves.md) | SRR-BERT-Leaves CLS |
|
|
180
|
+
| [`examples/minimal_extract_embed.md`](examples/minimal_extract_embed.md) | `extract_facts` / `embed_facts` |
|
|
181
|
+
|
|
182
|
+
## Try it on Google Colab
|
|
183
|
+
|
|
184
|
+
After publishing, install in Colab (GPU runtime recommended):
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
!pip install cxrfescore
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
For a pre-release check against TestPyPI:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
!pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "cxrfescore==0.2.2"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Citation
|
|
197
|
+
|
|
198
|
+
If you use CXRFEScore, please cite:
|
|
199
|
+
|
|
200
|
+
> Pablo Messina, Rene Vidal, Denis Parra, Alvaro Soto, and Vladimir Araujo. 2024. [Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation](https://aclanthology.org/2024.findings-acl.236/). In *Findings of the Association for Computational Linguistics: ACL 2024*, pages 3955–3986, Bangkok, Thailand. Association for Computational Linguistics.
|
|
201
|
+
|
|
202
|
+
```bibtex
|
|
203
|
+
@inproceedings{messina-etal-2024-extracting,
|
|
204
|
+
title = "Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation",
|
|
205
|
+
author = "Messina, Pablo and
|
|
206
|
+
Vidal, Rene and
|
|
207
|
+
Parra, Denis and
|
|
208
|
+
Soto, Alvaro and
|
|
209
|
+
Araujo, Vladimir",
|
|
210
|
+
editor = "Ku, Lun-Wei and
|
|
211
|
+
Martins, Andre and
|
|
212
|
+
Srikumar, Vivek",
|
|
213
|
+
booktitle = "Findings of the Association for Computational Linguistics: ACL 2024",
|
|
214
|
+
month = aug,
|
|
215
|
+
year = "2024",
|
|
216
|
+
address = "Bangkok, Thailand",
|
|
217
|
+
publisher = "Association for Computational Linguistics",
|
|
218
|
+
url = "https://aclanthology.org/2024.findings-acl.236/",
|
|
219
|
+
doi = "10.18653/v1/2024.findings-acl.236",
|
|
220
|
+
pages = "3955--3986"
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Paper: [ACL Anthology](https://aclanthology.org/2024.findings-acl.236/) · [PDF](https://aclanthology.org/2024.findings-acl.236.pdf) · [arXiv](https://arxiv.org/abs/2407.01948)
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
pip install -e ".[dev]"
|
|
230
|
+
pytest # unit + mocked tests (default)
|
|
231
|
+
pytest -m integration # downloads HF models; needs network / GPU optional
|
|
232
|
+
python -m build
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# CXRFEScore
|
|
2
|
+
|
|
3
|
+
Fact-level embedding metric for evaluating chest X-ray radiology report generation.
|
|
4
|
+
|
|
5
|
+
CXRFEScore extracts factual statements from reports with a T5 fact extractor, embeds them with CXRFE (or compatible CXR-BERT models), and scores hypothesis/reference pairs via soft bipartite matching of fact embeddings.
|
|
6
|
+
|
|
7
|
+
Based on:
|
|
8
|
+
|
|
9
|
+
> Messina et al., *Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation*, Findings of ACL 2024.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install cxrfescore
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Optional visualization support:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install cxrfescore[viz]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Python:** 3.10+
|
|
24
|
+
|
|
25
|
+
### Runtime dependencies (installed automatically)
|
|
26
|
+
|
|
27
|
+
| Package | Role |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `torch` | Model inference |
|
|
30
|
+
| `transformers` | Load Hugging Face models |
|
|
31
|
+
| `numpy` | Arrays / aggregation |
|
|
32
|
+
| `scikit-learn` | Cosine similarity |
|
|
33
|
+
| `nltk` | Sentence splitting |
|
|
34
|
+
| `tqdm` | Progress bars |
|
|
35
|
+
| `platformdirs` | Default cache directory |
|
|
36
|
+
|
|
37
|
+
Optional: `matplotlib` via `cxrfescore[viz]` for `visualize_fact_similarity`.
|
|
38
|
+
|
|
39
|
+
### Additional requirements (not pip packages)
|
|
40
|
+
|
|
41
|
+
On first use the metric downloads:
|
|
42
|
+
|
|
43
|
+
- **Encoder (default):** [`pamessina/CXRFE`](https://huggingface.co/pamessina/CXRFE)
|
|
44
|
+
- **Fact extractor:** [`pamessina/T5FactExtractor`](https://huggingface.co/pamessina/T5FactExtractor)
|
|
45
|
+
- **NLTK** `punkt` / `punkt_tab` (auto-downloaded)
|
|
46
|
+
|
|
47
|
+
Supported alternate encoders:
|
|
48
|
+
|
|
49
|
+
- `microsoft/BiomedVLP-CXR-BERT-specialized`
|
|
50
|
+
- `microsoft/BiomedVLP-BioViL-T`
|
|
51
|
+
- `StanfordAIMI/SRR-BERT-Leaves` (CLS embeddings only; label head unused)
|
|
52
|
+
|
|
53
|
+
A GPU is recommended but not required. CPU works and is slower.
|
|
54
|
+
|
|
55
|
+
Known-good stack from development: PyTorch 2.x + `transformers` 4.x / 5.x.
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from cxrfescore import CXRFEScore
|
|
61
|
+
|
|
62
|
+
metric = CXRFEScore(device="cuda") # or "cpu"
|
|
63
|
+
|
|
64
|
+
hyps = [
|
|
65
|
+
"There is a small right pleural effusion. The heart size is normal.",
|
|
66
|
+
]
|
|
67
|
+
refs = [
|
|
68
|
+
"Small right pleural effusion. Normal heart size.",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
result = metric(hyps, refs)
|
|
72
|
+
print(result["mean_similarity"])
|
|
73
|
+
print(result["per_pair_similarity"])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Extract facts or embeddings only
|
|
77
|
+
|
|
78
|
+
`extract_facts` treats each input string as a **full report**: sentence-split → T5 fact extraction → unique-fact aggregation (same pipeline as scoring).
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
facts_per_report = metric.extract_facts(hyps)
|
|
82
|
+
embeddings = metric.embed_facts(facts_per_report[0]) # shape (num_facts, dim)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Caching
|
|
86
|
+
|
|
87
|
+
With `use_cache=True` (default), results are kept **in memory** during the session:
|
|
88
|
+
|
|
89
|
+
- Sentence→facts: `{cache_dir}/sent_to_facts.pkl` (shared across encoders)
|
|
90
|
+
- Fact→embedding: `{cache_dir}/embeddings/<encoder_name>/fact_to_embedding.pkl` (per encoder)
|
|
91
|
+
|
|
92
|
+
Disk writes are **not** automatic. Call `save_cache()` explicitly after a large batch or at the end of a job:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
metric = CXRFEScore(use_cache=True, cache_dir="/path/to/cache")
|
|
96
|
+
# ... score / extract / embed ...
|
|
97
|
+
metric.save_cache()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Disable with `use_cache=False`.
|
|
101
|
+
|
|
102
|
+
Inspect what is already on disk:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from cxrfescore import inspect_cache
|
|
106
|
+
|
|
107
|
+
inspect_cache() # default cache location
|
|
108
|
+
# or: metric.inspect_cache() # uses this instance's cache_dir
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Visualization
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# requires: pip install cxrfescore[viz]
|
|
115
|
+
metric.visualize_fact_similarity(ref_report=refs[0], cand_report=hyps[0])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Examples
|
|
119
|
+
|
|
120
|
+
| File | Purpose |
|
|
121
|
+
|---|---|
|
|
122
|
+
| [`examples/colab_smoke.md`](examples/colab_smoke.md) | Full Colab smoke + adversarial pairs |
|
|
123
|
+
| [`examples/minimal_cxrfe.md`](examples/minimal_cxrfe.md) | Default CXRFE encoder |
|
|
124
|
+
| [`examples/minimal_cxr_bert_specialized.md`](examples/minimal_cxr_bert_specialized.md) | CXR-BERT specialized |
|
|
125
|
+
| [`examples/minimal_biovil_t.md`](examples/minimal_biovil_t.md) | BioViL-T |
|
|
126
|
+
| [`examples/minimal_srr_bert_leaves.md`](examples/minimal_srr_bert_leaves.md) | SRR-BERT-Leaves CLS |
|
|
127
|
+
| [`examples/minimal_extract_embed.md`](examples/minimal_extract_embed.md) | `extract_facts` / `embed_facts` |
|
|
128
|
+
|
|
129
|
+
## Try it on Google Colab
|
|
130
|
+
|
|
131
|
+
After publishing, install in Colab (GPU runtime recommended):
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
!pip install cxrfescore
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For a pre-release check against TestPyPI:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
!pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "cxrfescore==0.2.2"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Citation
|
|
144
|
+
|
|
145
|
+
If you use CXRFEScore, please cite:
|
|
146
|
+
|
|
147
|
+
> Pablo Messina, Rene Vidal, Denis Parra, Alvaro Soto, and Vladimir Araujo. 2024. [Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation](https://aclanthology.org/2024.findings-acl.236/). In *Findings of the Association for Computational Linguistics: ACL 2024*, pages 3955–3986, Bangkok, Thailand. Association for Computational Linguistics.
|
|
148
|
+
|
|
149
|
+
```bibtex
|
|
150
|
+
@inproceedings{messina-etal-2024-extracting,
|
|
151
|
+
title = "Extracting and Encoding: Leveraging Large Language Models and Medical Knowledge to Enhance Radiological Text Representation",
|
|
152
|
+
author = "Messina, Pablo and
|
|
153
|
+
Vidal, Rene and
|
|
154
|
+
Parra, Denis and
|
|
155
|
+
Soto, Alvaro and
|
|
156
|
+
Araujo, Vladimir",
|
|
157
|
+
editor = "Ku, Lun-Wei and
|
|
158
|
+
Martins, Andre and
|
|
159
|
+
Srikumar, Vivek",
|
|
160
|
+
booktitle = "Findings of the Association for Computational Linguistics: ACL 2024",
|
|
161
|
+
month = aug,
|
|
162
|
+
year = "2024",
|
|
163
|
+
address = "Bangkok, Thailand",
|
|
164
|
+
publisher = "Association for Computational Linguistics",
|
|
165
|
+
url = "https://aclanthology.org/2024.findings-acl.236/",
|
|
166
|
+
doi = "10.18653/v1/2024.findings-acl.236",
|
|
167
|
+
pages = "3955--3986"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Paper: [ACL Anthology](https://aclanthology.org/2024.findings-acl.236/) · [PDF](https://aclanthology.org/2024.findings-acl.236.pdf) · [arXiv](https://arxiv.org/abs/2407.01948)
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install -e ".[dev]"
|
|
177
|
+
pytest # unit + mocked tests (default)
|
|
178
|
+
pytest -m integration # downloads HF models; needs network / GPU optional
|
|
179
|
+
python -m build
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Colab smoke test (human)
|
|
2
|
+
|
|
3
|
+
Run this **after** `cxrfescore` is uploaded to TestPyPI and **before** production PyPI.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Open [Google Colab](https://colab.research.google.com/).
|
|
8
|
+
2. Runtime → Change runtime type → **GPU** (T4 is enough).
|
|
9
|
+
3. Run the cells below in order.
|
|
10
|
+
|
|
11
|
+
## Install from TestPyPI
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
!pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "cxrfescore==0.2.2"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After the production release, use:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
!pip install cxrfescore
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Smoke test
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import torch
|
|
27
|
+
from cxrfescore import CXRFEScore
|
|
28
|
+
|
|
29
|
+
print("torch:", torch.__version__)
|
|
30
|
+
print("cuda available:", torch.cuda.is_available())
|
|
31
|
+
|
|
32
|
+
metric = CXRFEScore(
|
|
33
|
+
device="cuda" if torch.cuda.is_available() else "cpu",
|
|
34
|
+
batch_size=8,
|
|
35
|
+
num_workers=0,
|
|
36
|
+
verbose=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
hyps = [
|
|
40
|
+
"There is a small right pleural effusion. The heart size is normal.",
|
|
41
|
+
"Large left pneumothorax with mediastinal shift.",
|
|
42
|
+
]
|
|
43
|
+
refs = [
|
|
44
|
+
"Small right pleural effusion. Normal heart size.",
|
|
45
|
+
"No acute cardiopulmonary process. Lungs are clear.",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
result = metric(hyps, refs)
|
|
49
|
+
print("mean_similarity:", result["mean_similarity"])
|
|
50
|
+
print("per_pair_similarity:", result["per_pair_similarity"])
|
|
51
|
+
|
|
52
|
+
# Sanity checks
|
|
53
|
+
assert result["per_pair_similarity"].shape == (2,)
|
|
54
|
+
assert result["per_pair_similarity"][0] > result["per_pair_similarity"][1]
|
|
55
|
+
print("Colab smoke test PASSED")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Optional visualization (easy pair)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
!pip install "cxrfescore[viz]"
|
|
62
|
+
metric.visualize_fact_similarity(ref_report=refs[0], cand_report=hyps[0])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Challenging / adversarial pairs
|
|
66
|
+
|
|
67
|
+
These cases are designed to stress clinical nuances that embedding metrics often mishandle
|
|
68
|
+
(or handle well). For each pair, inspect the printed score **and** the fact-similarity heatmap.
|
|
69
|
+
A good metric should score clinically wrong pairs lower than near-paraphrases.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
adversarial_cases = [
|
|
73
|
+
{
|
|
74
|
+
"name": "Negation flip (should score LOW)",
|
|
75
|
+
"note": "Nearly identical wording, opposite clinical meaning.",
|
|
76
|
+
"ref": "There is no evidence of pneumothorax.",
|
|
77
|
+
"hyp": "There is evidence of pneumothorax.",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Laterality swap (should score LOW)",
|
|
81
|
+
"note": "Same finding, wrong side — clinically important error.",
|
|
82
|
+
"ref": "Moderate right pleural effusion.",
|
|
83
|
+
"hyp": "Moderate left pleural effusion.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "Severity / size swap (should score MID-LOW)",
|
|
87
|
+
"note": "Correct finding/side, wrong magnitude.",
|
|
88
|
+
"ref": "Small right pleural effusion.",
|
|
89
|
+
"hyp": "Large right pleural effusion.",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "Synonym paraphrase (should score HIGH)",
|
|
93
|
+
"note": "Different wording, same meaning — robustness check.",
|
|
94
|
+
"ref": "The heart is enlarged. There is pulmonary edema.",
|
|
95
|
+
"hyp": "Cardiomegaly is present. Findings consistent with pulmonary oedema.",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Presence vs absence of shared anatomy (should score LOW)",
|
|
99
|
+
"note": "Shares anatomy tokens but asserts opposite finding.",
|
|
100
|
+
"ref": "The lungs are clear without focal consolidation.",
|
|
101
|
+
"hyp": "There is focal consolidation in the lungs.",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "Device / support device mismatch (should score LOW)",
|
|
105
|
+
"note": "Support-device facts often confuse overlap-based metrics.",
|
|
106
|
+
"ref": "Endotracheal tube tip is appropriately positioned above the carina.",
|
|
107
|
+
"hyp": "Nasogastric tube tip is appropriately positioned in the stomach.",
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Score all adversarial pairs in one batch
|
|
114
|
+
adv_hyps = [c["hyp"] for c in adversarial_cases]
|
|
115
|
+
adv_refs = [c["ref"] for c in adversarial_cases]
|
|
116
|
+
adv_result = metric(adv_hyps, adv_refs)
|
|
117
|
+
|
|
118
|
+
print("=== Adversarial pair scores ===")
|
|
119
|
+
for case, score in zip(adversarial_cases, adv_result["per_pair_similarity"]):
|
|
120
|
+
print(f"\n[{case['name']}] score={float(score):.4f}")
|
|
121
|
+
print(f" note: {case['note']}")
|
|
122
|
+
print(f" REF: {case['ref']}")
|
|
123
|
+
print(f" HYP: {case['hyp']}")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Visualize each challenging pair (one heatmap per case)
|
|
128
|
+
for case in adversarial_cases:
|
|
129
|
+
print("\n" + "=" * 72)
|
|
130
|
+
print(case["name"])
|
|
131
|
+
print(case["note"])
|
|
132
|
+
print("=" * 72)
|
|
133
|
+
metric.visualize_fact_similarity(
|
|
134
|
+
ref_report=case["ref"],
|
|
135
|
+
cand_report=case["hyp"],
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# Quick relative sanity check (not a hard unit test — inspect failures by eye)
|
|
141
|
+
scores = {c["name"]: float(s) for c, s in zip(adversarial_cases, adv_result["per_pair_similarity"])}
|
|
142
|
+
synonym = scores["Synonym paraphrase (should score HIGH)"]
|
|
143
|
+
negation = scores["Negation flip (should score LOW)"]
|
|
144
|
+
laterality = scores["Laterality swap (should score LOW)"]
|
|
145
|
+
|
|
146
|
+
print("synonym:", synonym)
|
|
147
|
+
print("negation:", negation)
|
|
148
|
+
print("laterality:", laterality)
|
|
149
|
+
|
|
150
|
+
if synonym <= negation:
|
|
151
|
+
print("WARNING: synonym paraphrase did not outscore negation flip — inspect heatmaps.")
|
|
152
|
+
if synonym <= laterality:
|
|
153
|
+
print("WARNING: synonym paraphrase did not outscore laterality swap — inspect heatmaps.")
|
|
154
|
+
if synonym > negation and synonym > laterality:
|
|
155
|
+
print("Relative ordering looks reasonable (synonym > negation/laterality).")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Checklist
|
|
159
|
+
|
|
160
|
+
- [ ] Install from TestPyPI succeeds
|
|
161
|
+
- [ ] Models download from Hugging Face
|
|
162
|
+
- [ ] CUDA used when GPU runtime is selected
|
|
163
|
+
- [ ] Similar pair scores higher than unrelated pair
|
|
164
|
+
- [ ] Adversarial pairs inspected (negation, laterality, severity, synonyms, devices)
|
|
165
|
+
- [ ] No import / runtime errors
|
|
166
|
+
|
|
167
|
+
## Citation
|
|
168
|
+
|
|
169
|
+
If you use CXRFEScore, please cite [Messina et al., Findings of ACL 2024](https://aclanthology.org/2024.findings-acl.236/). Full BibTeX is in the [README](../README.md#citation).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Minimal example — BioViL-T
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
!pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "cxrfescore==0.2.2"
|
|
5
|
+
|
|
6
|
+
from cxrfescore import CXRFEScore
|
|
7
|
+
|
|
8
|
+
metric = CXRFEScore(
|
|
9
|
+
encoder_model_name="microsoft/BiomedVLP-BioViL-T",
|
|
10
|
+
device="cuda",
|
|
11
|
+
)
|
|
12
|
+
result = metric(
|
|
13
|
+
["There is a small right pleural effusion."],
|
|
14
|
+
["Small right pleural effusion."],
|
|
15
|
+
)
|
|
16
|
+
print(result["mean_similarity"])
|
|
17
|
+
metric.save_cache()
|
|
18
|
+
```
|