cxr-report-generation / src /prompts.py
adhisetiawan's picture
Sync latest application changes
21245a4
Raw
History Blame Contribute Delete
14.3 kB
from __future__ import annotations
from src.schemas.detection import Finding, LocalizedFinding
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _pct(v: int) -> str:
return f"{v / 10:.0f}%"
def _bbox_description(box_2d: list[int]) -> str:
"""Convert [y0, x0, y1, x1] in [0,1000] to a human-readable region string."""
y0, x0, y1, x1 = box_2d
w = (x1 - x0) / 10
h = (y1 - y0) / 10
return (
f"region top {_pct(y0)} – bottom {_pct(y1)}, "
f"left {_pct(x0)} – right {_pct(x1)} "
f"(spans {w:.0f}% wide Γ— {h:.0f}% tall of image)"
)
FINDING_DISCOVERY_PROMPT = """Instructions:
Review all provided chest X-ray images from the same radiographic study and identify visible positive abnormal radiographic findings.
Study interpretation rules:
1. All provided images belong to the same patient and the same radiographic study.
2. Treat all provided images as complementary views of one examination.
3. Integrate visual evidence across all provided views.
4. Do not treat each image as a separate patient or separate study.
5. Return one consolidated study-level list of findings.
6. Do not duplicate the same finding only because it is visible in multiple views.
7. A finding does not need to be equally visible in every view.
8. If a finding is clearly visible or reasonably supported in at least one provided view, it may be included even when it is subtle, obscured, or not confidently visible in another view.
9. Use additional views as complementary evidence, not as a veto against a finding visible in one view.
10. Analyze image evidence only.
11. Do not assume access to a radiology report, prior report, patient history, laboratory data, or clinical diagnosis.
12. The provided images are different views from the same examination and are not longitudinal studies.
13. Do not infer temporal descriptions such as "new", "increased", "recurrent", "improved", or "worsened" unless actual prior-study images are explicitly supplied separately.
Finding rules:
1. Do not generate a radiology report.
2. Do not provide a final clinical diagnosis.
3. Prefer radiographic finding terminology over disease diagnosis terminology.
4. Examples of preferred findings include:
- pneumothorax
- pleural effusion
- focal airspace opacity
- consolidation
- atelectatic opacity
- pulmonary nodule
- hilar enlargement
- visible fracture
5. Include only positive or suspected visible abnormal findings.
6. Do not include normal anatomical observations.
7. Do not include negative findings such as:
- no pneumothorax
- no pleural effusion
- lungs are clear
- heart size is normal
8. For "certainty", use exactly one of:
- positive
- probable
- questionable
9. "Left" and "right" refer to the patient's anatomical left and right.
10. Keep the anatomical location concise and image-grounded.
11. If no positive abnormal finding is visible across the study, return:
{"findings":[]}
STRICT OUTPUT CONTRACT:
Your entire response MUST be exactly one raw JSON object.
The first non-whitespace character MUST be "{"
and the last non-whitespace character MUST be "}".
Do not output:
- reasoning
- analysis
- explanations
- headings
- markdown
- code fences
- "FINDINGS:"
- "Consolidated list of findings:"
- text before the JSON
- text after the JSON
Do not return a top-level JSON list.
Incorrect output:
[
{
"finding": "nodule",
"anatomical_location": "right upper lobe",
"certainty": "positive"
}
]
Correct output:
{
"findings": [
{
"finding": "nodule",
"anatomical_location": "right upper lobe",
"certainty": "positive"
}
]
}
Return exactly this schema:
{
"findings": [
{
"finding": "radiographic finding",
"anatomical_location": "concise anatomical location",
"certainty": "positive"
}
]
}
Review all provided views and return only the raw JSON object.
"""
def build_localization_prompt(finding: Finding) -> str:
return f"""Instructions:
You are given one chest X-ray image and one target radiographic finding that
was identified during a study-level review.
Target finding:
"{finding.finding}"
Reported anatomical location:
"{finding.anatomical_location}"
Your task is to localize the VISIBLE IMAGE REGION corresponding specifically
to the target radiographic finding in this single image.
IMPORTANT STUDY-LEVEL CONTEXT:
The target finding may have been identified using additional chest X-ray views
from the same radiographic study.
You are now examining ONE specific view only.
The finding does not have to be visible in this view.
If the target finding is not visually identifiable in the current image,
return an empty JSON list:
[]
CRITICAL LOCALIZATION RULES:
1. A bounding box must represent a visually identifiable instance of the
target radiographic finding.
2. Anatomical proximity alone is NOT sufficient visual evidence.
3. Do not return a box merely because the expected anatomical structure is
visible.
4. Do not box the entire lung, lobe, hemithorax, shoulder, mediastinum,
cardiac silhouette, or other broad anatomical region unless the target
abnormal visual pattern is genuinely diffuse throughout that region.
5. Do not substitute another abnormality for the requested target.
6. Do not infer a bounding box from the finding name alone.
7. If you cannot identify a visually supportable target region, return [].
8. It is preferable to return [] rather than provide a speculative,
approximate, anatomy-only, or forced bounding box.
9. Do not force a localization merely because the target finding was identified
at the study level.
10. The bounding box must be as tight as reasonably possible around the
visible abnormal finding.
FOCAL FINDING RULES:
For focal findings such as:
- pulmonary nodule
- nodular opacity
- focal opacity
- fracture
- focal consolidation
the bounding box should tightly surround the focal abnormality.
Do not return a large box covering the expected anatomical region.
DIFFUSE FINDING RULES:
For findings that may be spatially diffuse, such as:
- pleural effusion
- diffuse opacity
- atelectatic change
a larger bounding box is allowed only when the abnormal visual pattern itself
is visibly distributed throughout the boxed region.
LEFT/RIGHT RULE:
"Left" and "right" always refer to the patient's anatomical left and right,
not the displayed image side.
BOUNDING BOX FORMAT:
Use:
[y0, x0, y1, x1]
where:
(y0, x0) = top-left
(y1, x1) = bottom-right
Normalize every coordinate to:
[0, 1000]
The coordinates refer to the exact square-padded image supplied with this
query.
OUTPUT CONTRACT:
Return raw JSON only.
If the target is visually localizable:
[
{{
"box_2d": [y0, x0, y1, x1],
"label": "{finding.finding}"
}}
]
If the target is not visually localizable:
[]
Do not output:
- reasoning
- explanation
- headings
- markdown
- code fences
- text before JSON
- text after JSON
Localize the target only when it is visually supported in this image.
"""
def build_report_prompt(
findings: list[LocalizedFinding],
masks: list | None = None,
) -> str:
# Build a lookup: (finding_label, image_index) β†’ MaskResult
mask_map: dict[tuple[str, int], object] = {}
for m in (masks or []):
mask_map[(m.finding_label, m.image_index)] = m
_VIEW_LABEL = {0: "PA", 1: "Lateral"}
if not findings:
findings_text = "No positive abnormal radiographic findings were detected."
else:
blocks: list[str] = []
for idx, f in enumerate(findings, 1):
block: list[str] = [
f"Finding {idx}: {f.finding}",
f" Certainty : {f.certainty.title()}",
f" Anatomical region : {f.anatomical_location}",
]
for v in f.localizations:
view_name = _VIEW_LABEL.get(v.image_index, f"View {v.image_index}")
prefix = f" View {v.image_index} ({view_name})"
if v.status == "localized" and v.boxes:
bbox_desc = _bbox_description(v.boxes[0].box_2d)
block.append(f"{prefix} β€” Localized | {bbox_desc}")
# Stage 2 segmentation detail
seg = mask_map.get((f.finding, v.image_index))
if seg and getattr(seg, "status", None) == "success":
poly = getattr(seg, "polygon", None)
n_pts = len(poly) if poly else 0
# Polygon bounding box β†’ approximate segmented area
if poly and n_pts >= 3:
xs = [p[0] for p in poly]
ys = [p[1] for p in poly]
# Normalise to [0,1000] the same as bbox
# polygon coords are in original image pixels;
# use the bbox as a reference for relative extent
y0, x0, y1, x1 = v.boxes[0].box_2d
bbox_w = max(x1 - x0, 1)
bbox_h = max(y1 - y0, 1)
block.append(
f"{'':12} Segmentation (Stage 2): pixel-level mask captured β€” "
f"{n_pts} boundary points, "
f"located within the {_pct(x1-x0)} Γ— {_pct(y1-y0)} bounding region"
)
else:
block.append(
f"{'':12} Segmentation (Stage 2): completed (mask captured)"
)
else:
block.append(
f"{'':12} Segmentation (Stage 2): not performed for this view"
)
elif v.status == "abstained":
block.append(f"{prefix} β€” Finding not visible in this view")
elif v.status == "rejected_by_quality_gate":
bbox_desc = ""
if v.candidate_boxes:
bbox_desc = f" | candidate region: {_bbox_description(v.candidate_boxes[0].box_2d)}"
block.append(
f"{prefix} β€” Detected but spatial localization uncertain{bbox_desc}"
)
else:
block.append(f"{prefix} β€” {v.status}")
blocks.append("\n".join(block))
findings_text = "\n\n".join(blocks)
return f"""You are an expert radiologist reviewing a chest X-ray study.
The following radiographic findings were automatically detected:
{findings_text}
Based on the provided chest X-ray image(s) and the quantitative analysis data above \
(spatial bounding regions, segmentation coverage), write a professional, concise, and \
clinically accurate structured radiology report.
Apply standard radiology reporting standards to each field below:
- "study_type": Identify the imaging modality and views \
(e.g. "Chest X-Ray PA View", "Chest X-Ray PA and Lateral Views").
- "summary": One concise sentence summarizing the overall study result.
- "main_findings": JSON array of strings. \
List each key abnormal finding briefly, as you would name them in a FINDINGS \
section header. Use ["No significant abnormalities detected"] if the study \
is unremarkable.
- "detail_findings": JSON array of strings. \
For each finding, write a detailed radiological description β€” one string per finding. \
Use the bounding region percentages and segmentation data provided above to describe \
the precise location, extent, and distribution of each abnormality \
(e.g. occupying the lower-third of the left hemithorax). \
Translate percentage coordinates into anatomical terms \
(e.g. "top 55% to 85% left 10% to 45%" β†’ "lower left hemithorax extending to the \
costophrenic angle"). \
Include appearance, severity, and any multi-view corroboration. \
If segmentation was performed, reference the extent of the segmented region. \
If no findings, state that the study is unremarkable.
- "impression": Summarize the key findings and their clinical significance \
in 1-3 sentences β€” written as you would in an IMPRESSION section.
- "recommendations": Suggested clinical follow-up, correlation with history, \
or additional imaging if warranted (string).
- "additional_informations": Any other relevant radiological observations, \
incidental findings, or technical notes not covered above. \
Use "None" if not applicable.
Output STRICTLY a single JSON object with EXACT keys:
"study_type", "summary", "main_findings", "detail_findings",
"impression", "recommendations", "additional_informations"
Example output:
{{
"study_type": "Chest X-Ray PA and Lateral Views",
"summary": "The study demonstrates a moderate right pleural effusion with \
associated right lower lobe atelectasis.",
"main_findings": [
"Moderate right pleural effusion",
"Right lower lobe atelectasis"
],
"detail_findings": [
"A moderate right pleural effusion is present, blunting the right \
costophrenic angle and tracking along the lateral chest wall.",
"Right lower lobe atelectatic change is noted, likely compressive \
in etiology secondary to the adjacent pleural effusion."
],
"impression": "Moderate right pleural effusion with associated compressive \
right lower lobe atelectasis. No pneumothorax or focal consolidation \
identified. Clinical correlation is recommended.",
"recommendations": "Correlation with clinical history and laboratory \
findings. Consider diagnostic or therapeutic thoracentesis if clinically \
indicated. Repeat chest radiograph following intervention to assess response.",
"additional_informations": "Cardiac silhouette is at the upper limit of \
normal in size. No acute osseous abnormality identified."
}}
STRICT OUTPUT CONTRACT:
Use double quotes for all keys and string values.
No markdown, no prose, no code fences, no text before or after the JSON.
The first character of your output MUST be "{{" and the last MUST be "}}".
"""