Feature Extraction
Transformers
Safetensors
Italian
radgraph_it
radiology
information-extraction
named-entity-recognition
relation-extraction
medical
radgraph
custom_code
Instructions to use radgraphIT/Radgraph-IT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use radgraphIT/Radgraph-IT with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="radgraphIT/Radgraph-IT", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("radgraphIT/Radgraph-IT", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add transformers-compatible wrapper (AutoModel/AutoTokenizer via trust_remote_code)
0c48771 verified | """In-memory representation of a DyGIE-format document (JSONL). | |
| Trimmed port of the vendored radgraph.dygie.data.dataset_readers.document module: only the | |
| NER + relation pieces are kept (no coreference clusters, no events -- this project's configs | |
| always set loss_weights.coref = loss_weights.events = 0, so those heads never ran in v1 | |
| either). See training/README.md for the schema. | |
| """ | |
| import re | |
| import json | |
| from typing import Any, Dict, List, Optional | |
| import numpy as np | |
| def _fields_to_batches(d: dict, keys_to_ignore=()): | |
| """Inverse of `_batches_to_fields`: {"a": [1, 2], "b": [3, 4]} -> [{"a": 1, "b": 3}, {"a": 2, "b": 4}].""" | |
| keys = [k for k in d.keys() if k not in keys_to_ignore] | |
| lengths = {k: len(d[k]) for k in keys} | |
| if len(set(lengths.values())) != 1: | |
| raise ValueError(f"For document {d.get('doc_key')}, fields have different lengths: {lengths}.") | |
| length = next(iter(lengths.values())) | |
| return [{k: d[k][i] for k in keys} for i in range(length)] | |
| def _batches_to_fields(batches: List[dict]): | |
| first_keys = batches[0].keys() | |
| for entry in batches[1:]: | |
| if set(entry.keys()) != set(first_keys): | |
| raise ValueError("Keys do not match on all entries.") | |
| res = {k: [] for k in first_keys} | |
| for batch in batches: | |
| for k, v in batch.items(): | |
| res[k].append(v) | |
| return res | |
| class Span: | |
| """A span, tracked both sentence-relative and document-relative.""" | |
| def __init__(self, start: int, end: int, sentence: "Sentence", sentence_offsets: bool = False): | |
| self.sentence = sentence | |
| # `sentence.text_joined` is memoized on Sentence (computed once) rather than rejoined | |
| # here per span: the relation head can construct O(K^2) PredictedRelation/Span objects | |
| # per document during decode (K = pruned span count), so re-joining a ~1000-word | |
| # sentence per span turns into the dominant runtime cost otherwise. | |
| self.sentence_text = sentence.text_joined | |
| self.start_sent = start if sentence_offsets else start - sentence.sentence_start | |
| self.end_sent = end if sentence_offsets else end - sentence.sentence_start | |
| def start_doc(self): | |
| return self.start_sent + self.sentence.sentence_start | |
| def end_doc(self): | |
| return self.end_sent + self.sentence.sentence_start | |
| def span_doc(self): | |
| return (self.start_doc, self.end_doc) | |
| def span_sent(self): | |
| return (self.start_sent, self.end_sent) | |
| def __repr__(self): | |
| return str(self.span_sent) | |
| def __eq__(self, other): | |
| return (self.span_doc == other.span_doc and self.span_sent == other.span_sent | |
| and self.sentence == other.sentence) | |
| def __hash__(self): | |
| return hash(self.span_sent + (self.sentence_text,)) | |
| class NER: | |
| def __init__(self, ner, sentence: "Sentence", sentence_offsets: bool = False): | |
| self.span = Span(ner[0], ner[1], sentence, sentence_offsets) | |
| self.label = ner[2] | |
| def __repr__(self): | |
| return f"{self.span!r}: {self.label}" | |
| def __eq__(self, other): | |
| return self.span == other.span and self.label == other.label | |
| def to_json(self): | |
| return list(self.span.span_doc) + [self.label] | |
| def _format_float(x): | |
| return round(x, 4) | |
| class PredictedNER(NER): | |
| def __init__(self, ner, sentence, sentence_offsets: bool = False): | |
| """`ner` = [span_start, span_end, label, raw_score, softmax_score].""" | |
| super().__init__(ner, sentence, sentence_offsets) | |
| self.raw_score = ner[3] | |
| self.softmax_score = ner[4] | |
| def to_json(self): | |
| return super().to_json() + [_format_float(self.raw_score), _format_float(self.softmax_score)] | |
| class Relation: | |
| def __init__(self, relation, sentence: "Sentence", sentence_offsets: bool = False): | |
| start1, end1, start2, end2, label = relation | |
| self.pair = (Span(start1, end1, sentence, sentence_offsets), | |
| Span(start2, end2, sentence, sentence_offsets)) | |
| self.label = label | |
| def __repr__(self): | |
| return f"{self.pair[0]!r}, {self.pair[1]!r}: {self.label}" | |
| def __eq__(self, other): | |
| return self.pair == other.pair and self.label == other.label | |
| def to_json(self): | |
| return list(self.pair[0].span_doc) + list(self.pair[1].span_doc) + [self.label] | |
| class PredictedRelation(Relation): | |
| def __init__(self, relation, sentence, sentence_offsets: bool = False): | |
| """`relation` = [start1, end1, start2, end2, label, raw_score, softmax_score].""" | |
| super().__init__(relation[:5], sentence, sentence_offsets) | |
| self.raw_score = relation[5] | |
| self.softmax_score = relation[6] | |
| def to_json(self): | |
| return super().to_json() + [_format_float(self.raw_score), _format_float(self.softmax_score)] | |
| class Sentence: | |
| """Despite the name, this project's documents always have exactly one "sentence" spanning | |
| the whole report (see training/README.md); the multi-sentence machinery is kept because | |
| the JSONL format is naturally list-of-sentences and nothing is gained by special-casing it. | |
| """ | |
| def __init__(self, entry: dict, sentence_start: int, sentence_ix: int): | |
| self.sentence_start = sentence_start | |
| self.sentence_ix = sentence_ix | |
| self.text = entry["sentences"] | |
| self.text_joined = " ".join(self.text) # memoized once; see Span.__init__ | |
| self.metadata = {k: v for k, v in entry.items() if k.startswith("_")} | |
| if "ner" in entry: | |
| self.ner = [NER(x, self) for x in entry["ner"]] | |
| self.ner_dict = {e.span.span_sent: e.label for e in self.ner} | |
| else: | |
| self.ner, self.ner_dict = None, None | |
| self.predicted_ner = ([PredictedNER(x, self) for x in entry["predicted_ner"]] | |
| if "predicted_ner" in entry else None) | |
| if "relations" in entry: | |
| self.relations = [Relation(x, self) for x in entry["relations"]] | |
| self.relation_dict = {(r.pair[0].span_sent, r.pair[1].span_sent): r.label | |
| for r in self.relations} | |
| else: | |
| self.relations, self.relation_dict = None, None | |
| self.predicted_relations = ([PredictedRelation(x, self) for x in entry["predicted_relations"]] | |
| if "predicted_relations" in entry else None) | |
| def to_json(self): | |
| res = {"sentences": self.text} | |
| if self.ner is not None: | |
| res["ner"] = [e.to_json() for e in self.ner] | |
| if self.predicted_ner is not None: | |
| res["predicted_ner"] = [e.to_json() for e in self.predicted_ner] | |
| if self.relations is not None: | |
| res["relations"] = [r.to_json() for r in self.relations] | |
| if self.predicted_relations is not None: | |
| res["predicted_relations"] = [r.to_json() for r in self.predicted_relations] | |
| res.update(self.metadata) | |
| return res | |
| def __len__(self): | |
| return len(self.text) | |
| def __repr__(self): | |
| return " ".join(self.text) | |
| class Document: | |
| _ALLOWED_FIELDS = re.compile(r"doc_key|dataset|sentences|weight|.*ner$|.*relations$|^_.*") | |
| def __init__(self, doc_key, dataset, sentences: List[Sentence], weight: Optional[float] = None): | |
| self.doc_key = doc_key | |
| self.dataset = dataset | |
| self.sentences = sentences | |
| self.weight = weight | |
| def from_json(cls, js: Dict[str, Any]) -> "Document": | |
| unexpected = [f for f in js if not cls._ALLOWED_FIELDS.match(f)] | |
| if unexpected: | |
| raise ValueError(f"Unexpected fields (prefix with '_' if intentional): {unexpected}") | |
| doc_key = js["doc_key"] | |
| dataset = js.get("dataset") | |
| entries = _fields_to_batches(js, ("doc_key", "dataset", "weight")) | |
| sentence_lengths = [len(e["sentences"]) for e in entries] | |
| sentence_starts = np.roll(np.cumsum(sentence_lengths), 1) | |
| sentence_starts[0] = 0 | |
| sentences = [Sentence(entry, int(start), ix) | |
| for ix, (entry, start) in enumerate(zip(entries, sentence_starts.tolist()))] | |
| return cls(doc_key, dataset, sentences, js.get("weight")) | |
| def to_json(self): | |
| res = {"doc_key": self.doc_key, "dataset": self.dataset} | |
| res.update(_batches_to_fields([s.to_json() for s in self.sentences])) | |
| if self.weight is not None: | |
| res["weight"] = self.weight | |
| return res | |
| def n_tokens(self): | |
| return sum(len(s) for s in self.sentences) | |
| def __getitem__(self, ix): | |
| return self.sentences[ix] | |
| def __len__(self): | |
| return len(self.sentences) | |
| def __repr__(self): | |
| return "\n".join(f"{i}: {' '.join(s.text)}" for i, s in enumerate(self.sentences)) | |
| class Dataset: | |
| def __init__(self, documents: List[Document]): | |
| self.documents = documents | |
| def __getitem__(self, i): | |
| return self.documents[i] | |
| def __len__(self): | |
| return len(self.documents) | |
| def from_jsonl(cls, fname): | |
| documents = [] | |
| with open(fname) as f: | |
| for line in f: | |
| documents.append(Document.from_json(json.loads(line))) | |
| return cls(documents) | |
| def to_jsonl(self, fname): | |
| with open(fname, "w") as f: | |
| for doc in self.documents: | |
| print(json.dumps(doc.to_json()), file=f) | |