dxlorhuggingface commited on
Commit
1a15dde
Β·
verified Β·
1 Parent(s): a26223a

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +226 -151
main.py CHANGED
@@ -1,154 +1,229 @@
1
- from flask import Flask, request, jsonify
2
- from flask_cors import CORS
3
- from PIL import Image, ImageDraw
4
- import io
5
- import base64
6
- import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- from model import predict
9
-
10
- app = Flask(__name__)
11
- CORS(app)
12
-
13
- # global variables
14
- threshold = 0.02
15
- diagnosis = ''
16
- pc_source = False
17
-
18
- @app.route('/api/process-annotation', methods=['POST'])
19
- def process_annotation():
20
  try:
21
- if request.content_type and request.content_type.startswith('multipart/form-data'):
22
- source = request.form.get('source', 'telegram')
23
- else:
24
- data = request.get_json()
25
- source = data.get('source', 'pc') if data else 'pc'
26
-
27
- if source == "pc":
28
- global pc_source
29
- pc_source = True
30
-
31
- if not data:
32
- return jsonify({"error": "No JSON data provided"}), 400
33
- img_data = data.get('image')
34
- bbox = data.get('coordinates')
35
- category = data.get('category')
36
- view = data.get('view')
37
-
38
- header, encoded = img_data.split(',', 1)
39
- img_bytes = base64.b64decode(encoded)
40
- img = Image.open(io.BytesIO(img_bytes))
41
-
42
- cropped = img.crop((
43
- bbox['x'],
44
- bbox['y'],
45
- bbox['x'] + bbox['width'],
46
- bbox['y'] + bbox['height']
47
- ))
48
-
49
- print(f"\nParameters:[Category-{category}, View-{view}, Annotations-{bbox}]\n")
50
- print("Processing...\n")
51
- time.sleep(2)
52
-
53
- error = predict(cropped, view, category)
54
- label = get_label(error)
55
- confidence = 100 - (100 * float(error))
56
- diagnosis = build_diagnosis(error, confidence)
57
-
58
- draw = ImageDraw.Draw(img)
59
- draw.rectangle(
60
- [bbox['x'], bbox['y'], bbox['x'] + bbox['width'], bbox['y'] + bbox['height']],
61
- outline="red", width=3
62
- )
63
-
64
- buffered = io.BytesIO()
65
- cropped.save(buffered, format="PNG")
66
- processed_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
67
- processed_data_url = f"data:image/png;base64,{processed_b64}"
68
-
69
- print(f"Results: Error-{error}, Comment-{label}, Confidence-{confidence}, Diagnosis-{diagnosis}\n")
70
- print(f"Rendering results to: {source} \n")
71
-
72
- return jsonify({
73
- "processed_image": processed_data_url,
74
- "category": category,
75
- "comment": label,
76
- "error": error,
77
- "confidence": confidence,
78
- "threshold": threshold,
79
- "diagnosis": diagnosis
80
- })
81
-
82
- elif source == "telegram":
83
- category = request.form.get('category')
84
- view = request.form.get('view')
85
- source_field = request.form.get('source')
86
- image_file = request.files.get('image')
87
-
88
- if not image_file:
89
- return jsonify({"error": "No image file provided"}), 400
90
-
91
- if not category or not view:
92
- return jsonify({"error": "Missing category or view"}), 400
93
-
94
- try:
95
- img = Image.open(image_file)
96
- if img.mode != 'RGB':
97
- img = img.convert('RGB')
98
- except Exception as e:
99
- return jsonify({"error": f"Invalid image file: {str(e)}"}), 400
100
-
101
- print(f"\nParameters:[Category-{category}, View-{view}, Image size-{img.size}]\n")
102
- print("Processing...\n")
103
-
104
- error = predict(img, view, category)
105
- label = get_label(error)
106
- confidence = 100 - (100 * float(error))
107
- diagnosis = build_diagnosis(error, confidence)
108
-
109
- buffered = io.BytesIO()
110
- img.save(buffered, format="PNG")
111
- processed_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
112
- processed_data_url = f"data:image/png;base64,{processed_b64}"
113
-
114
- response_data = {
115
- "processed_image": processed_data_url,
116
- "category": category,
117
- "view": view,
118
- "comment": label,
119
- "error": error,
120
- "confidence": confidence,
121
- "threshold": threshold,
122
- "diagnosis": diagnosis,
123
- "source": "telegram"
124
- }
125
-
126
- print(f"Results: Error-{error}, Comment-{label}, Confidence-{confidence}, Diagnosis-{diagnosis}\n")
127
- print(f"Rendering results to: {source} \n")
128
-
129
- return jsonify(response_data)
130
-
131
- else:
132
- return jsonify({"error": "Unsupported source"}), 400
133
-
134
  except Exception as e:
135
- print(f"Unexpected error: {e}")
136
- return jsonify({"error": str(e)}), 500
137
-
138
- def get_label(error):
139
- return "⚠️ Anomaly detected" if error > threshold else "βœ… Normal structure"
140
-
141
- def build_diagnosis(error, confidence):
142
- if confidence < 100 and confidence > 98 and error > threshold:
143
- diagnosis = 'High probability that there is an anomaly in the structure.'
144
- elif confidence < 98 and confidence > 96 and error > threshold:
145
- diagnosis = 'My analysis concludes there could be an anomaly in the structure, but either the structure is not well annotated or the image could be distorted, small, unclear or bad hence the uncertainty.'
146
- elif confidence < 96 and confidence > 92 and error > threshold:
147
- diagnosis = 'High possibility that anomaly is false positive and image is greatly distorted or irrelevant. Please check the image or the annotation and try again. If results are same, consult an expert.'
148
- elif confidence < 92 and error > threshold:
149
- diagnosis = 'Please upload a good ultrasound scan to obtain diagnosis. I cannot recognize the image nor the outlined structure.'
150
- elif confidence < 100 and confidence > 98 and error < threshold:
151
- diagnosis = 'Healthy structure detected. Annotation is correct OR model partially detects healthy area.'
152
-
153
- diagnosis += " THIS IS NOT PROFESSIONAL MEDICAL ADVICE. LIASE WITH AN EXPERT"
154
- return diagnosis
 
 
 
 
 
1
+ import logging
2
+ import aiohttp
3
+ import os,time
4
+
5
+ from aiohttp import FormData
6
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
7
+
8
+ from telegram.ext import (
9
+ Application, CommandHandler, MessageHandler,
10
+ filters, CallbackQueryHandler, ContextTypes
11
+ )
12
+
13
+ from dotenv import load_dotenv
14
+ load_dotenv(dotenv_path='env.env')
15
+
16
+
17
+ #config
18
+ TOKEN = "8320924107:AAH505mhHkOxeY3aLk0GObIpO_KCtY9hhLM"
19
+ API_ENDPOINT = os.getenv("API_ENDPOINT")
20
+ logging.basicConfig(level=logging.INFO)
21
+
22
+ #category mappings
23
+ VIEW_CATEGORIES = {
24
+ "crl": {
25
+ "Maxilla": "mx",
26
+ "Mandible-MDS": "mds",
27
+ "Mandible-MLS": "mls",
28
+ "Lateral ventricle": "lv",
29
+ "Head": "head",
30
+ "Gestational sac": "gsac",
31
+ "Thorax": "thorax",
32
+ "Abdomen": "ab",
33
+ "Body(Biparietal diameter)": "bd",
34
+ "Rhombencephalon": "rbp",
35
+ "Diencephalon": "dp",
36
+ "NTAPS": "ntaps",
37
+ "Nasal bone": "nb"
38
+ },
39
+ "nt": {
40
+ "Maxilla": "mx",
41
+ "Mandible-MDS": "mds",
42
+ "Mandible-MLS": "mls",
43
+ "Lateral ventricle": "lv",
44
+ "Head": "head",
45
+ "Thorax": "thorax",
46
+ "Abdomen": "ab",
47
+ "Rhombencephalon": "rbp",
48
+ "Diencephalon": "dp",
49
+ "Nuchal translucency": "nt",
50
+ "NTAPS": "ntaps",
51
+ "Nasal bone": "nb"
52
+ }
53
+ }
54
+
55
+ #start
56
+ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
57
+ await update.message.reply_text("Send an ultrasound image (JPG/png only) to begin. See /instructions first")
58
+
59
+ async def instructions(update: Update, context: ContextTypes.DEFAULT_TYPE):
60
+ instruction_message = """
61
+ πŸ“‹ **Instructions**
62
+
63
+ πŸ”Έ **Step 1:** Send a cropped ultrasound image of a structure you want to analyze. See all structures with /list command
64
+ πŸ”Έ **Step 2:** Select the ultrasound view (CRL or NT)
65
+ πŸ”Έ **Step 3:** Choose the anatomical category to analyze
66
+ πŸ”Έ **Step 4:** Wait for the AI analysis results
67
+
68
+ πŸ“Œ **Important notes:**
69
+ β€’ Only JPG/PNG images are supported
70
+ β€’ Ensure the ultrasound image is clear and properly oriented
71
+ β€’ Results are for reference only - always consult a medical professional
72
+ β€’ Processing may take a few seconds
73
+ β€’ Stop bot with /stop
74
+
75
+ πŸ’‘ **Tips:**
76
+ β€’ Use high-quality, well-lit images for better accuracy
77
+ β€’ Make sure the anatomical structure is clearly visible
78
+ β€’ Different views (CRL/NT) have different category options
79
+
80
+ πŸ†˜ **Need help?** Contact support if you encounter any issues @d3ikshr.
81
+ """
82
+
83
+ await update.message.reply_text(instruction_message, parse_mode='Markdown')
84
+
85
+ async def list_categories(update: Update, context: ContextTypes.DEFAULT_TYPE):
86
+ list_message = """
87
+ πŸ“‹ **Available Categories by View**
88
+
89
+ πŸ” **CRL view categories:**
90
+ β€’ Maxilla β€’ Mandible-MDS β€’ Mandible-MLS
91
+ β€’ Lateral ventricle β€’ Head β€’ Gestational sac
92
+ β€’ Thorax β€’ Abdomen β€’ Body(Biparietal diameter)
93
+ β€’ Rhombencephalon β€’ Diencephalon β€’ NTAPS
94
+ β€’ Nasal bone
95
+
96
+ πŸ” **NT view categories:**
97
+ β€’ Maxilla β€’ Mandible-MDS β€’ Mandible-MLS
98
+ β€’ Lateral ventricle β€’ Head β€’ Thorax
99
+ β€’ Abdomen β€’ Rhombencephalon β€’ Diencephalon
100
+ β€’ Nuchal translucency β€’ NTAPS β€’ Nasal bone
101
+
102
+ πŸ’‘ **Note:** Categories will be shown automatically based on your selected view during analysis.
103
+ """
104
+
105
+ await update.message.reply_text(list_message, parse_mode='Markdown')
106
+
107
+ async def stop(update: Update, context: ContextTypes.DEFAULT_TYPE):
108
+ await update.message.reply_text("πŸ›‘ Bot stopped for this chat. Use /start to begin again.")
109
+ # Clear user data
110
+ context.user_data.clear()
111
+
112
+ # Receive image
113
+ async def handle_image(update: Update, context: ContextTypes.DEFAULT_TYPE):
114
+ photo = update.message.photo[-1]
115
+ file = await photo.get_file()
116
+ file_path = f"{update.message.from_user.id}_ultrasound.jpg"
117
+ await file.download_to_drive(file_path)
118
+ context.user_data["image_path"] = file_path
119
+
120
+ # get view
121
+ buttons = [
122
+ [InlineKeyboardButton("CRL", callback_data="view:crl"),
123
+ InlineKeyboardButton("NT", callback_data="view:nt")]
124
+ ]
125
+ await update.message.reply_text(
126
+ "Select the ultrasound view:",
127
+ reply_markup=InlineKeyboardMarkup(buttons)
128
+ )
129
+
130
+ #selcet view
131
+ async def handle_view(update: Update, context: ContextTypes.DEFAULT_TYPE):
132
+ query = update.callback_query
133
+ await query.answer()
134
+ view = query.data.split(":")[1]
135
+ context.user_data["selected_view"] = view
136
+
137
+ #get categories for the selected view
138
+ categories = list(VIEW_CATEGORIES[view].keys())
139
+ buttons = [[InlineKeyboardButton(cat, callback_data=f"category:{cat}")]
140
+ for cat in categories]
141
+ await query.edit_message_text(
142
+ "Select anatomical category:",
143
+ reply_markup=InlineKeyboardMarkup(buttons)
144
+ )
145
+
146
+ #get category then upload
147
+ async def handle_category(update: Update, context: ContextTypes.DEFAULT_TYPE):
148
+ query = update.callback_query
149
+ await query.answer()
150
+ category_display = query.data.split(":")[1]
151
+ context.user_data["selected_category"] = category_display
152
+
153
+ image_path = context.user_data.get("image_path")
154
+ view = context.user_data.get("selected_view")
155
+
156
+ if not image_path or not view:
157
+ await query.edit_message_text("Missing image or view.")
158
+ return
159
+
160
+ await query.edit_message_text("πŸ”„ Processing image...")
161
+ time.sleep(3)
162
+ await query.edit_message_text("πŸ₯ Building diagnosis, please wait...")
163
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  try:
165
+ #read the image file into memory first
166
+ with open(image_path, "rb") as f:
167
+ image_data = f.read()
168
+
169
+ #mapping
170
+ category_value = VIEW_CATEGORIES[view][category_display]
171
+
172
+ #create form data
173
+ form = FormData()
174
+ form.add_field("view", view)
175
+ form.add_field("category", category_value)
176
+ form.add_field("source", "telegram")
177
+ form.add_field("image", image_data, filename="image.jpg", content_type="image/jpeg")
178
+
179
+ #send request
180
+ async with aiohttp.ClientSession() as session:
181
+ async with session.post(API_ENDPOINT, data=form) as resp:
182
+ if resp.status == 200:
183
+ result = await resp.json()
184
+
185
+ message = f"""
186
+ πŸ“Š **Analysis Results**
187
+
188
+ πŸ” **View:** {result.get('view', view).upper()}
189
+ πŸ₯ **Category:** {category_display}
190
+
191
+ πŸ“ˆ **Confidence:** {result.get('confidence', 0):.2f}%
192
+ ⚠️ **Reconstruction error:** {result.get('error', 0):.5f}
193
+
194
+ πŸ“‹ **Status:** {result.get('comment', 'No comment')}
195
+
196
+ 🩺 **Diagnosis:** {result.get('diagnosis', 'No diagnosis')}*
197
+ """
198
+
199
+ await query.edit_message_text(message, parse_mode='Markdown')
200
+
201
+ else:
202
+ error_text = await resp.text()
203
+ await query.edit_message_text(f"❌ Upload failed. Status: {resp.status}\nError: {error_text}")
204
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  except Exception as e:
206
+ logging.error(f"Error processing request: {e}")
207
+ await query.edit_message_text(f"❌ Error: {str(e)}")
208
+
209
+ finally:
210
+ try:
211
+ if os.path.exists(image_path):
212
+ os.remove(image_path)
213
+ logging.info(f"Cleaned up image file: {image_path}")
214
+ except Exception as e:
215
+ logging.error(f"Error cleaning up file: {e}")
216
+
217
+ def main():
218
+ app = Application.builder().token(TOKEN).build()
219
+ app.add_handler(CommandHandler("start", start))
220
+ app.add_handler(CommandHandler("instructions", instructions))
221
+ app.add_handler(CommandHandler("list", list_categories))
222
+ app.add_handler(CommandHandler("stop", stop))
223
+ app.add_handler(MessageHandler(filters.PHOTO, handle_image))
224
+ app.add_handler(CallbackQueryHandler(handle_view, pattern="^view:"))
225
+ app.add_handler(CallbackQueryHandler(handle_category, pattern="^category:"))
226
+ app.run_polling()
227
+
228
+ if __name__ == "__main__":
229
+ main()