diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..2eea525d885d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/__pycache__/anaemia.cpython-311.pyc b/__pycache__/anaemia.cpython-311.pyc new file mode 100644 index 000000000000..b6b64a007e8a Binary files /dev/null and b/__pycache__/anaemia.cpython-311.pyc differ diff --git a/__pycache__/bmi.cpython-311.pyc b/__pycache__/bmi.cpython-311.pyc new file mode 100644 index 000000000000..b9fd90b5fd84 Binary files /dev/null and b/__pycache__/bmi.cpython-311.pyc differ diff --git a/__pycache__/chatgpt_values.cpython-311.pyc b/__pycache__/chatgpt_values.cpython-311.pyc new file mode 100644 index 000000000000..9014cf7cf85f Binary files /dev/null and b/__pycache__/chatgpt_values.cpython-311.pyc differ diff --git a/__pycache__/diabetes.cpython-311.pyc b/__pycache__/diabetes.cpython-311.pyc new file mode 100644 index 000000000000..fa521adae037 Binary files /dev/null and b/__pycache__/diabetes.cpython-311.pyc differ diff --git a/__pycache__/image_loading.cpython-311.pyc b/__pycache__/image_loading.cpython-311.pyc new file mode 100644 index 000000000000..35c7f65f86b9 Binary files /dev/null and b/__pycache__/image_loading.cpython-311.pyc differ diff --git a/__pycache__/lipids_ranges.cpython-311.pyc b/__pycache__/lipids_ranges.cpython-311.pyc new file mode 100644 index 000000000000..0d9439c9f43c Binary files /dev/null and b/__pycache__/lipids_ranges.cpython-311.pyc differ diff --git a/anaemia.py b/anaemia.py new file mode 100644 index 000000000000..89613462797c --- /dev/null +++ b/anaemia.py @@ -0,0 +1,52 @@ +testdict = { + "mcv":{ + "test_found":True, + "test_value":60, + "test_unit":False, + "test_ref_min":85, + "test_ref_max":False + }, + "rbc_count":{ + "test_found":True, + "test_value":60, + "test_unit":False, + "test_ref_min":85, + "test_ref_max":False + }, + "hb":{ + "test_found":True, + "test_value":11, + "test_unit":False, + "test_ref_min":12, + "test_ref_max":16 + }, +} +#mcv need fl RBC need 10^6/uL +#returns tuple - bool whether anaemia, and bool whether iron deficient +def anaemia_analysis (hbdict): + output_phrase = "I didn't catch that" + print ("in anaemia analysis") + hblevel = hbdict["hb"]["test_value"] + if hblevel < 7: + return ":large_orange_circle: Your haemoglobin level is dangerously low. Please visit a doctor immediately." + if hblevel < hbdict["hb"]["test_ref_min"]: + if not hbdict["mcv"]["test_found"]: + return ":large_orange_circle: You likely have anaemia, but there is not enough information to determine the cause. Visit a doctor if you have any concerns of blood loss." + mcv = hbdict["mcv"]["test_value"] + if mcv < hbdict["mcv"]["test_ref_min"]: #microcytic + if not hbdict["rbc_count"]["test_found"]: + return ":large_orange_circle: You likely have anaemia, but there is not enough information to determine the cause. Visit a doctor if you have any concerns of blood loss." + mentzer = mcv/ hbdict["rbc_count"]["test_value"] + if mentzer > 13: + output_phrase = ":large_orange_circle: You likely have anaemia, which could be due to iron deficiency. This can be caused by minor bleeding e.g. menstruation, or from the gastrointestinal tract. Visit your doctor for an assessment to rule out sources of bleeding. Consider taking iron supplements and foods rich in iron, such as green leafy vegetables, meat, especially red meat (beef, mutton, pork), seafood, and organs (kidney, liver)." + else : + output_phrase = ":large_orange_circle: You likely have anaemia, which could be related to genetic conditions such as thalassaemia. Visit your doctor for an assessment." + elif mcv > hbdict["mcv"]["test_ref_max"]: # macrocytic + output_phrase = ":large_orange_circle: You likely have anaemia, which may be caused by low folate (vitamin B9) or vitamin B12 levels. Other causes may include chronic alcohol intake, thyroid problems, and liver problems. Consider taking more foods high in folate, such as broccoli, spinach, and brown rice, and foods high in vitamin B12, such as meat, milk, cheese and eggs." + else: #normocytic + output_phrase = ":large_orange_circle: You likely have anaemia, which may be associated with chronic illness such as kidney disease, or chronic inflammatory conditions." + else: + output_phrase = ":large_green_circle: You likely do not have anaemia." + return output_phrase + +#print (f"here is result: {anaemia_analysis (testdict)}") diff --git a/bmi.py b/bmi.py new file mode 100644 index 000000000000..53687f503196 --- /dev/null +++ b/bmi.py @@ -0,0 +1,43 @@ +test_results = { + "height":{ + "test_found":False, + "test_value":1.5, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "weight":{ + "test_found":False, + "test_value":70, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + } +} + + +def bmi_advice(test_results): + print ("in bmi advice") + output_string = "I didn't catch that" + weight = test_results["weight"]["test_value"] + height = test_results["height"]["test_value"] + if weight > 0 and height > 0: + bmi = round(weight / pow(height,2), 1) + else: + return ":large_yellow_circle: You need both weight and height to calculate BMI." + weightloss = False + if bmi >=27.5: + output_string = ":large_orange_circle: You are obese. Your BMI is " + str(bmi) + weightloss = True + elif bmi >= 23: + output_string = ":large_orange_circle: You are overweight. Your BMI is " + str(bmi) + weightloss = True + elif bmi < 18.5: + output_string = ":large_orange_circle: You are underweight. Your BMI is " + str(bmi)+ " Consider increasing food intake, for example, by taking smaller, frequent healthy meals. Increase protein intake by taking more lean meats, fish, eggs, dairy, legumes and nuts. Do strength training to build up muscles." + else: + output_string = ":large_green_circle: You have a healthy BMI. Your BMI is " + str(bmi) + if weightloss: + output_string += "Choose healthier choices that are lower in fat (e.g. lean meat, low-fat dairy products), lower or no sugar (e.g. unsweetened beverages, fresh fruits), and higher in fibre (e.g. whole-meal bread, brown rice). Look out for alternatives that are lower in calories. Reduce your meal sizes by consuming ¾ of your usual. Do some moderate-intensity aerobic physical activities such as brisk walking, swimming or cycling for a minimum of 150-300 minutes weekly. If you're just starting out, accumulating shorter bouts of 10-minute exercise is a good start too." + return output_string + +#print ("advice " + bmi_advice(test_results)) diff --git a/chatgpt_values.py b/chatgpt_values.py new file mode 100644 index 000000000000..f6a490df3cf4 --- /dev/null +++ b/chatgpt_values.py @@ -0,0 +1,123 @@ +import streamlit as st +import pandas as pd +import numpy as np +import os +import time +import base64 +import json +import cv2 +from openai import OpenAI +from paddleocr import PaddleOCR + + +template_prompt = """ +Extract the items from the health screening result listed below into a json format, using the example json template. Ignore other items not listed in the json template. Output data types are "test_found" (True/False), "test_value" (float), "test_unit" (string), "test_ref_min" (float), "test_ref_max" (float). If test item is not found, output False for "test_found", and output False for the other values. If test is found but reference max, reference min, or reference range is not found, output False for "test_ref_min" and "test_ref_max". +Convert height to metres and weight to kg. + +Example output json template +{ + "ldl_cholesterol": { + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "hdl_cholesterol": { + "test_found":True, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "total_cholesterol": { + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "mcv":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "hb":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "rbc_count":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "glucose":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "hba1c":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "systolic_bp":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "diastolic_bp":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "height":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "weight":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + } +} + +Health screening result: +""" + +def extract_values(client,extracted_text): + extract_start_time = time.time() + extract_prompt = f"{template_prompt} {extracted_text}" + response = client.chat.completions.create( + model="gpt-4-turbo-preview", #gpt-3.5-turbo-1106 + seed=4022024, + response_format={ "type": "json_object" }, + messages=[ + {"role": "system", "content": "You are a helpful assistant designed to output JSON."}, + {"role": "user", "content": extract_prompt} + ] + ) + test_results = json.loads(response.choices[0].message.content) + extract_end_time = time.time() + extract_time = int(extract_end_time - extract_start_time) + return response,test_results,extract_time diff --git a/diabetes.py b/diabetes.py new file mode 100644 index 000000000000..408cc95d5865 --- /dev/null +++ b/diabetes.py @@ -0,0 +1,77 @@ +testdict = { + "glucose":{ + "test_found":True, + "test_value":6.5, + "test_unit":"mmol/l", + "test_ref_min":False, + "test_ref_max":False + }, + "hba1c":{ + "test_found":True, + "test_value":6.4, + "test_unit":"%", + "test_ref_min":False, + "test_ref_max":False + } +} + +test_attributes = { + "age" : 40, + "sex" :"male", #0 for male, 1 for female + "smoker" : True, + "stroke" : False, + "diabetes" : True, + "ckd" : False, + "heart_attack" : False , + "race" :"indian", + "systolic_blood_pressure" : 120, + "on_BP_meds" : False +} + + +def get_dm_advice(inputattributes, inputdict): + print ("in dm advice") + output_phrase = "I didn't catch that." + lifestyle = False + #known dm + a1c_value = inputdict["hba1c"]["test_value"] if inputdict["hba1c"]["test_found"] else 0 + glucose_value = inputdict["glucose"]["test_value"] if inputdict["glucose"]["test_found"] else 0 + if inputattributes["diabetes"]: + if a1c_value >0: + if inputattributes["age"] >= 80: + a1ctarget = 8 + elif inputattributes["age"] <=40: + a1ctarget = 6.5 + else: + a1ctarget = 7.0 + if a1c_value > a1ctarget: + output_phrase = ":large_orange_circle: Your HbA1c is above target, your diabetes can be controlled better. Aim for a HbA1c below 7." + lifestyle = True + else: + output_phrase = ":large_green_circle: Your HbA1c is below 7, which is within expected range for a diabetic." + if glucose_value >0: + if glucose_value > 6: + output_phrase += " \nYour fasting glucose level is slightly high. Please consult your doctor for your specific glucose targets. Having a HbA1c measurement may be helpful to better evauate your diabetes control." + else: + output_phrase += " \nYour fasting glucose level is within range, <6." + else: + #diagnose dm + if a1c_value >= 6.5 or glucose_value >= 7: + output_phrase = ":large_orange_circle: You likely have diabetes. Consult a doctor for advice, you may need to be started on medications." + lifestyle = True + elif a1c_value >= 6.1 or glucose_value >=6.1: + output_phrase = ":large_yellow_circle: You likely have prediabetes." + if a1c_value >= 5.7: + output_phrase += " Your HbA1c is >6.0." + if glucose_value >=5.6: + output_phrase += " Your fasting glucose is >6.0. If this was a non-fasting sample, it may be difficult to assess. A fasting sample is preferred." + lifestyle = True + else: + output_phrase = ":large_green_circle: You likely do not have diabetes." + if lifestyle: + output_phrase += " Eat a healthy balanced diet - using My Healthy Plates (filling a quarter of the plate with wholegrains, quarter with good sources of protein, and half with fruit and vegetables). Maintain a healthy weight BMI ranging from 18.5 to 22.9 kg/m2. Exercise regularly, aiming for 150 minutes of moderate-intensity activity per week or 20 minutes of vigorous-intensity activity 3 or more days a week). Limit alcohol intake to 1 drink per day for females, and 2 drinks per day for males." + if inputattributes["smoker"]: + output_phrase += " You are highly encouraged to quit smoking." + return output_phrase + +#print(f"advice {get_dm_advice(test_attributes, testdict)}") diff --git a/image_loading.py b/image_loading.py new file mode 100644 index 000000000000..b957cfc2c03c --- /dev/null +++ b/image_loading.py @@ -0,0 +1,36 @@ +import streamlit as st +import pandas as pd +import numpy as np +import os +import time +import base64 +import json +import cv2 +import re +from openai import OpenAI +from paddleocr import PaddleOCR + +def load_image(): + uploaded_file = st.file_uploader(label='Upload your test results image below:') + if uploaded_file is not None: + image_data = uploaded_file.getvalue() + st.image(image_data, caption='', use_column_width=True) # Adjust width for mobile screens + return image_data + +def remove_nric(text): + pattern = r'[STFG]\d{7}[A-Z]' + replaced_text = re.sub(pattern, ' ', text) + return replaced_text + +def extract_text(image,ocr_model): + ocr_start_time = time.time() + result = ocr_model.ocr(image) + result = result [0] #idk why this needs a result[0] instead of result for Github + extracted_text = '' + for idx in range(len(result)): + txt = result[idx][1][0] + extracted_text += str(txt) + " " + extracted_text_clean = remove_nric(extracted_text) + ocr_end_time = time.time() + ocr_time = int(ocr_end_time - ocr_start_time) + return extracted_text_clean, ocr_time diff --git a/lipids_ranges.py b/lipids_ranges.py new file mode 100644 index 000000000000..d0eedfee55d4 --- /dev/null +++ b/lipids_ranges.py @@ -0,0 +1,315 @@ +#REMEMBER TO CHANGE BP TO TESTVALS +# import json + +# # Open the file containing the JSON data +# with open('Sample json extraction\sample json extraction.txt') as f: +# # Load the JSON data from the file +# data = json.load(f) + +# # add data to a dictionary where key is test name and value is dictionary of test name/results etc. +# masterdict = {} +# # Access each individual object and list the attributes +# for dict in data['test_results']: +# masterdict [dict["test_name"]] = dict + +testdict = { + "ldl_cholesterol": { + "test_found":True, + "test_value":5.8, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "hdl_cholesterol": { + "test_found":True, + "test_value":1.0, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "total_cholesterol": { + "test_found":True, + "test_value":4.1, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "systolic_bp":{ + "test_found":True, + "test_value":200, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "diastolic_bp":{ + "test_found":True, + "test_value":100, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, +} + + +#access value by e.g. masterdict["CHOLESTEROL"]["test_value"] +#print (f"master dict {masterdict}") + +#sample of attributes needed +test_attributes = { + "age" : 40, + "sex" :"male", #0 for male, 1 for female + "smoker" : True, + "stroke" : False, + "diabetes" : False , + "ckd" : False, + "heart_attack" : False , + "race" :"indian", + "on_BP_meds" : False, + "systolic_blood_pressure": 120 +} + +def getLDLBPtarget (attributes,testvals): + print ("in ldl target") + LDLtargetcalc = 0 + BP_target = (0, 0) + # check all attributes present else return "invalid" + if testvals["systolic_bp"]["test_found"]: + bval = testvals ["systolic_bp"]["test_value"] + elif attributes["systolic_blood_pressure"] and attributes["systolic_blood_pressure"] > 0 : + bval = attributes["systolic_blood_pressure"] + else: + bval = 0 + if(attributes["stroke"]): + LDLtargetcalc = 1.8 + BP_target = (140, 90) #with disclaimer + if(attributes["diabetes"] or attributes["ckd"]): + BP_target = (130, 80) + if (attributes["diabetes"] and attributes["ckd"]): + LDLtargetcalc = 1.8 + else: + LDLtargetcalc = 2.6 + if(attributes["heart_attack"]): + LDLtargetcalc = 1.4 + BP_target = (130, 80) + #SGFRS scoring, only if no stroke or diabetes then proceed + if LDLtargetcalc == 0: + #function "all" returns true if no values are false (0 values map to false) i.e. if any of these are 0 or empty, return + if not all ((bval, attributes["age"], attributes["sex"], attributes["race"], testvals["total_cholesterol"]["test_value"], testvals ["hdl_cholesterol"]["test_value"])): + print ("something missing "+str((bval, attributes["age"], attributes["sex"], attributes["race"], testvals["total_cholesterol"]["test_value"], testvals ["hdl_cholesterol"]["test_value"]))) + print(bool(bval), bool(attributes["age"]), bool (attributes["sex"]), bool(attributes["race"]), bool(testvals["total_cholesterol"]["test_value"]), bool(testvals ["hdl_cholesterol"]["test_value"])) + return ":large_yellow_circle: More information is needed to calculate your blood pressure or cholesterol target. Please fill in the boxes above (age, sex, race, systolic blood pressure). We also need your HDL cholesterol and total cholesterol. In general, aim for BP <140/90 and LDL <3.4 if you have no other risk factors." + #dictionary with the values (M, F), corresponding to lower bound of age e.g. 20-40 would be 20 + # first sieve out all 20-40, then split into 20-34 and 35-39 for age score, and total chol for chol score + # age as tuple (-9, -7,-4, -3): 20-34 M -9 F -7, 35-39 M -4 F -3 ; + # total chol as tuple ((4, 4), (7, 8), (9, 11), (11, 13)) for 4.1-5.1 : M 4 F 4, 5.2-6.1 : M 7 F 8, 6.2-7.2 : M 9 F 11, >7.3 : M 11 F 13 + + agedict = { + 20 : { + "age": ( + (-9, -7), #20-34 (M, F) + (-4, -3)), #35-39 (M, F)) + "tchol": ( + (4, 4), #4.1-5.1 (M, F) + (7, 8), #5.2-6.1 (M, F) + (9, 11), #6.2-7.2 (M, F) + (11, 13)),#>=7.3 (M, F) + "smoker": (8, 9) + }, + 40 :{ + "age": (( + 0, 0), + (3, 3)), + "tchol" : ( + (3, 3), + (5, 6), + (6, 8), + (8, 10)), + "smoker": (5, 7) + }, + 50 :{ + "age": ( + (6, 6), + (8, 8)), + "tchol" : ( + (2, 2), + (3, 4), + (4, 5), + (5, 7)), + "smoker": (3, 4) + }, + 60 :{ + "age": ( + (10, 10), + (11, 12)), + "tchol" : ( + (1, 1), + (1, 2), + (2, 3), + (3, 4)), + "smoker": (1, 2) + }, + 70 :{ + "age": ( + (12, 14), + (13, 16)), + "tchol" : ( + (0, 1), + (0, 1), + (1, 2), + (1, 2)), + "smoker": (1, 1) + } + } + #dictionary corresponding to ageless variables + otherriskdict = { + "hdl" : (-1, 0, 1, 2), #regardless of gender, for ranges >=1.6, 1.3-1.5, 1.0-1.2, <1.0 + "sysBP" : ( + ((0, 1),(1, 3)), #untreated M, F , treated M, F for 120-129 + ((1, 2),(2, 4)), #130-139 + ((1, 3),(2, 5)), #140-159 + ((2, 4),(3, 6)), #>160 + ) + } + #start scoring + #print (f"scoring now") + score = 0 + sex = 0 if attributes["sex"].lower() == "male" else 1 + age = attributes["age"] + tcval = testvals ["total_cholesterol"]["test_value"] + if testvals ["total_cholesterol"]["test_unit"].lower() =="mg/dl": + tcval *= 0.02586 + testvals ["total_cholesterol"]["test_value"] *= 0.02586 + testvals ["total_cholesterol"]["test_unit"] = "mmol/L" + if testvals ["total_cholesterol"]["test_unit"].lower() =="mmol/l": + if tcval > 7.2: cholbracket = 3 + elif tcval > 6.1: cholbracket = 2 + elif tcval > 5.1: cholbracket = 1 + elif tcval > 4.1: cholbracket = 0 + else: cholbracket = -1 + else: + cholbracket = -1 + return "invalid cholesterol units" + + hval = testvals ["hdl_cholesterol"]["test_value"] + if testvals ["hdl_cholesterol"]["test_unit"].lower() =="mg/dl": + hval *= 0.02586 + testvals ["hdl_cholesterol"]["test_value"] *= 0.02586 + testvals ["hdl_cholesterol"]["test_unit"] = "mmol/L" + if testvals ["hdl_cholesterol"]["test_unit"].lower() =="mmol/l": + if hval > 1.5: hdlbracket = 0 + elif hval > 1.2: hdlbracket = 1 + elif hval > 1: hdlbracket = 2 + else: hdlbracket = 3 + else: + hdlbracket = -1 + return "invalid cholesterol units" + + if bval > 159: bpbracket = 3 + elif bval > 139: bpbracket = 2 + elif bval > 129: bpbracket = 1 + elif bval > 119: bpbracket = 0 + else: bpbracket = -1 + + # HDL points + if hdlbracket >-1: + score += otherriskdict["hdl"][hdlbracket] + + #systolic BP points + treated = 1 if attributes["on_BP_meds"] else 0 + if bpbracket > -1: + score += otherriskdict["sysBP"][bpbracket][treated][sex] + print (f"score after BP/hdl {score}") + agescore = 0 + if age >70: + curdict = agedict[70] + agebracket = 0 if age <75 else 1 + if age >80: + BP_target = (150, 90) + elif age >60: + curdict = agedict[60] + agebracket = 0 if age <65 else 1 + elif age >50: + curdict = agedict[50] + agebracket = 0 if age <55 else 1 + elif age >40: + curdict = agedict[40] + agebracket = 0 if age <45 else 1 + elif age >20: + curdict = agedict[20] + agebracket = 0 if age <35 else 1 + else: + return ":large_yellow_circle: You are too young to use this calculator. In general, aim for BP <140/90 and LDL cholesterol < 3.4." + + # age only points + agescore += curdict["age"][agebracket][sex] + #print (f"agescore {agescore} after age only") + + # age and cholesterol points + if cholbracket > -1: #no points if cholesterol is <4.1 + agescore += curdict["tchol"][cholbracket][sex] + #print (f"agescore {agescore} after chol") + + # age and smoking points + if attributes["smoker"]: + agescore += curdict["smoker"][sex] + #print (f"agescore {agescore} after smoking") + + score += agescore + if attributes["race"].lower() == "indian": + raceint = 0 + elif attributes["race"].lower() == "malay": + raceint = 1 + elif attributes["race"].lower() == "chinese": + raceint = 2 + else: + return ":large_yellow_circle: This calculator is not validated for other races. In general, aim for LDL cholesterol <3.4 and BP <140/90." + + #matching to cardiovascular risk bracket + cvriskdict = { + ">20": ((16, 24), (17, 25), (19, 27)), + "10-20": ((12, 20), (14, 22), (16, 24)), + "5-10": ((9, 18), (11, 19), (13, 21)), + "<5": ((8, 17), (10, 18), (12, 20)), + } + recmeds = True + BP_target = (140,90) + if score >= cvriskdict[">20"][raceint][sex]: + LDLtargetcalc = 1.8 + BP_target = (130, 80) + elif score >= cvriskdict["10-20"][raceint][sex]: + LDLtargetcalc = 2.6 + elif score >= cvriskdict["5-10"][raceint][sex]: + LDLtargetcalc = 3.4 + elif score <= cvriskdict["<5"][raceint][sex]: + LDLtargetcalc = 3.4 + recmeds = False + + #print (f"score {score} LDL target {LDLtargetcalc}") + if not testvals ["ldl_cholesterol"]["test_found"]: + return ":large_yellow_circle: LDL cholesterol not found. LDL cholesterol is the main cholesterol that affects medical management." + if testvals ["ldl_cholesterol"]["test_unit"].lower() =="mg/dl": + testvals ["ldl_cholesterol"]["test_value"] *= 0.02586 + testvals ["ldl_cholesterol"]["test_unit"] = "mmol/L" + if testvals ["ldl_cholesterol"]["test_value"] > LDLtargetcalc: + output_phrase = ":large_orange_circle: Your LDL cholesterol is high. Eat a healthy balanced diet - using My Healthy Plates (filling a quarter of the plate with wholegrains, quarter with good sources of protein (fish, lean meat, tofu and other bean products, nuts), and half with fruit and vegetables. increase soluble fibre intake, avoid food with trans fat,replace saturated fat with polyunsaturated fats. Certain diets like ketogenic diet increase LDL-C levels. Aim for regular moderate-intensity physical activity for 150-300min a week. For people who are overweight or obese, weight reduction of 5–10% could be beneficial for improving lipid profile. Limit alcohol intake to 1 drink per day for females, and 2 drinks per day for males." + if recmeds: + output_phrase += "You may require cholesterol lowering medications, consult your doctor. " + + else: + output_phrase = ":large_green_circle: Your LDL cholesterol is within target range (less than " + str(LDLtargetcalc) + ")." + if testvals["systolic_bp"]["test_found"]: + bp = (testvals["systolic_bp"]["test_value"], testvals["diastolic_bp"]["test_value"]) + if bp[0] > BP_target[0] or bp[1] > BP_target[1]: + if bp[0] > 180 or bp[1] > 120: + output_phrase += " \n:large_orange_circle: Your blood pressure is dangerously high, SBP >180 or DBP >120. Visit a doctor for assessment.\n" + else: + output_phrase += " \n:large_orange_circle: Your blood pressure is high. Your target should be " + str(BP_target[0]) + "/" + str(BP_target[1]) + " . Take a healthy diet (e.g., reducing salt intake and alcohol consumption), increase physical activity, lose weight if overweight or obese." + if attributes["stroke"]: + output_phrase += "Since you have had a stroke before, your blood pressure targets may need to be customised according to the type of stroke. Seek advice from your stroke doctor for specific blood pressure targets." + else: + output_phrase += ":large_green_circle: Your BP is in the normal range (less than "+ str(BP_target[0]) + "/" + str(BP_target[1]) + ")." + if attributes["smoker"]: + output_phrase += " \nYou are highly encouraged to quit smoking." + return output_phrase + +#print (f"advice is {getLDLBPtarget (test_attributes, testdict)}") diff --git a/packages.txt b/packages.txt new file mode 100644 index 000000000000..f359f073a1f1 --- /dev/null +++ b/packages.txt @@ -0,0 +1 @@ +libgl1-mesa-glx diff --git a/refranges.py b/refranges.py new file mode 100644 index 000000000000..88a889b161bb --- /dev/null +++ b/refranges.py @@ -0,0 +1,93 @@ +import json +from lipids_ranges import getLDLtarget +from diabetes import get_dm_advice +from anaemia import anaemia_analysis, get_anaemia_advice + +testdict = { + "ldl_cholesterol": { + "test_found":True, + "test_value":2.0, + "test_unit":"mmol/l", + "test_ref_min":False, + "test_ref_max":False + }, + "hdl_cholesterol": { + "test_found":True, + "test_value":1.2, + "test_unit":"mmol/l", + "test_ref_min":False, + "test_ref_max":False + }, + "total_cholesterol": { + "test_found":True, + "test_value":5.0, + "test_unit":"mmol/l", + "test_ref_min":False, + "test_ref_max":False + }, + "mcv":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "rbc_count":{ + "test_found":True, + "test_value":60, + "test_unit":False, + "test_ref_min":85, + "test_ref_max":False + } + "hb":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "glucose":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "hba1c":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "systolic_bp":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "diastolic_bp":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "height":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + }, + "weight":{ + "test_found":False, + "test_value":False, + "test_unit":False, + "test_ref_min":False, + "test_ref_max":False + } +} + + diff --git a/requirements.txt b/requirements.txt index 502d7d1a0d19..13602138719b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -altair -pandas +opencv_python +paddleocr +paddlepaddle streamlit +openai diff --git a/streamlit_app.py b/streamlit_app.py index 7a0ed1272052..d0c2cda43116 100644 --- a/streamlit_app.py +++ b/streamlit_app.py @@ -1,40 +1,153 @@ -import altair as alt -import numpy as np -import pandas as pd import streamlit as st +import pandas as pd +import numpy as np +import os +import time +import base64 +import json +import cv2 +from openai import OpenAI +from paddleocr import PaddleOCR +from image_loading import load_image, extract_text +from chatgpt_values import extract_values +from lipids_ranges import getLDLBPtarget +from diabetes import get_dm_advice +from anaemia import anaemia_analysis +from bmi import bmi_advice +#from dotenv import load_dotenv -""" -# Welcome to Streamlit! +#load_dotenv() + +#os.environ['API_KEY']='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' +API_KEY = os.environ['API_KEY'] # API_KEY in streamlit secret -Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. -If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community -forums](https://discuss.streamlit.io). +client = OpenAI(api_key=API_KEY) -In the meantime, below is an example of what you can do with just a few lines of code: +ocr_model = PaddleOCR(use_angle_cls=True, lang='en') + +tab1, tab2 = st.tabs(["Main", "About"]) + +test_attributes = {} + +measurements_list = """ + - Height + - Weight + - Cholesterol + - Haemoglobin + - Mean Corpuscular Volume (MCV) + - Red Blood Cell (RBC) + - Glucose + - HbA1c + - Blood Pressure (BP) """ -num_points = st.slider("Number of points in spiral", 1, 10000, 1100) -num_turns = st.slider("Number of turns in spiral", 1, 300, 31) - -indices = np.linspace(0, 1, num_points) -theta = 2 * np.pi * num_turns * indices -radius = indices - -x = radius * np.cos(theta) -y = radius * np.sin(theta) - -df = pd.DataFrame({ - "x": x, - "y": y, - "idx": indices, - "rand": np.random.randn(num_points), -}) - -st.altair_chart(alt.Chart(df, height=700, width=700) - .mark_point(filled=True) - .encode( - x=alt.X("x", axis=None), - y=alt.Y("y", axis=None), - color=alt.Color("idx", legend=None, scale=alt.Scale()), - size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), - )) +with tab1: + col1, mid, col2 = st.columns([1,1,10]) + with col1: + st.image("https://i.ibb.co/869GgfZ/stethoscope-logo-text.jpg", width=100) + with col2: + st.subheader('Instructions: \n1. Fill in the form below \n 2. Upload a picture of your lab test results \n3. Click "Generate my report!" button') + + # User inputs + age = st.number_input("Enter your age", min_value=0, max_value=140, step=1,value="min") + sex = st.selectbox("Select your sex", ["Female","Male"]) + race = st.selectbox("Select your race", ["Chinese", "Malay", "Indian", "Others"]) + smoker = st.checkbox("Are you a smoker?") + stroke = st.checkbox("Have you ever had a stroke?") + diabetes = st.checkbox("Do you have a history of diabetes?") + heart_attack = st.checkbox("Have you ever had a heart attack?") + ckd = st.checkbox("Do you have chronic kidney disease?") + on_bp_meds = st.checkbox("Are you taking blood pressure medication?") + systolic_bp = st.number_input("Enter your last recorded systolic blood pressure (leave blank if not available)", min_value=50,max_value=300,value=None) + + image = load_image() + + if st.button('Generate my report!'): + # Save test attributes + test_attributes["age"] = age + test_attributes["sex"] = sex + test_attributes["race"] = race + test_attributes["smoker"] = smoker #true or false + test_attributes["stroke"] = stroke + test_attributes["diabetes"] = diabetes + test_attributes["heart_attack"] = heart_attack + test_attributes["ckd"] = ckd + test_attributes["on_BP_meds"] = on_bp_meds + test_attributes["systolic_blood_pressure"] = systolic_bp #null or integer + # Extract text from image + if not image: #Image not uploaded + st.error("Upload an image of your test results first!",icon="🚨") + else: # Image uploaded + with st.status('Reading image...', expanded=True) as status: + st.json(test_attributes) + extracted_text,ocr_time = extract_text(image,ocr_model) + st.markdown(extracted_text) + st.success(f"Processed image in {ocr_time} seconds.") # Use status instead of toast/success + # Extract structured data from text using ChatGPT + # TODO: PUT TRY AND ERROR IF FAIL + st.write("Extracting values from image...") + response,test_results,extract_time = extract_values(client,extracted_text) # use chatgpt to extract + st.json(test_results) + st.text(response.usage) + status.update(label="Analysed results!", state="complete", expanded=False) + st.success(f"Extracted values in {extract_time} seconds.") # Use status instead of toast/success + # Insert YT logic + print (test_results) + print (test_attributes) + #test_results test_attributes + full_output = "" + dm = False + anaemia = False + LDLBP = False + BMI = False + for key, value in test_results.items(): + print (f"looking at {key} and {value}") + if value["test_found"]: + if key == "hb": + fbc_output = anaemia_analysis (test_results) + full_output += f"**Full Blood Count** \n{fbc_output} \n\n" # streamlit needs 2 whitespace before newline char + elif key == "ldl_cholesterol": + chol_output = getLDLBPtarget (test_attributes, test_results) + full_output += f"**Cholesterol and Blood Pressure** \n{chol_output} \n\n" + elif key == "glucose" or key == "hba1c": + if not dm: + glucose_output = get_dm_advice(test_attributes, test_results) + full_output += f"**Blood Sugar** \n{glucose_output} \n\n" + dm = True + elif key == "systolic_bp": + if not test_results["hdl_cholesterol"]["test_found"]: + bp_output = "We need your cholesterol levels to interpret the blood pressure targets better. In general, aim for a blood pressure <140/90. \n\n" + full_output += f"**Blood Pressure** \n{bp_output}\n" + elif key == "weight" or key == "height": + if not BMI: + bmi_output = bmi_advice(test_results) + BMI = True + full_output += f"**Height/Weight (BMI)** \n{bmi_output} \n\n" + print(full_output) + if full_output == "": # if no supported lab results found + full_output = "No supported medical lab results detected in your image. \nCheck if your image contains lab results listed in the About page." + st.error(f"{full_output}",icon="🚨") + else: + st.subheader(':bookmark_tabs: Your Report') + st.markdown(full_output) + # print test results + st.subheader(':test_tube: Measurement values detected') + for test_name, test_info in test_results.items(): + if test_info["test_found"]: + st.markdown(f"**Test Name:** {test_name.replace('_', ' ').upper()}") + st.markdown(f"**Test Value:** {test_info['test_value']} {test_info['test_unit']}") + st.text("") + + +with tab2: + col1, mid, col2 = st.columns([1,1,10]) + with col1: + st.image("https://i.ibb.co/869GgfZ/stethoscope-logo-text.jpg", width=100) + with col2: + st.text("") + st.markdown("**Lab Lokun** is an AI-assisted app that interprets and explains blood and lab test reports to provide personalised health advice and recommendations using Singapore ACG guidelines. **Lab Lokun** is co-created by doctors and non-doctors who have interpreted indecipherable lab results to their friends and family too many times. :joy:") + st.subheader('Lab measurements included for analysis') + st.markdown(measurements_list) + st.write('Other lab tests will be added soon...stay tuned!') + +st.caption('Disclaimer: Information provided through our app is for informational and educational purposes only and is not intended as a substitute for professional medical advice, diagnosis, or treatment. Always seek the advice of your physician or other qualified health provider with any questions you may have regarding a medical condition.') diff --git a/testing.py b/testing.py new file mode 100644 index 000000000000..1ab9e3c3c981 --- /dev/null +++ b/testing.py @@ -0,0 +1,48 @@ + +from lipids_ranges import getLDLBPtarget +from diabetes import get_dm_advice +from anaemia import anaemia_analysis +from bmi import bmi_advice + +test_attributes = { + "age":57, + "sex":"male", + "race":"Chinese", + "smoker":False, + "stroke":False, + "diabetes":False, + "heart_attack":False, + "ckd":False, + "on_BP_meds":False, + "systolic_blood_pressure": 120 +} + +test_results = { + 'ldl_cholesterol': {'test_found': False, 'test_value': 2.2, 'test_unit': 'mmol/l', 'test_ref_min': False, 'test_ref_max': False}, + 'hdl_cholesterol': {'test_found': False, 'test_value': 1, 'test_unit': 'mmol/l', 'test_ref_min': False, 'test_ref_max': False}, + 'total_cholesterol': {'test_found': False, 'test_value': 3.7, 'test_unit': 'mmol/l', 'test_ref_min': False, 'test_ref_max': False}, + 'mcv': {'test_found': True, 'test_value': 100, 'test_unit': False, 'test_ref_min': 85, 'test_ref_max': 90}, + 'hb': {'test_found': True, 'test_value': 10, 'test_unit': 'g/dL', 'test_ref_min': 11.5, 'test_ref_max': 15.5}, + 'rbc_count': {'test_found': True, 'test_value': 4.2, 'test_unit': False, 'test_ref_min': False, 'test_ref_max': False}, + 'glucose': {'test_found': False, 'test_value': False, 'test_unit': 'mmol/L', 'test_ref_min': False, 'test_ref_max': False}, + 'hba1c': {'test_found': False, 'test_value': 6.0, 'test_unit': '%', 'test_ref_min': False, 'test_ref_max': False}, + 'systolic_bp': {'test_found': False, 'test_value': False, 'test_unit': 'mmHg', 'test_ref_min': False, 'test_ref_max': False}, + 'diastolic_bp': {'test_found': False, 'test_value': False, 'test_unit': 'mmHg', 'test_ref_min': False, 'test_ref_max': False}, + 'height': {'test_found': False, 'test_value': 1.61, 'test_unit': 'm', 'test_ref_min': False, 'test_ref_max': False}, + 'weight': {'test_found': False, 'test_value': 58, 'test_unit': 'kg', 'test_ref_min': False, 'test_ref_max': False} +} + +for key, value in test_results.items(): + print (f"looking at {key} and {value}") + if value["test_found"]: + if key == "hb": + print (f"FBC {anaemia_analysis (test_results)}") + elif key == "ldl_cholesterol": + print (f"LDL/BP {getLDLBPtarget (test_attributes, test_results)}") + elif key == "glucose": + print (f"glucose {get_dm_advice(test_attributes, test_results)}") + elif key == "systolic_bp": + if not test_results["total_cholesterol"]["test_found"] or not test_results["hdl_cholesterol"]["test_found"]: + print("we need your cholesterol levels to interpret the blood pressure targets better. In general, aim for a blood pressure <140/90.") + elif key == "weight": + print (f"BMI {bmi_advice(test_results)}") \ No newline at end of file