sugatobagchi commited on
Commit
cce75df
Β·
verified Β·
1 Parent(s): 0ce4340

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +5 -14
  2. llm.py +36 -3
app.py CHANGED
@@ -38,18 +38,9 @@ CSS = """
38
 
39
  HERO = """
40
  <div id="hero">
41
- <h1>🩺 Chart Whisperer</h1>
42
- <p>Turn a raw clinical recording into a structured SOAP note. MedASR
43
- transcribes the conversation, MedGemma 4B drafts the note with speaker
44
- attribution β€” nothing is fabricated beyond what was actually said.</p>
45
- <div class="pills" style="margin-top: 10px;">
46
- <span class="pill">πŸŽ™οΈ MedASR</span>
47
- <span class="pill">πŸ€– MedGemma 4B</span>
48
- <span class="pill">⚑ ZeroGPU</span>
49
- </div>
50
- <p style="margin-top:14px; font-size:13px; opacity:.85;">Not a diagnostic
51
- tool β€” for demonstration/research use only. Don't upload real patient
52
- data.</p>
53
  </div>
54
  """
55
 
@@ -84,7 +75,7 @@ def clear_audio():
84
  return None, PLACEHOLDER_TRANSCRIPT, PLACEHOLDER_SOAP, PLACEHOLDER_TIMING
85
 
86
 
87
- with gr.Blocks(title="Chart Whisperer") as demo:
88
  gr.HTML(HERO)
89
 
90
  with gr.Row():
@@ -138,4 +129,4 @@ with gr.Blocks(title="Chart Whisperer") as demo:
138
  )
139
 
140
  if __name__ == "__main__":
141
- demo.launch(theme=THEME, css=CSS)
 
38
 
39
  HERO = """
40
  <div id="hero">
41
+ <h1>Medical Notes Using Medgemma</h1>
42
+ <p>Turn a raw clinical recording into a structured SOAP note β€” nothing is
43
+ fabricated beyond what was actually said.</p>
 
 
 
 
 
 
 
 
 
44
  </div>
45
  """
46
 
 
75
  return None, PLACEHOLDER_TRANSCRIPT, PLACEHOLDER_SOAP, PLACEHOLDER_TIMING
76
 
77
 
78
+ with gr.Blocks(title="Medical Notes Using Medgemma") as demo:
79
  gr.HTML(HERO)
80
 
81
  with gr.Row():
 
129
  )
130
 
131
  if __name__ == "__main__":
132
+ demo.launch(theme=THEME, css=CSS, footer_links=[])
llm.py CHANGED
@@ -1,6 +1,7 @@
1
  """Audio -> MedASR transcript -> MedGemma 4B SOAP note pipeline."""
2
 
3
  import os
 
4
  import time
5
  from dataclasses import dataclass
6
 
@@ -24,8 +25,40 @@ SYSTEM_PROMPT = (
24
 
25
  # Forced prefix for the assistant turn: with `continue_final_message=True`,
26
  # generation resumes mid-turn from this exact text, so there is no token
27
- # position left for a preamble or transcript restatement to occupy.
28
- SOAP_PREFIX = "SOAP Note:\n\n**S β€” Subjective:**"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  _asr_pipe = None
31
  _llm_model = None
@@ -101,7 +134,7 @@ def generate_soap_note(transcript: str) -> str:
101
 
102
  new_tokens = generated_ids[0][input_len:]
103
  completion = processor.decode(new_tokens, skip_special_tokens=True)
104
- return (SOAP_PREFIX + completion).strip()
105
 
106
 
107
  @dataclass
 
1
  """Audio -> MedASR transcript -> MedGemma 4B SOAP note pipeline."""
2
 
3
  import os
4
+ import re
5
  import time
6
  from dataclasses import dataclass
7
 
 
25
 
26
  # Forced prefix for the assistant turn: with `continue_final_message=True`,
27
  # generation resumes mid-turn from this exact text, so there is no token
28
+ # position left for a preamble or transcript restatement to occupy. Only
29
+ # the first header is guaranteed this way β€” the other three are generated
30
+ # freely and get normalized to match by _normalize_soap_note below.
31
+ SOAP_PREFIX = "S β€” Subjective:"
32
+
33
+ _SOAP_HEADERS = [
34
+ ("S", "Subjective"),
35
+ ("O", "Objective"),
36
+ ("A", "Assessment"),
37
+ ("P", "Plan"),
38
+ ]
39
+ _SOAP_TITLE_RE = re.compile(r"(?im)^[ \t]*\**[ \t]*SOAP Note[ \t]*:?\**[ \t]*\n+")
40
+ _SOAP_HEADER_RES = [
41
+ (
42
+ re.compile(
43
+ rf"(?im)^[ \t]*\**[ \t]*(?:{letter}[ \t]*[-β€”][ \t]*)?{word}[ \t]*:\**"
44
+ ),
45
+ f"{letter} β€” {word}:",
46
+ )
47
+ for letter, word in _SOAP_HEADERS
48
+ ]
49
+
50
+
51
+ def _normalize_soap_note(text: str) -> str:
52
+ """Force all four section headers to the same 'X β€” Word:' shape.
53
+
54
+ Only the first header is pinned via the forced assistant prefix; the
55
+ model is free to drift on the rest (e.g. writing 'Plan:' instead of
56
+ 'P β€” Plan:'), so headers are normalized here rather than trusted.
57
+ """
58
+ text = _SOAP_TITLE_RE.sub("", text.strip())
59
+ for pattern, canonical in _SOAP_HEADER_RES:
60
+ text = pattern.sub(canonical, text)
61
+ return text.strip()
62
 
63
  _asr_pipe = None
64
  _llm_model = None
 
134
 
135
  new_tokens = generated_ids[0][input_len:]
136
  completion = processor.decode(new_tokens, skip_special_tokens=True)
137
+ return _normalize_soap_note(SOAP_PREFIX + completion)
138
 
139
 
140
  @dataclass