File size: 7,928 Bytes
1a15dde
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86becbd
 
1a15dde
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86becbd
1a15dde
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import logging
import aiohttp
import os,time

from aiohttp import FormData
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup

from telegram.ext import (
    Application, CommandHandler, MessageHandler,
    filters, CallbackQueryHandler, ContextTypes
)

from dotenv import load_dotenv
load_dotenv(dotenv_path='env.env')


#config
TOKEN = "8320924107:AAH505mhHkOxeY3aLk0GObIpO_KCtY9hhLM"
API_ENDPOINT = os.getenv("API_ENDPOINT")
logging.basicConfig(level=logging.INFO)

#category mappings
VIEW_CATEGORIES = {
    "crl": {
        "Maxilla": "mx",
        "Mandible-MDS": "mds", 
        "Mandible-MLS": "mls",
        "Lateral ventricle": "lv",
        "Head": "head",
        "Gestational sac": "gsac",
        "Thorax": "thorax",
        "Abdomen": "ab",
        "Body(Biparietal diameter)": "bd",
        "Rhombencephalon": "rbp",
        "Diencephalon": "dp",
        "NTAPS": "ntaps",
        "Nasal bone": "nb"
    },
    "nt": {
        "Maxilla": "mx",
        "Mandible-MDS": "mds", 
        "Mandible-MLS": "mls",
        "Lateral ventricle": "lv",
        "Head": "head",
        "Thorax": "thorax",
        "Abdomen": "ab",
        "Rhombencephalon": "rbp",
        "Diencephalon": "dp",
        "Nuchal translucency": "nt",
        "NTAPS": "ntaps",
        "Nasal bone": "nb"
    }
}

#start
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text("Send an ultrasound image (JPG/png only) to begin. See /instructions first")

async def instructions(update: Update, context: ContextTypes.DEFAULT_TYPE):
    instruction_message = """
πŸ“‹ **Instructions**

πŸ”Έ **Step 1:** Send a cropped ultrasound image of a structure you want to analyze. See all structures with /list command
πŸ”Έ **Step 2:** Select the ultrasound view (CRL or NT)
πŸ”Έ **Step 3:** Choose the anatomical category to analyze
πŸ”Έ **Step 4:** Wait for the AI analysis results

πŸ“Œ **Important notes:**
β€’ Only JPG/PNG images are supported
β€’ Ensure the ultrasound image is clear and properly oriented
β€’ Results are for reference only - always consult a medical professional
β€’ Processing may take a few seconds
β€’ Stop bot with /stop

πŸ’‘ **Tips:**
β€’ Use high-quality, well-lit images for better accuracy
β€’ Make sure the anatomical structure is clearly visible
β€’ Different views (CRL/NT) have different category options

πŸ†˜ **Need help?** Contact support if you encounter any issues @d3ikshr.
    """
    
    await update.message.reply_text(instruction_message, parse_mode='Markdown')

async def list_categories(update: Update, context: ContextTypes.DEFAULT_TYPE):
    list_message = """
πŸ“‹ **Available Categories by View**

πŸ” **CRL view categories:**
β€’ Maxilla β€’ Mandible-MDS β€’ Mandible-MLS
β€’ Lateral ventricle β€’ Head β€’ Gestational sac
β€’ Thorax β€’ Abdomen β€’ Body(Biparietal diameter)
β€’ Rhombencephalon β€’ Diencephalon β€’ NTAPS
β€’ Nasal bone

πŸ” **NT view categories:**
β€’ Maxilla β€’ Mandible-MDS β€’ Mandible-MLS
β€’ Lateral ventricle β€’ Head β€’ Thorax
β€’ Abdomen β€’ Rhombencephalon β€’ Diencephalon
β€’ Nuchal translucency β€’ NTAPS β€’ Nasal bone

πŸ’‘ **Note:** Categories will be shown automatically based on your selected view during analysis.
    """
    
    await update.message.reply_text(list_message, parse_mode='Markdown')

async def stop(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text("πŸ›‘ Bot stopped for this chat. Use /start to begin again.")
    # Clear user data
    context.user_data.clear()

# Receive image
async def handle_image(update: Update, context: ContextTypes.DEFAULT_TYPE):
    photo = update.message.photo[-1]
    file = await photo.get_file()
    file_path = f"{update.message.from_user.id}_ultrasound.jpg"
    await file.download_to_drive(file_path)
    context.user_data["image_path"] = file_path
    
    # get view
    buttons = [
        [InlineKeyboardButton("CRL", callback_data="view:crl"),
         InlineKeyboardButton("NT", callback_data="view:nt")]
    ]
    await update.message.reply_text(
        "Select the ultrasound view:",
        reply_markup=InlineKeyboardMarkup(buttons)
    )

#selcet view
async def handle_view(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    view = query.data.split(":")[1]
    context.user_data["selected_view"] = view
    
    #get categories for the selected view
    categories = list(VIEW_CATEGORIES[view].keys())
    buttons = [[InlineKeyboardButton(cat, callback_data=f"category:{cat}")]
               for cat in categories]
    await query.edit_message_text(
        "Select anatomical category:",
        reply_markup=InlineKeyboardMarkup(buttons)
    )

#get category then upload
async def handle_category(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    category_display = query.data.split(":")[1]
    context.user_data["selected_category"] = category_display
    
    image_path = context.user_data.get("image_path")
    view = context.user_data.get("selected_view")
    
    if not image_path or not view:
        await query.edit_message_text("Missing image or view.")
        return

    await query.edit_message_text("πŸ”„ Processing image...")
    time.sleep(3)
    await query.edit_message_text("πŸ₯ Building diagnosis, please wait...")

    try:
        #read the image file into memory first
        with open(image_path, "rb") as f:
            image_data = f.read()
        
        #mapping
        category_value = VIEW_CATEGORIES[view][category_display]
        
        #create form data
        form = FormData()
        form.add_field("view", view)
        form.add_field("category", category_value)
        form.add_field("source", "telegram")
        form.add_field("image", image_data, filename="image.jpg", content_type="image/jpeg")
        
        #send request 
        async with aiohttp.ClientSession() as session:
            async with session.post(API_ENDPOINT, data=form) as resp:
                if resp.status == 200:
                    result = await resp.json()
                    
                    message = f"""
πŸ“Š **Analysis Results**

πŸ” **View:** {result.get('view', view).upper()}
πŸ₯ **Category:** {category_display}

πŸ“ˆ **Confidence:** {result.get('confidence', 0):.2f}%
⚠️ **Reconstruction error:** {result.get('error', 0):.5f}

πŸ“‹ **Status:** {result.get('comment', 'No comment')}

🩺 **Diagnosis:** {result.get('diagnosis', 'No diagnosis')}*
                    """
                    
                    await query.edit_message_text(message, parse_mode='Markdown')
                     
                else:
                    error_text = await resp.text()
                    await query.edit_message_text(f"❌ Upload failed. Status: {resp.status}\nError: {error_text}")
                    
    except Exception as e:
        logging.error(f"Error processing request: {e}")
        await query.edit_message_text(f"❌ Error: {str(e)}")
        
    finally:
        try:
            if os.path.exists(image_path):
                os.remove(image_path)
                logging.info(f"Cleaned up image file: {image_path}")
        except Exception as e:
            logging.error(f"Error cleaning up file: {e}")

def main():
    app = Application.builder().token(TOKEN).build()
    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("instructions", instructions))
    app.add_handler(CommandHandler("list", list_categories))
    app.add_handler(CommandHandler("stop", stop))
    app.add_handler(MessageHandler(filters.PHOTO, handle_image))
    app.add_handler(CallbackQueryHandler(handle_view, pattern="^view:"))
    app.add_handler(CallbackQueryHandler(handle_category, pattern="^category:"))
    app.run_polling()

if __name__ == "__main__":
    main()