Fix loader: correct CADAD signature, bundle sentence-segmentation collate + concept vocab, decode_greedy path verified end-to-end
Browse files- chest2err.py +85 -90
- chest2err_collate.py +105 -0
- concept2id.json +387 -0
chest2err.py
CHANGED
|
@@ -2,35 +2,38 @@
|
|
| 2 |
|
| 3 |
Usage:
|
| 4 |
from chest2err import chest2err_score, chest2err_detail
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
config, so no extra weights are downloaded at inference time. The backbone class
|
| 11 |
-
itself is loaded from the `transformers` package.
|
| 12 |
"""
|
| 13 |
from __future__ import annotations
|
| 14 |
|
| 15 |
import json
|
| 16 |
-
import os
|
| 17 |
-
import re
|
| 18 |
import math
|
|
|
|
| 19 |
from pathlib import Path
|
| 20 |
-
from typing import Any, Dict, List, Optional
|
| 21 |
|
| 22 |
import torch
|
| 23 |
-
import torch.nn.functional as F
|
| 24 |
from transformers import AutoModel, AutoTokenizer
|
| 25 |
from safetensors.torch import load_file
|
| 26 |
|
| 27 |
-
#
|
| 28 |
from chest2err_modeling import CADAD
|
| 29 |
-
|
| 30 |
-
# ---------------------------------------------------------------------------
|
| 31 |
|
| 32 |
PACKAGE_DIR = Path(__file__).resolve().parent
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
def _load_config() -> Dict[str, Any]:
|
| 36 |
with open(PACKAGE_DIR / "chest2err_config.json") as f:
|
|
@@ -40,114 +43,106 @@ def _load_config() -> Dict[str, Any]:
|
|
| 40 |
class Chest2Err:
|
| 41 |
"""Loads the merged backbone + decoder once, then scores pairs."""
|
| 42 |
|
| 43 |
-
def __init__(self,
|
|
|
|
|
|
|
| 44 |
cfg = _load_config()
|
| 45 |
self.cfg = cfg
|
| 46 |
self.device = device
|
| 47 |
self.max_length = cfg["max_length"]
|
| 48 |
-
self.template = cfg["input_template"]
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
self.
|
| 54 |
-
|
| 55 |
-
torch_dtype=torch.bfloat16,
|
| 56 |
-
).to(device).eval()
|
| 57 |
|
| 58 |
-
#
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
n_anat=cfg["n_anat"],
|
| 65 |
-
|
|
|
|
| 66 |
decoder_layers=cfg["decoder_layers"],
|
| 67 |
decoder_heads=cfg["decoder_heads"],
|
| 68 |
decoder_ff=cfg["decoder_ff"],
|
| 69 |
-
|
| 70 |
max_decode_steps=cfg["max_decode_steps"],
|
| 71 |
)
|
| 72 |
-
self.decoder.load_state_dict(decoder_state, strict=False)
|
| 73 |
-
self.decoder = self.decoder.to(device).to(torch.bfloat16).eval()
|
| 74 |
-
|
| 75 |
-
# ----------------------- input prep ------------------------- #
|
| 76 |
-
|
| 77 |
-
@staticmethod
|
| 78 |
-
def _split_sentences(text: str) -> List[str]:
|
| 79 |
-
"""Light sentence splitter. Section headers and bullet lines count as boundaries too."""
|
| 80 |
-
# Split on . ! ? and section headers like [Lungs] or "Lungs:"
|
| 81 |
-
chunks = re.split(r"(?<=[.!?])\s+|\n+", text or "")
|
| 82 |
-
sents = [c.strip().lstrip("- ").strip() for c in chunks]
|
| 83 |
-
return [s for s in sents if s]
|
| 84 |
-
|
| 85 |
-
def _encode_pair(self, ref: str, cand: str) -> Dict[str, torch.Tensor]:
|
| 86 |
-
ref_sents = self._split_sentences(ref)
|
| 87 |
-
cand_sents = self._split_sentences(cand)
|
| 88 |
-
text = self.template.format(reference_report=ref, candidate_report=cand)
|
| 89 |
-
enc = self.tokenizer(
|
| 90 |
-
text,
|
| 91 |
-
max_length=self.max_length,
|
| 92 |
-
truncation=True,
|
| 93 |
-
padding=False,
|
| 94 |
-
return_tensors="pt",
|
| 95 |
-
add_special_tokens=False,
|
| 96 |
-
)
|
| 97 |
-
# NB: a production-grade encoder also produces seg_token_mask aligning each
|
| 98 |
-
# sentence to its token span. The CADAD decoder consumes per-sentence
|
| 99 |
-
# mean-pooled vectors; this helper exposes the API surface.
|
| 100 |
-
return {
|
| 101 |
-
"input_ids": enc["input_ids"].to(self.device),
|
| 102 |
-
"attention_mask": enc["attention_mask"].to(self.device),
|
| 103 |
-
"ref_sentences": ref_sents,
|
| 104 |
-
"cand_sentences": cand_sents,
|
| 105 |
-
}
|
| 106 |
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
@torch.inference_mode()
|
| 110 |
def score(self, ref: str, cand: str) -> float:
|
| 111 |
-
|
| 112 |
-
detail = self.detail(ref, cand)
|
| 113 |
-
return detail["score"]
|
| 114 |
|
| 115 |
@torch.inference_mode()
|
| 116 |
def detail(self, ref: str, cand: str) -> Dict[str, Any]:
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
out = self.backbone(
|
| 120 |
-
input_ids=enc["input_ids"],
|
| 121 |
-
attention_mask=enc["attention_mask"],
|
| 122 |
-
use_cache=False,
|
| 123 |
-
)
|
| 124 |
-
h = out.last_hidden_state
|
| 125 |
-
tuples = self.decoder.generate(
|
| 126 |
-
h=h,
|
| 127 |
-
attention_mask=enc["attention_mask"],
|
| 128 |
-
ref_sentences=enc["ref_sentences"],
|
| 129 |
-
cand_sentences=enc["cand_sentences"],
|
| 130 |
)
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
score = math.exp(-K_total)
|
|
|
|
| 133 |
cat_counts = [0] * self.cfg["n_cat"]
|
| 134 |
anat_counts = [0] * self.cfg["n_anat"]
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
return {
|
| 141 |
"score": score,
|
| 142 |
"K_total": K_total,
|
| 143 |
-
"tuples":
|
| 144 |
"category_counts": cat_counts,
|
| 145 |
"anatomy_counts": anat_counts,
|
| 146 |
}
|
| 147 |
|
| 148 |
|
| 149 |
-
# ----------------------- module-level convenience ----------------------- #
|
| 150 |
-
|
| 151 |
_INSTANCE: Optional[Chest2Err] = None
|
| 152 |
|
| 153 |
|
|
|
|
| 2 |
|
| 3 |
Usage:
|
| 4 |
from chest2err import chest2err_score, chest2err_detail
|
| 5 |
+
score = chest2err_score(ref, cand) # float in (0, 1]
|
| 6 |
+
detail = chest2err_detail(ref, cand) # full breakdown
|
| 7 |
|
| 8 |
+
The bundle ships the merged backbone weights, the decoder weights, the
|
| 9 |
+
tokenizer, and the concept vocabulary. No additional downloads occur at
|
| 10 |
+
inference; the Qwen3-architecture backbone class is taken from the
|
| 11 |
+
`transformers` package and instantiated from the bundled `config.json`.
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
from __future__ import annotations
|
| 14 |
|
| 15 |
import json
|
|
|
|
|
|
|
| 16 |
import math
|
| 17 |
+
import os
|
| 18 |
from pathlib import Path
|
| 19 |
+
from typing import Any, Dict, List, Optional
|
| 20 |
|
| 21 |
import torch
|
|
|
|
| 22 |
from transformers import AutoModel, AutoTokenizer
|
| 23 |
from safetensors.torch import load_file
|
| 24 |
|
| 25 |
+
# Sibling files in this package
|
| 26 |
from chest2err_modeling import CADAD
|
| 27 |
+
from chest2err_collate import encode_pair_for_decoder, collate_decoder_batch
|
|
|
|
| 28 |
|
| 29 |
PACKAGE_DIR = Path(__file__).resolve().parent
|
| 30 |
|
| 31 |
+
CAT_NAMES = {0: "EOS", 1: "false_prediction", 2: "omission", 3: "location",
|
| 32 |
+
4: "severity", 5: "comparison"}
|
| 33 |
+
ANAT_NAMES = {0: "Lung & Airways", 1: "Cardiovascular", 2: "Mediastinum & Hila",
|
| 34 |
+
3: "Upper Abdomen", 4: "Pleura", 5: "Bones / Spine", 6: "Chest Wall",
|
| 35 |
+
7: "Lower Neck", 8: "Others"}
|
| 36 |
+
|
| 37 |
|
| 38 |
def _load_config() -> Dict[str, Any]:
|
| 39 |
with open(PACKAGE_DIR / "chest2err_config.json") as f:
|
|
|
|
| 43 |
class Chest2Err:
|
| 44 |
"""Loads the merged backbone + decoder once, then scores pairs."""
|
| 45 |
|
| 46 |
+
def __init__(self,
|
| 47 |
+
device: str = "cuda" if torch.cuda.is_available() else "cpu",
|
| 48 |
+
attn_implementation: Optional[str] = None):
|
| 49 |
cfg = _load_config()
|
| 50 |
self.cfg = cfg
|
| 51 |
self.device = device
|
| 52 |
self.max_length = cfg["max_length"]
|
|
|
|
| 53 |
|
| 54 |
+
# Concept vocab (size determines decoder output head dim)
|
| 55 |
+
with open(PACKAGE_DIR / "concept2id.json") as f:
|
| 56 |
+
self.concept2id: Dict[str, int] = json.load(f)
|
| 57 |
+
self.n_concept = len(self.concept2id)
|
| 58 |
+
self.id2concept = {v: k for k, v in self.concept2id.items()}
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
# Tokenizer + backbone load from bundled files only.
|
| 61 |
+
self.tokenizer = AutoTokenizer.from_pretrained(str(PACKAGE_DIR))
|
| 62 |
+
kw = {"torch_dtype": torch.bfloat16}
|
| 63 |
+
if attn_implementation:
|
| 64 |
+
kw["attn_implementation"] = attn_implementation
|
| 65 |
+
backbone = AutoModel.from_pretrained(str(PACKAGE_DIR), **kw)
|
| 66 |
+
|
| 67 |
+
# CADAD wraps the backbone + decoder. Construct, then load merged backbone
|
| 68 |
+
# weights + decoder weights.
|
| 69 |
+
self.model = CADAD(
|
| 70 |
+
backbone=backbone,
|
| 71 |
+
hidden_size=cfg["hidden_size"],
|
| 72 |
+
n_cat=cfg["n_cat"],
|
| 73 |
n_anat=cfg["n_anat"],
|
| 74 |
+
n_concept=self.n_concept,
|
| 75 |
+
n_severity=2,
|
| 76 |
decoder_layers=cfg["decoder_layers"],
|
| 77 |
decoder_heads=cfg["decoder_heads"],
|
| 78 |
decoder_ff=cfg["decoder_ff"],
|
| 79 |
+
dropout=cfg["decoder_dropout"],
|
| 80 |
max_decode_steps=cfg["max_decode_steps"],
|
| 81 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
# The backbone weights were already loaded by AutoModel.from_pretrained.
|
| 84 |
+
# Now layer the decoder weights on top.
|
| 85 |
+
decoder_state = load_file(str(PACKAGE_DIR / "decoder.safetensors"))
|
| 86 |
+
missing, unexpected = self.model.load_state_dict(decoder_state, strict=False)
|
| 87 |
+
# Expected: many `backbone.*` keys are "missing" from decoder_state
|
| 88 |
+
# (they came from model.safetensors via from_pretrained). That's fine.
|
| 89 |
+
|
| 90 |
+
self.model = self.model.to(device).eval()
|
| 91 |
|
| 92 |
@torch.inference_mode()
|
| 93 |
def score(self, ref: str, cand: str) -> float:
|
| 94 |
+
return self.detail(ref, cand)["score"]
|
|
|
|
|
|
|
| 95 |
|
| 96 |
@torch.inference_mode()
|
| 97 |
def detail(self, ref: str, cand: str) -> Dict[str, Any]:
|
| 98 |
+
item = encode_pair_for_decoder(
|
| 99 |
+
self.tokenizer, ref, cand, max_length=self.max_length,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
)
|
| 101 |
+
batch = collate_decoder_batch([item],
|
| 102 |
+
pad_token_id=self.tokenizer.pad_token_id or 0)
|
| 103 |
+
batch = {k: v.to(self.device) for k, v in batch.items()}
|
| 104 |
+
|
| 105 |
+
with torch.autocast(
|
| 106 |
+
device_type="cuda" if str(self.device).startswith("cuda") else "cpu",
|
| 107 |
+
dtype=torch.bfloat16,
|
| 108 |
+
):
|
| 109 |
+
seqs = self.model.decode_greedy(
|
| 110 |
+
batch["input_ids"],
|
| 111 |
+
batch["attention_mask"],
|
| 112 |
+
batch["ref_seg_token_mask"],
|
| 113 |
+
batch["cand_seg_token_mask"],
|
| 114 |
+
)
|
| 115 |
+
seq = seqs[0]
|
| 116 |
+
K_total = len(seq)
|
| 117 |
score = math.exp(-K_total)
|
| 118 |
+
|
| 119 |
cat_counts = [0] * self.cfg["n_cat"]
|
| 120 |
anat_counts = [0] * self.cfg["n_anat"]
|
| 121 |
+
tuples_out: List[Dict[str, Any]] = []
|
| 122 |
+
for t in seq:
|
| 123 |
+
c = int(t.get("cat", 0))
|
| 124 |
+
a = int(t.get("anat", 0))
|
| 125 |
+
if 1 <= c <= self.cfg["n_cat"]:
|
| 126 |
+
cat_counts[c - 1] += 1
|
| 127 |
+
if 0 <= a < self.cfg["n_anat"]:
|
| 128 |
+
anat_counts[a] += 1
|
| 129 |
+
tuples_out.append({
|
| 130 |
+
"cat": c, "cat_name": CAT_NAMES.get(c, str(c)),
|
| 131 |
+
"anat": a, "anat_name": ANAT_NAMES.get(a, str(a)),
|
| 132 |
+
"concept_id": int(t.get("concept_id", 0)),
|
| 133 |
+
"concept": self.id2concept.get(int(t.get("concept_id", 0)), "<UNK>"),
|
| 134 |
+
"ref_seg_idx": int(t.get("ref_seg_idx", -1)),
|
| 135 |
+
"cand_seg_idx": int(t.get("cand_seg_idx", -1)),
|
| 136 |
+
})
|
| 137 |
return {
|
| 138 |
"score": score,
|
| 139 |
"K_total": K_total,
|
| 140 |
+
"tuples": tuples_out,
|
| 141 |
"category_counts": cat_counts,
|
| 142 |
"anatomy_counts": anat_counts,
|
| 143 |
}
|
| 144 |
|
| 145 |
|
|
|
|
|
|
|
| 146 |
_INSTANCE: Optional[Chest2Err] = None
|
| 147 |
|
| 148 |
|
chest2err_collate.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sentence segmentation + collate for chest2err inference.
|
| 2 |
+
|
| 3 |
+
Stripped-down version of the in-tree training collate: keeps only what's needed
|
| 4 |
+
to run greedy decoding on one (ref, cand) pair.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import re
|
| 9 |
+
from typing import Any, Dict, List, Tuple
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
_SECTION_HEADER_RE = re.compile(r"\[[^\]]+\]")
|
| 15 |
+
_BULLET_RE = re.compile(r"\n\s*[-*•]\s+")
|
| 16 |
+
_SENT_BOUNDARY_RE = re.compile(r"(?<=\.)\s+(?=[A-Z])")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def segment_report(text: str, min_len: int = 3) -> List[str]:
|
| 20 |
+
if not text or not text.strip():
|
| 21 |
+
return []
|
| 22 |
+
t = _SECTION_HEADER_RE.sub("\n", text)
|
| 23 |
+
t = _BULLET_RE.sub("\n", t)
|
| 24 |
+
out: List[str] = []
|
| 25 |
+
for line in t.split("\n"):
|
| 26 |
+
line = line.strip()
|
| 27 |
+
if len(line) < min_len:
|
| 28 |
+
continue
|
| 29 |
+
for sent in _SENT_BOUNDARY_RE.split(line):
|
| 30 |
+
sent = sent.strip()
|
| 31 |
+
if len(sent) >= min_len:
|
| 32 |
+
out.append(sent)
|
| 33 |
+
return out
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def encode_pair_for_decoder(
|
| 37 |
+
tokenizer,
|
| 38 |
+
ref_text: str,
|
| 39 |
+
cand_text: str,
|
| 40 |
+
max_length: int = 1280,
|
| 41 |
+
ref_marker: str = "[REF]",
|
| 42 |
+
cand_marker: str = "[PRED]",
|
| 43 |
+
) -> Dict[str, Any]:
|
| 44 |
+
ref_segs = segment_report(ref_text)
|
| 45 |
+
cand_segs = segment_report(cand_text)
|
| 46 |
+
|
| 47 |
+
ref_marker_ids = tokenizer.encode(ref_marker + " ", add_special_tokens=False)
|
| 48 |
+
cand_marker_ids = tokenizer.encode(" " + cand_marker + " ", add_special_tokens=False)
|
| 49 |
+
ref_seg_token_ids = [tokenizer.encode(s + " ", add_special_tokens=False) for s in ref_segs]
|
| 50 |
+
cand_seg_token_ids = [tokenizer.encode(s + " ", add_special_tokens=False) for s in cand_segs]
|
| 51 |
+
|
| 52 |
+
def _total_len(rs, cs):
|
| 53 |
+
return (len(ref_marker_ids) + sum(len(x) for x in rs)
|
| 54 |
+
+ len(cand_marker_ids) + sum(len(x) for x in cs))
|
| 55 |
+
|
| 56 |
+
while _total_len(ref_seg_token_ids, cand_seg_token_ids) > max_length and cand_seg_token_ids:
|
| 57 |
+
cand_seg_token_ids.pop(); cand_segs = cand_segs[:-1]
|
| 58 |
+
while _total_len(ref_seg_token_ids, cand_seg_token_ids) > max_length and ref_seg_token_ids:
|
| 59 |
+
ref_seg_token_ids.pop(); ref_segs = ref_segs[:-1]
|
| 60 |
+
|
| 61 |
+
input_ids: List[int] = []
|
| 62 |
+
input_ids.extend(ref_marker_ids)
|
| 63 |
+
ref_ranges: List[Tuple[int, int]] = []
|
| 64 |
+
for ids in ref_seg_token_ids:
|
| 65 |
+
s = len(input_ids); input_ids.extend(ids); ref_ranges.append((s, len(input_ids)))
|
| 66 |
+
input_ids.extend(cand_marker_ids)
|
| 67 |
+
cand_ranges: List[Tuple[int, int]] = []
|
| 68 |
+
for ids in cand_seg_token_ids:
|
| 69 |
+
s = len(input_ids); input_ids.extend(ids); cand_ranges.append((s, len(input_ids)))
|
| 70 |
+
|
| 71 |
+
return {
|
| 72 |
+
"input_ids": input_ids,
|
| 73 |
+
"ref_seg_ranges": ref_ranges,
|
| 74 |
+
"cand_seg_ranges": cand_ranges,
|
| 75 |
+
"ref_segs": ref_segs,
|
| 76 |
+
"cand_segs": cand_segs,
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def collate_decoder_batch(items: List[Dict[str, Any]], pad_token_id: int = 0) -> Dict[str, torch.Tensor]:
|
| 81 |
+
T = max(len(it["input_ids"]) for it in items)
|
| 82 |
+
Sr = max(max(len(it["ref_seg_ranges"]), 1) for it in items)
|
| 83 |
+
Sc = max(max(len(it["cand_seg_ranges"]), 1) for it in items)
|
| 84 |
+
B = len(items)
|
| 85 |
+
|
| 86 |
+
input_ids = torch.full((B, T), pad_token_id, dtype=torch.long)
|
| 87 |
+
attention_mask = torch.zeros((B, T), dtype=torch.long)
|
| 88 |
+
ref_seg_token_mask = torch.zeros((B, Sr, T), dtype=torch.bool)
|
| 89 |
+
cand_seg_token_mask = torch.zeros((B, Sc, T), dtype=torch.bool)
|
| 90 |
+
|
| 91 |
+
for b, it in enumerate(items):
|
| 92 |
+
ids = it["input_ids"]; L = len(ids)
|
| 93 |
+
input_ids[b, :L] = torch.tensor(ids, dtype=torch.long)
|
| 94 |
+
attention_mask[b, :L] = 1
|
| 95 |
+
for s, (a, e) in enumerate(it["ref_seg_ranges"]):
|
| 96 |
+
ref_seg_token_mask[b, s, a:e] = True
|
| 97 |
+
for s, (a, e) in enumerate(it["cand_seg_ranges"]):
|
| 98 |
+
cand_seg_token_mask[b, s, a:e] = True
|
| 99 |
+
|
| 100 |
+
return {
|
| 101 |
+
"input_ids": input_ids,
|
| 102 |
+
"attention_mask": attention_mask,
|
| 103 |
+
"ref_seg_token_mask": ref_seg_token_mask,
|
| 104 |
+
"cand_seg_token_mask": cand_seg_token_mask,
|
| 105 |
+
}
|
concept2id.json
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"Abdominal aortic aneurysm": 0,
|
| 3 |
+
"Abdominal aortic aneurysm (partially imaged)": 1,
|
| 4 |
+
"Abdominal aortic calcification / atherosclerosis": 2,
|
| 5 |
+
"Abdominal aortic calcification / atherosclerosis (partially imaged)": 3,
|
| 6 |
+
"Abdominal lymphadenopathy": 4,
|
| 7 |
+
"Aberrant right subclavian artery": 5,
|
| 8 |
+
"Accessory hemiazygos vein": 6,
|
| 9 |
+
"Accessory spleen": 7,
|
| 10 |
+
"Accessory spleen / splenule / polysplenia": 8,
|
| 11 |
+
"Acinar infiltration areas": 9,
|
| 12 |
+
"Acinar opacities": 10,
|
| 13 |
+
"Acute rib fracture": 11,
|
| 14 |
+
"Adrenal atrophy": 12,
|
| 15 |
+
"Adrenal calcification": 13,
|
| 16 |
+
"Adrenal gland": 14,
|
| 17 |
+
"Adrenal gland calibration": 15,
|
| 18 |
+
"Adrenal gland normal": 16,
|
| 19 |
+
"Adrenal nodule": 17,
|
| 20 |
+
"Adrenal nodule (mass)": 18,
|
| 21 |
+
"Adrenal nodule / mass": 19,
|
| 22 |
+
"Adrenal thickening / hyperplasia": 20,
|
| 23 |
+
"Adrenals": 21,
|
| 24 |
+
"Air bronchograms": 22,
|
| 25 |
+
"Alveolar-interstitial density": 23,
|
| 26 |
+
"Angiomyolipoma": 24,
|
| 27 |
+
"Anterior mediastinal mass": 25,
|
| 28 |
+
"Aorta": 26,
|
| 29 |
+
"Aorta / pulmonary artery": 27,
|
| 30 |
+
"Aortic aneurysm": 28,
|
| 31 |
+
"Aortic calcification": 29,
|
| 32 |
+
"Aortic dissection / intramural hematoma": 30,
|
| 33 |
+
"Aortic stent": 31,
|
| 34 |
+
"Aortic valve calcification": 32,
|
| 35 |
+
"Aortic valve replacement": 33,
|
| 36 |
+
"Ascending aorta": 34,
|
| 37 |
+
"Ascites": 35,
|
| 38 |
+
"Atelectasis": 36,
|
| 39 |
+
"Atheroma plaques": 37,
|
| 40 |
+
"Atheroma plaques are observed in the aorta": 38,
|
| 41 |
+
"Atheromatous plaques": 39,
|
| 42 |
+
"Atherosclerotic wall calcifications": 40,
|
| 43 |
+
"Axillary lymphadenopathy": 41,
|
| 44 |
+
"Azygos fissure / lobe": 42,
|
| 45 |
+
"Azygos fissure lobe": 43,
|
| 46 |
+
"Azygos fissure variation": 44,
|
| 47 |
+
"Azygos lobe": 45,
|
| 48 |
+
"Azygos lobe / fissure": 46,
|
| 49 |
+
"Azygos lobe variation": 47,
|
| 50 |
+
"Bilateral adrenal glands": 48,
|
| 51 |
+
"Biliary drainage catheter": 49,
|
| 52 |
+
"Biliary duct dilation": 50,
|
| 53 |
+
"Biliary sludge": 51,
|
| 54 |
+
"Biliary stent / catheter / drain": 52,
|
| 55 |
+
"Bones / Spine": 53,
|
| 56 |
+
"Bones / Spine_others": 54,
|
| 57 |
+
"Bovine arch aorta": 55,
|
| 58 |
+
"Bowel wall thickening / inflammation": 56,
|
| 59 |
+
"Breast": 57,
|
| 60 |
+
"Breast & Axilla": 58,
|
| 61 |
+
"Breast implant": 59,
|
| 62 |
+
"Breast implant (intact or present)": 60,
|
| 63 |
+
"Breast mass / focal asymmetry": 61,
|
| 64 |
+
"Bronchial wall thickening": 62,
|
| 65 |
+
"Bronchiectasis": 63,
|
| 66 |
+
"Bronchopleural fistula": 64,
|
| 67 |
+
"Bronchopneumonia": 65,
|
| 68 |
+
"Bronchopneumonic infiltration": 66,
|
| 69 |
+
"Bulla": 67,
|
| 70 |
+
"Bulla / giant bulla": 68,
|
| 71 |
+
"Bullae / giant bulla": 69,
|
| 72 |
+
"CT involvement score": 70,
|
| 73 |
+
"Calcific mediastinal / hilar lymph nodes": 71,
|
| 74 |
+
"Calcified mediastinal / hilar lymph nodes": 72,
|
| 75 |
+
"Cardiac size and morphology": 73,
|
| 76 |
+
"Cardiomegaly": 74,
|
| 77 |
+
"Cardiovascular": 75,
|
| 78 |
+
"Cardiovascular_others": 76,
|
| 79 |
+
"Cavitary nodule / mass": 77,
|
| 80 |
+
"Central venous catheter": 78,
|
| 81 |
+
"Central venous catheter / PICC": 79,
|
| 82 |
+
"Centrilobular nodules / bronchiolitis pattern": 80,
|
| 83 |
+
"Cervical / supraclavicular lymphadenopathy": 81,
|
| 84 |
+
"Chest Wall": 82,
|
| 85 |
+
"Chest Wall_others": 83,
|
| 86 |
+
"Chest tube": 84,
|
| 87 |
+
"Chest tube / pleural drain": 85,
|
| 88 |
+
"Chest wall mass": 86,
|
| 89 |
+
"Chest wall soft tissue edema": 87,
|
| 90 |
+
"Chest wall soft tissue edema / hematoma": 88,
|
| 91 |
+
"Chest wall tumor invasion": 89,
|
| 92 |
+
"Cholecystectomy": 90,
|
| 93 |
+
"Cholehithiasis / gallstones": 91,
|
| 94 |
+
"Cholelithiasis": 92,
|
| 95 |
+
"Cholelithiasis / gallstones": 93,
|
| 96 |
+
"Complex renal cyst / solid renal mass": 94,
|
| 97 |
+
"Consolidation": 95,
|
| 98 |
+
"Coronary artery calcification": 96,
|
| 99 |
+
"Coronary stent or bypass graft": 97,
|
| 100 |
+
"Crazy-paving pattern": 98,
|
| 101 |
+
"Cylindrical and cystic bronchiectasis": 99,
|
| 102 |
+
"Cylindrical bronchiectasis": 100,
|
| 103 |
+
"DISH": 101,
|
| 104 |
+
"Degenerative / osseous lesions": 102,
|
| 105 |
+
"Degenerative spine changes": 103,
|
| 106 |
+
"Dextrocardia": 104,
|
| 107 |
+
"Diaphragmatic elevation": 105,
|
| 108 |
+
"Diverticulosis": 106,
|
| 109 |
+
"Effusion": 107,
|
| 110 |
+
"Emphysema": 108,
|
| 111 |
+
"Endotracheal tube": 109,
|
| 112 |
+
"Esophageal dilation": 110,
|
| 113 |
+
"Esophageal stent": 111,
|
| 114 |
+
"Esophageal wall thickening / mass": 112,
|
| 115 |
+
"Esophagus dilation": 113,
|
| 116 |
+
"Fibrotic band": 114,
|
| 117 |
+
"Focal liver lesion": 115,
|
| 118 |
+
"Focal liver lesion (nodule / mass)": 116,
|
| 119 |
+
"Focal splenic lesion": 117,
|
| 120 |
+
"Focal splenic lesion (nodule / mass)": 118,
|
| 121 |
+
"Fractures": 119,
|
| 122 |
+
"Free fluid": 120,
|
| 123 |
+
"GGO": 121,
|
| 124 |
+
"Gallbladder": 122,
|
| 125 |
+
"Gallbladder & biliary": 123,
|
| 126 |
+
"Gallbladder wall thickening": 124,
|
| 127 |
+
"Gallstones": 125,
|
| 128 |
+
"Gallstones / cholelithiasis": 126,
|
| 129 |
+
"Goiter": 127,
|
| 130 |
+
"Ground-glass opacity": 128,
|
| 131 |
+
"Ground-glass opacity (GGO)": 129,
|
| 132 |
+
"Gynecomastia": 130,
|
| 133 |
+
"Healed rib fracture": 131,
|
| 134 |
+
"Heart": 132,
|
| 135 |
+
"Heart contour": 133,
|
| 136 |
+
"Heart contour and size": 134,
|
| 137 |
+
"Heart contour and size are natural": 135,
|
| 138 |
+
"Heart contour and size are normal": 136,
|
| 139 |
+
"Heart contour and size are normal.": 137,
|
| 140 |
+
"Heart contour size": 138,
|
| 141 |
+
"Heart contour size is natural": 139,
|
| 142 |
+
"Heart contour size is natural.": 140,
|
| 143 |
+
"Heart contour size is normal": 141,
|
| 144 |
+
"Heart contour, size": 142,
|
| 145 |
+
"Heart contour, size are normal": 143,
|
| 146 |
+
"Heart contour, size are normal.": 144,
|
| 147 |
+
"Heart contour, size is normal": 145,
|
| 148 |
+
"Heart dimensions": 146,
|
| 149 |
+
"Heart dimensions and compartments": 147,
|
| 150 |
+
"Heart has a natural appearance": 148,
|
| 151 |
+
"Heart size": 149,
|
| 152 |
+
"Heart size and morphology": 150,
|
| 153 |
+
"Hemangioma": 151,
|
| 154 |
+
"Hemothorax": 152,
|
| 155 |
+
"Hepatic calcification": 153,
|
| 156 |
+
"Hepatic steatosis": 154,
|
| 157 |
+
"Hepatomegaly": 155,
|
| 158 |
+
"Hepatosteatosis": 156,
|
| 159 |
+
"Hiatal hernia": 157,
|
| 160 |
+
"Hilar lymphadenopathy": 158,
|
| 161 |
+
"Honeycomb appearance": 159,
|
| 162 |
+
"Honeycomb lung": 160,
|
| 163 |
+
"Honeycombing": 161,
|
| 164 |
+
"Horseshoe kidney": 162,
|
| 165 |
+
"Horseshoe kidney variation": 163,
|
| 166 |
+
"Hydatid cyst": 164,
|
| 167 |
+
"Hydronephrosis": 165,
|
| 168 |
+
"Hydropic gallbladder / distension": 166,
|
| 169 |
+
"IVC filter": 167,
|
| 170 |
+
"Infectious process": 168,
|
| 171 |
+
"Interlobular septal thickening": 169,
|
| 172 |
+
"Interstitial / fibrotic lung disease": 170,
|
| 173 |
+
"Interstitial lung disease-fibrosis": 171,
|
| 174 |
+
"Inverted halo sign": 172,
|
| 175 |
+
"Kidneys / urinary tract": 173,
|
| 176 |
+
"LVAD": 174,
|
| 177 |
+
"LVAD / other cardiac assist device": 175,
|
| 178 |
+
"Left atrium": 176,
|
| 179 |
+
"Linear atelectasis": 177,
|
| 180 |
+
"Liver": 178,
|
| 181 |
+
"Liver contour irregularity / cirrhosis features": 179,
|
| 182 |
+
"Liver is in normal appearance.": 180,
|
| 183 |
+
"Liver right lobe transplantation": 181,
|
| 184 |
+
"Liver transplant": 182,
|
| 185 |
+
"Lobar / segmental atelectasis": 183,
|
| 186 |
+
"Lobular kidney contours": 184,
|
| 187 |
+
"Loculated pleural effusion": 185,
|
| 188 |
+
"Lower Neck_others": 186,
|
| 189 |
+
"Lung parenchymal attenuation patterns": 187,
|
| 190 |
+
"Lungs": 188,
|
| 191 |
+
"Lungs & Airways_others": 189,
|
| 192 |
+
"Lymph nodes": 190,
|
| 193 |
+
"Lymphadenopathy": 191,
|
| 194 |
+
"Lytic bone lesion": 192,
|
| 195 |
+
"Lytic-destructive lesions": 193,
|
| 196 |
+
"Main pulmonary artery enlargement": 194,
|
| 197 |
+
"Mediastinal hematoma / fluid collection": 195,
|
| 198 |
+
"Mediastinal lymph nodes": 196,
|
| 199 |
+
"Mediastinal lymphadenopathy": 197,
|
| 200 |
+
"Mediastinal mass": 198,
|
| 201 |
+
"Mediastinal masses / cysts": 199,
|
| 202 |
+
"Mediastinal structures": 200,
|
| 203 |
+
"Mediastinum & Hila": 201,
|
| 204 |
+
"Mediastinum & Hila_others": 202,
|
| 205 |
+
"Middle / posterior mediastinal mass or cyst": 203,
|
| 206 |
+
"Mitral annular calcification": 204,
|
| 207 |
+
"Mixed osteolytic-osteosclerotic lesion": 205,
|
| 208 |
+
"Mosaic attenuation / air-trapping": 206,
|
| 209 |
+
"Motion artifact": 207,
|
| 210 |
+
"Motion artifact / suboptimal study": 208,
|
| 211 |
+
"Mucoid impaction / plugging": 209,
|
| 212 |
+
"Nasogastric / orogastric tube": 210,
|
| 213 |
+
"Nasogastric tube": 211,
|
| 214 |
+
"Neck soft tissue mass": 212,
|
| 215 |
+
"Nephrectomy": 213,
|
| 216 |
+
"Nephrectomy (kidney absent / operated)": 214,
|
| 217 |
+
"Nephrostomy catheter": 215,
|
| 218 |
+
"Neural foramina": 216,
|
| 219 |
+
"No lytic-destructive lesion": 217,
|
| 220 |
+
"No significant intrathoracic abnormality": 218,
|
| 221 |
+
"No upper abdominal free fluid-collection": 219,
|
| 222 |
+
"No upper abdominal free fluid-collection was detected in the sections.": 220,
|
| 223 |
+
"Nodular infiltrates": 221,
|
| 224 |
+
"Nodules and masses": 222,
|
| 225 |
+
"Non-acute / healed rib fracture": 223,
|
| 226 |
+
"Omental caking / peritoneal carcinomatosa": 224,
|
| 227 |
+
"Omental caking / peritoneal carcinomatosis": 225,
|
| 228 |
+
"Osteolytic bone lesion": 226,
|
| 229 |
+
"Osteopenia": 227,
|
| 230 |
+
"Osteophyte": 228,
|
| 231 |
+
"Osteophytes": 229,
|
| 232 |
+
"Osteoporosis": 230,
|
| 233 |
+
"Osteosclerotic bone lesion": 231,
|
| 234 |
+
"Others": 232,
|
| 235 |
+
"Others (devices / post-surgical / global)": 233,
|
| 236 |
+
"Others_others": 234,
|
| 237 |
+
"Pacemaker / ICD leads": 235,
|
| 238 |
+
"Pancreas": 236,
|
| 239 |
+
"Pancreatic calcification": 237,
|
| 240 |
+
"Pancreatic lipomatosis": 238,
|
| 241 |
+
"Pancreatic mass (>3 cm)": 239,
|
| 242 |
+
"Pancreatic mass / focal lesion": 240,
|
| 243 |
+
"Paraseptal emphysema": 241,
|
| 244 |
+
"Parenchymal scarring": 242,
|
| 245 |
+
"Parenchymal scarring / fibrotic band": 243,
|
| 246 |
+
"Pectus excavatum": 244,
|
| 247 |
+
"Pectus excavatum deformity": 245,
|
| 248 |
+
"Pectus excavatus": 246,
|
| 249 |
+
"Pectus excavatus anomaly": 247,
|
| 250 |
+
"Peribronchial sheath thickening": 248,
|
| 251 |
+
"Peribronchial thickening": 249,
|
| 252 |
+
"Peribronchial wall thickening": 250,
|
| 253 |
+
"Pericardial effusion": 251,
|
| 254 |
+
"Pericardial thickening": 252,
|
| 255 |
+
"Pericardial thickening / calcification": 253,
|
| 256 |
+
"Peripheral patchy ground glass densities": 254,
|
| 257 |
+
"Peritoneal carcinomatosis": 255,
|
| 258 |
+
"Pleura_others": 256,
|
| 259 |
+
"Pleural effusion": 257,
|
| 260 |
+
"Pleural nodule": 258,
|
| 261 |
+
"Pleural nodule / mass": 259,
|
| 262 |
+
"Pleural plaques": 260,
|
| 263 |
+
"Pleural thickening": 261,
|
| 264 |
+
"Pneumobilia": 262,
|
| 265 |
+
"Pneumomediastinum": 263,
|
| 266 |
+
"Pneumonia": 264,
|
| 267 |
+
"Pneumonic infiltration": 265,
|
| 268 |
+
"Pneumopericardium": 266,
|
| 269 |
+
"Pneumoperitoneum": 267,
|
| 270 |
+
"Pneumothorax": 268,
|
| 271 |
+
"Post-cholecystectomy": 269,
|
| 272 |
+
"Post-cholecystectomy (gallbladder operated / absent)": 270,
|
| 273 |
+
"Post-lobectomy / segmentectomy": 271,
|
| 274 |
+
"Post-lumpectomy / post-mastectomy change": 272,
|
| 275 |
+
"Post-mastectomy change": 273,
|
| 276 |
+
"Post-pneumonectomy": 274,
|
| 277 |
+
"Post-surgical change": 275,
|
| 278 |
+
"Post-thoracotomy change": 276,
|
| 279 |
+
"Post-thyroidectomy change": 277,
|
| 280 |
+
"Post-transplant change": 278,
|
| 281 |
+
"Postoperative spine change / hardware": 279,
|
| 282 |
+
"Postoperative stomach change": 280,
|
| 283 |
+
"Pulmonary cyst / cystic lung disease": 281,
|
| 284 |
+
"Pulmonary cysts / cystic lung disease": 282,
|
| 285 |
+
"Pulmonary embolism": 283,
|
| 286 |
+
"Pulmonary mass": 284,
|
| 287 |
+
"Pulmonary mass (>3 cm)": 285,
|
| 288 |
+
"Pulmonary nodule": 286,
|
| 289 |
+
"Pulmonary nodule (solid / PSN / GGN)": 287,
|
| 290 |
+
"Pulmonary trunk": 288,
|
| 291 |
+
"Pulmonary trunk caliber": 289,
|
| 292 |
+
"Pulmonary trunk calibration": 290,
|
| 293 |
+
"Pulmonary trunk diameter": 291,
|
| 294 |
+
"Renal artery stent": 292,
|
| 295 |
+
"Renal atrophy": 293,
|
| 296 |
+
"Renal atrophy / decreased renal size": 294,
|
| 297 |
+
"Renal calcification": 295,
|
| 298 |
+
"Renal calculi": 296,
|
| 299 |
+
"Renal calculi / nephrolithiasis": 297,
|
| 300 |
+
"Renal cyst": 298,
|
| 301 |
+
"Reticulation / intralobular thickening": 299,
|
| 302 |
+
"Schmorl nodules": 300,
|
| 303 |
+
"Schmorl's nodules": 301,
|
| 304 |
+
"Sclerotic bone lesion": 302,
|
| 305 |
+
"Scoliosis / kyphosis": 303,
|
| 306 |
+
"Septal thickening": 304,
|
| 307 |
+
"Sequela parenchymal changes": 305,
|
| 308 |
+
"Simple renal cyst": 306,
|
| 309 |
+
"Soft tissue density": 307,
|
| 310 |
+
"Soft tissue masses": 308,
|
| 311 |
+
"Spleen": 309,
|
| 312 |
+
"Spleen size": 310,
|
| 313 |
+
"Spleen sizes": 311,
|
| 314 |
+
"Splenectomy": 312,
|
| 315 |
+
"Splenomegaly": 313,
|
| 316 |
+
"Splenosis": 314,
|
| 317 |
+
"Sternal fracture": 315,
|
| 318 |
+
"Sternal hardware": 316,
|
| 319 |
+
"Study limitation / limited evaluation (non-motion)": 317,
|
| 320 |
+
"Study quality / global": 318,
|
| 321 |
+
"Subcarinal lymph nodes": 319,
|
| 322 |
+
"Subcutaneous emphysema": 320,
|
| 323 |
+
"Subsegmental / linear atelectasis": 321,
|
| 324 |
+
"Surgical hardware": 322,
|
| 325 |
+
"Surgical material": 323,
|
| 326 |
+
"Surgical suture materials": 324,
|
| 327 |
+
"Surgical sutures": 325,
|
| 328 |
+
"Suture materials": 326,
|
| 329 |
+
"Suture materials secondary to bypass surgery": 327,
|
| 330 |
+
"Syndesmophytes": 328,
|
| 331 |
+
"The left hemidiaphragm is elevated": 329,
|
| 332 |
+
"Thoracic aorta diameter": 330,
|
| 333 |
+
"Thoracic aortic aneurysm": 331,
|
| 334 |
+
"Thoracic aortic calcification": 332,
|
| 335 |
+
"Thoracic aortic diameter": 333,
|
| 336 |
+
"Thoracic aortic dilation": 334,
|
| 337 |
+
"Thoracic aortic ectasia / dilation": 335,
|
| 338 |
+
"Thoracic aortic ectasia / dilation (non-aneurysmal)": 336,
|
| 339 |
+
"Thoracic esophageal calibration": 337,
|
| 340 |
+
"Thoracic esophageal dilation": 338,
|
| 341 |
+
"Thoracic esophageal wall thickening": 339,
|
| 342 |
+
"Thoracic esophageal wall thickening / mass": 340,
|
| 343 |
+
"Thoracic esophagus": 341,
|
| 344 |
+
"Thoracic esophagus calibration": 342,
|
| 345 |
+
"Thoracic kyphosis": 343,
|
| 346 |
+
"Thoracic vertebral corpus heights": 344,
|
| 347 |
+
"Thoracic vertebral corpus heights, alignments and densities are normal": 345,
|
| 348 |
+
"Thoracic vertebral corpus heights, alignments and densities are normal.": 346,
|
| 349 |
+
"Thymic remnant / hyperplasia": 347,
|
| 350 |
+
"Thyroid enlargement (goiter)": 348,
|
| 351 |
+
"Thyroid nodule": 349,
|
| 352 |
+
"Trachea": 350,
|
| 353 |
+
"Trachea & Airways": 351,
|
| 354 |
+
"Trachea and main bronchi are open": 352,
|
| 355 |
+
"Tracheal / bronchial wall thickening": 353,
|
| 356 |
+
"Tracheal diverticulum": 354,
|
| 357 |
+
"Tracheal stenosis": 355,
|
| 358 |
+
"Tracheal stenosis / malacia": 356,
|
| 359 |
+
"Tracheal wall thickening": 357,
|
| 360 |
+
"Tracheobronchopathy osteochondroplastica": 358,
|
| 361 |
+
"Tracheomegaly": 359,
|
| 362 |
+
"Tracheostomy tube": 360,
|
| 363 |
+
"Traction bronchiectasis": 361,
|
| 364 |
+
"Traction bronchiectasis / bronchiolectasis": 362,
|
| 365 |
+
"Tree-in-bud": 363,
|
| 366 |
+
"Tubes, catheters, and support devices": 364,
|
| 367 |
+
"Upper Abdomen": 365,
|
| 368 |
+
"Upper Abdomen_others": 366,
|
| 369 |
+
"Upper abdominal free fluid-collection": 367,
|
| 370 |
+
"Upper abdominal vessels": 368,
|
| 371 |
+
"Valves and cardiac devices": 369,
|
| 372 |
+
"Vertebral compression fracture": 370,
|
| 373 |
+
"Vertebral corpus height": 371,
|
| 374 |
+
"Vertebral corpus heights": 372,
|
| 375 |
+
"Vertebral corpus heights are preserved": 373,
|
| 376 |
+
"Vertebral corpus heights are preserved.": 374,
|
| 377 |
+
"Vertebral corpus heights preserved": 375,
|
| 378 |
+
"Vertebral corpus heights, alignments and densities": 376,
|
| 379 |
+
"Vertebral corpus heights, alignments and densities within the sections are normal": 377,
|
| 380 |
+
"Vertebral fracture": 378,
|
| 381 |
+
"Vertebral hemangioma": 379,
|
| 382 |
+
"Viral pneumonia": 380,
|
| 383 |
+
"Volume loss / hyperinflation": 381,
|
| 384 |
+
"azygos fissure variation": 382,
|
| 385 |
+
"tree-in-bud": 383,
|
| 386 |
+
"<UNK>": 384
|
| 387 |
+
}
|