Tantawi commited on
Commit
a80a31b
·
verified ·
1 Parent(s): 33d2427

Update lab_analyzer.py

Browse files
Files changed (1) hide show
  1. lab_analyzer.py +177 -177
lab_analyzer.py CHANGED
@@ -1,178 +1,178 @@
1
- import base64
2
- import os
3
- from huggingface_hub import InferenceClient
4
- import asyncio
5
- from typing import Dict, Any
6
- import logging
7
- from dotenv import load_dotenv
8
-
9
- # Load environment variables from .env file
10
- load_dotenv()
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- class LabReportAnalyzer:
15
- """Lab Report Analysis service using Hugging Face Inference Client"""
16
-
17
- def __init__(self):
18
- """Initialize the analyzer with Hugging Face client"""
19
- self.client = InferenceClient(
20
- provider="nebius",
21
- api_key=os.getenv("HUGGINGFACE_API_KEY", "your-api-key-here"),
22
- )
23
- self.model = "google/gemma-3-27b-it"
24
-
25
- async def analyze_report(self, image_b64: str) -> Dict[str, Any]:
26
- """
27
- Analyze a lab report image and return structured results
28
-
29
- Args:
30
- image_b64: Base64 encoded image string
31
-
32
- Returns:
33
- Dictionary containing structured analysis results
34
- """
35
- try:
36
- prompt = self._get_analysis_prompt()
37
-
38
- # Run the inference in a thread pool to avoid blocking
39
- loop = asyncio.get_event_loop()
40
- completion = await loop.run_in_executor(
41
- None,
42
- self._run_inference,
43
- image_b64,
44
- prompt
45
- )
46
-
47
- # Extract and parse the response
48
- analysis_text = completion.choices[0].message.content.strip()
49
-
50
- # Parse the structured response
51
- parsed_result = self._parse_analysis_result(analysis_text)
52
-
53
- return parsed_result
54
-
55
- except Exception as e:
56
- logger.error(f"Error in analyze_report: {str(e)}")
57
- return {
58
- "error": True,
59
- "message": f"Analysis failed: {str(e)}",
60
- "raw_response": ""
61
- }
62
-
63
- def _run_inference(self, image_b64: str, prompt: str):
64
- """Run the Hugging Face inference synchronously"""
65
- return self.client.chat.completions.create(
66
- model=self.model,
67
- messages=[
68
- {
69
- "role": "user",
70
- "content": [
71
- {"type": "text", "text": prompt},
72
- {
73
- "type": "image_url",
74
- "image_url": {
75
- "url": f"data:image/jpeg;base64,{image_b64}"
76
- }
77
- }
78
- ]
79
- }
80
- ],
81
- )
82
-
83
- def _get_analysis_prompt(self) -> str:
84
- """Get the structured analysis prompt"""
85
- return """
86
- You are a medical analysis assistant.
87
-
88
- Analyze the following lab report image and give a structured, professional summary
89
- following these steps:
90
-
91
- 1. Extract the results (with normal ranges if available).
92
- 2. Highlight abnormal values clearly.
93
- 3. Explain what the results suggest in simple terms.
94
- 4. Provide an overall summary of health findings.
95
- 5. End with the disclaimer:
96
- "This analysis is for educational purposes only and should not replace professional medical advice."
97
-
98
- If the image is unreadable, respond: "The image text is unclear."
99
-
100
- Format your response as follows:
101
-
102
- Summary: (2–3 sentences explaining what the report shows)
103
- Key Findings: (3–5 bullet points with main abnormal or notable values)
104
- Interpretation: (1–2 sentences explaining what the findings suggest)
105
- Note: (One line disclaimer that it's not medical advice)
106
-
107
- Keep it short, clear, and professional — like a medical summary written for quick review.
108
- """
109
-
110
- def _parse_analysis_result(self, analysis_text: str) -> Dict[str, Any]:
111
- """
112
- Parse the structured analysis result into a dictionary
113
-
114
- Args:
115
- analysis_text: Raw analysis text from the model
116
-
117
- Returns:
118
- Structured dictionary with parsed components
119
- """
120
- try:
121
- result = {
122
- "error": False,
123
- "summary": "",
124
- "key_findings": [],
125
- "interpretation": "",
126
- "note": "",
127
- "raw_response": analysis_text
128
- }
129
-
130
- # Check if image is unreadable
131
- if "The image text is unclear" in analysis_text:
132
- result["error"] = True
133
- result["message"] = "The image text is unclear or unreadable"
134
- return result
135
-
136
- lines = analysis_text.split('\n')
137
- current_section = None
138
-
139
- for line in lines:
140
- line = line.strip()
141
- if not line:
142
- continue
143
-
144
- # Identify sections (handle both plain text and markdown formats)
145
- if line.startswith('Summary:') or line.startswith('**Summary:**'):
146
- current_section = 'summary'
147
- result['summary'] = line.replace('**Summary:**', '').replace('Summary:', '').strip()
148
- elif line.startswith('Key Findings:') or line.startswith('**Key Findings:**'):
149
- current_section = 'key_findings'
150
- elif line.startswith('Interpretation:') or line.startswith('**Interpretation:**'):
151
- current_section = 'interpretation'
152
- result['interpretation'] = line.replace('**Interpretation:**', '').replace('Interpretation:', '').strip()
153
- elif line.startswith('Note:') or line.startswith('**Note:**'):
154
- current_section = 'note'
155
- result['note'] = line.replace('**Note:**', '').replace('Note:', '').strip()
156
- else:
157
- # Continue previous section
158
- if current_section == 'summary' and not result['summary']:
159
- result['summary'] = line
160
- elif current_section == 'key_findings' and (line.startswith(('•', '-', '*')) or line.strip().startswith('*')):
161
- # Handle both regular bullets and markdown-style bullets
162
- clean_line = line.lstrip('•-* ').strip()
163
- if clean_line:
164
- result['key_findings'].append(clean_line)
165
- elif current_section == 'interpretation' and not result['interpretation']:
166
- result['interpretation'] = line
167
- elif current_section == 'note' and not result['note']:
168
- result['note'] = line
169
-
170
- return result
171
-
172
- except Exception as e:
173
- logger.error(f"Error parsing analysis result: {str(e)}")
174
- return {
175
- "error": True,
176
- "message": f"Failed to parse analysis: {str(e)}",
177
- "raw_response": analysis_text
178
  }
 
1
+ import base64
2
+ import os
3
+ from huggingface_hub import InferenceClient
4
+ import asyncio
5
+ from typing import Dict, Any
6
+ import logging
7
+ from dotenv import load_dotenv
8
+
9
+ # Load environment variables from .env file
10
+ load_dotenv()
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class LabReportAnalyzer:
15
+ """Lab Report Analysis service using Hugging Face Inference Client"""
16
+
17
+ def __init__(self):
18
+ """Initialize the analyzer with Hugging Face client"""
19
+ self.client = InferenceClient(
20
+ token=os.getenv("HUGGINGFACE_API_KEY", "your-api-key-here"),
21
+
22
+ )
23
+ self.model = "google/gemma-3-27b-it"
24
+
25
+ async def analyze_report(self, image_b64: str) -> Dict[str, Any]:
26
+ """
27
+ Analyze a lab report image and return structured results
28
+
29
+ Args:
30
+ image_b64: Base64 encoded image string
31
+
32
+ Returns:
33
+ Dictionary containing structured analysis results
34
+ """
35
+ try:
36
+ prompt = self._get_analysis_prompt()
37
+
38
+ # Run the inference in a thread pool to avoid blocking
39
+ loop = asyncio.get_event_loop()
40
+ completion = await loop.run_in_executor(
41
+ None,
42
+ self._run_inference,
43
+ image_b64,
44
+ prompt
45
+ )
46
+
47
+ # Extract and parse the response
48
+ analysis_text = completion.choices[0].message.content.strip()
49
+
50
+ # Parse the structured response
51
+ parsed_result = self._parse_analysis_result(analysis_text)
52
+
53
+ return parsed_result
54
+
55
+ except Exception as e:
56
+ logger.error(f"Error in analyze_report: {str(e)}")
57
+ return {
58
+ "error": True,
59
+ "message": f"Analysis failed: {str(e)}",
60
+ "raw_response": ""
61
+ }
62
+
63
+ def _run_inference(self, image_b64: str, prompt: str):
64
+ """Run the Hugging Face inference synchronously"""
65
+ return self.client.chat.completions.create(
66
+ model=self.model,
67
+ messages=[
68
+ {
69
+ "role": "user",
70
+ "content": [
71
+ {"type": "text", "text": prompt},
72
+ {
73
+ "type": "image_url",
74
+ "image_url": {
75
+ "url": f"data:image/jpeg;base64,{image_b64}"
76
+ }
77
+ }
78
+ ]
79
+ }
80
+ ],
81
+ )
82
+
83
+ def _get_analysis_prompt(self) -> str:
84
+ """Get the structured analysis prompt"""
85
+ return """
86
+ You are a medical analysis assistant.
87
+
88
+ Analyze the following lab report image and give a structured, professional summary
89
+ following these steps:
90
+
91
+ 1. Extract the results (with normal ranges if available).
92
+ 2. Highlight abnormal values clearly.
93
+ 3. Explain what the results suggest in simple terms.
94
+ 4. Provide an overall summary of health findings.
95
+ 5. End with the disclaimer:
96
+ "This analysis is for educational purposes only and should not replace professional medical advice."
97
+
98
+ If the image is unreadable, respond: "The image text is unclear."
99
+
100
+ Format your response as follows:
101
+
102
+ Summary: (2–3 sentences explaining what the report shows)
103
+ Key Findings: (3–5 bullet points with main abnormal or notable values)
104
+ Interpretation: (1–2 sentences explaining what the findings suggest)
105
+ Note: (One line disclaimer that it's not medical advice)
106
+
107
+ Keep it short, clear, and professional — like a medical summary written for quick review.
108
+ """
109
+
110
+ def _parse_analysis_result(self, analysis_text: str) -> Dict[str, Any]:
111
+ """
112
+ Parse the structured analysis result into a dictionary
113
+
114
+ Args:
115
+ analysis_text: Raw analysis text from the model
116
+
117
+ Returns:
118
+ Structured dictionary with parsed components
119
+ """
120
+ try:
121
+ result = {
122
+ "error": False,
123
+ "summary": "",
124
+ "key_findings": [],
125
+ "interpretation": "",
126
+ "note": "",
127
+ "raw_response": analysis_text
128
+ }
129
+
130
+ # Check if image is unreadable
131
+ if "The image text is unclear" in analysis_text:
132
+ result["error"] = True
133
+ result["message"] = "The image text is unclear or unreadable"
134
+ return result
135
+
136
+ lines = analysis_text.split('\n')
137
+ current_section = None
138
+
139
+ for line in lines:
140
+ line = line.strip()
141
+ if not line:
142
+ continue
143
+
144
+ # Identify sections (handle both plain text and markdown formats)
145
+ if line.startswith('Summary:') or line.startswith('**Summary:**'):
146
+ current_section = 'summary'
147
+ result['summary'] = line.replace('**Summary:**', '').replace('Summary:', '').strip()
148
+ elif line.startswith('Key Findings:') or line.startswith('**Key Findings:**'):
149
+ current_section = 'key_findings'
150
+ elif line.startswith('Interpretation:') or line.startswith('**Interpretation:**'):
151
+ current_section = 'interpretation'
152
+ result['interpretation'] = line.replace('**Interpretation:**', '').replace('Interpretation:', '').strip()
153
+ elif line.startswith('Note:') or line.startswith('**Note:**'):
154
+ current_section = 'note'
155
+ result['note'] = line.replace('**Note:**', '').replace('Note:', '').strip()
156
+ else:
157
+ # Continue previous section
158
+ if current_section == 'summary' and not result['summary']:
159
+ result['summary'] = line
160
+ elif current_section == 'key_findings' and (line.startswith(('•', '-', '*')) or line.strip().startswith('*')):
161
+ # Handle both regular bullets and markdown-style bullets
162
+ clean_line = line.lstrip('•-* ').strip()
163
+ if clean_line:
164
+ result['key_findings'].append(clean_line)
165
+ elif current_section == 'interpretation' and not result['interpretation']:
166
+ result['interpretation'] = line
167
+ elif current_section == 'note' and not result['note']:
168
+ result['note'] = line
169
+
170
+ return result
171
+
172
+ except Exception as e:
173
+ logger.error(f"Error parsing analysis result: {str(e)}")
174
+ return {
175
+ "error": True,
176
+ "message": f"Failed to parse analysis: {str(e)}",
177
+ "raw_response": analysis_text
178
  }