Minifigures commited on
Commit
af5c2df
·
verified ·
1 Parent(s): d1a69de

fix: DICOM-aware vision prep with fallback degradation

Browse files
Files changed (1) hide show
  1. app/llm/stages/stage1_diagnostic.py +31 -2
app/llm/stages/stage1_diagnostic.py CHANGED
@@ -48,11 +48,35 @@ def _fallback_reason(exc: Exception) -> str:
48
  return "llm_error"
49
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  def _encode_image_b64(image_path: Path) -> str:
52
  """Downscale to <= MAX_IMAGE_LONG_EDGE on the long edge, re-encode JPEG in memory."""
53
  from PIL import Image
54
 
55
- with Image.open(image_path) as opened:
56
  image = opened.copy() if opened.mode in ("L", "RGB") else opened.convert("RGB")
57
  long_edge = max(image.size)
58
  if long_edge > MAX_IMAGE_LONG_EDGE:
@@ -101,7 +125,12 @@ def generate_diagnostic_report(
101
  if not llm_client.llm_available(settings):
102
  raise llm_client.LLMUnavailableError("anthropic API key missing or SDK not installed")
103
  system = _format_system(prompt.text, analysis)
104
- image_b64 = _encode_image_b64(image_path)
 
 
 
 
 
105
  context = (
106
  f"Claim #{claim_id}. Declared modality: {declared_modality or 'not declared'}. "
107
  f"Original upload media type: {image_media_type}. "
 
48
  return "llm_error"
49
 
50
 
51
+ def _load_image(image_path: Path):
52
+ """Open an upload for vision encoding; DICOM studies render via their pixel data."""
53
+ from PIL import Image
54
+
55
+ with image_path.open("rb") as fh:
56
+ head = fh.read(132)
57
+ is_dicom = (len(head) >= 132 and head[128:132] == b"DICM") or image_path.suffix.lower() in (
58
+ ".dcm",
59
+ ".dicom",
60
+ )
61
+ if is_dicom:
62
+ import pydicom
63
+
64
+ ds = pydicom.dcmread(image_path, force=True)
65
+ arr = ds.pixel_array.astype("float32")
66
+ lo, hi = float(arr.min()), float(arr.max())
67
+ arr = (arr - lo) / (hi - lo) * 255.0 if hi > lo else arr * 0.0
68
+ arr8 = arr.astype("uint8")
69
+ if arr8.ndim == 3: # color or multi-frame: first plane
70
+ arr8 = arr8[..., 0] if arr8.shape[-1] in (3, 4) else arr8[0]
71
+ return Image.fromarray(arr8)
72
+ return Image.open(image_path)
73
+
74
+
75
  def _encode_image_b64(image_path: Path) -> str:
76
  """Downscale to <= MAX_IMAGE_LONG_EDGE on the long edge, re-encode JPEG in memory."""
77
  from PIL import Image
78
 
79
+ with _load_image(image_path) as opened:
80
  image = opened.copy() if opened.mode in ("L", "RGB") else opened.convert("RGB")
81
  long_edge = max(image.size)
82
  if long_edge > MAX_IMAGE_LONG_EDGE:
 
125
  if not llm_client.llm_available(settings):
126
  raise llm_client.LLMUnavailableError("anthropic API key missing or SDK not installed")
127
  system = _format_system(prompt.text, analysis)
128
+ try:
129
+ image_b64 = _encode_image_b64(image_path)
130
+ except Exception as exc:
131
+ # Whatever the upload turned out to be, vision prep must degrade to
132
+ # the deterministic fallback, never fail the stage.
133
+ raise llm_client.LLMUnavailableError(f"image preparation failed: {exc}") from exc
134
  context = (
135
  f"Claim #{claim_id}. Declared modality: {declared_modality or 'not declared'}. "
136
  f"Original upload media type: {image_media_type}. "