import streamlit as st import numpy as np import scipy.stats as stats from fpdf import FPDF import base64 import os from plots import test_profile import matplotlib.pyplot as plt from PIL import Image test_dict = { "animal": { "low_age_low_education": { "mean": 21.0, "std": 7.0 }, "low_age_high_education": { "mean": 22.4, "std": 6.8 }, "high_age_low_education": { "mean": 22.1, "std": 5.7 }, "high_age_high_education": { "mean": 25.6, "std": 5.6 } }, "verb": { "low_age_low_education": { "mean": 17.6, "std": 4.3 }, "low_age_high_education": { "mean": 20.5, "std": 5.4 }, "high_age_low_education": { "mean": 16.7, "std": 6.1 }, "high_age_high_education": { "mean": 22.7, "std": 5.1 } }, "repetition": { "low_age_low_education": { "mean": 24.8, "std": 4.8 }, "low_age_high_education": { "mean": 25.9, "std": 3.7 }, "high_age_low_education": { "mean": 24.0, "std": 4.9 }, "high_age_high_education": { "mean": 25.8, "std": 3.8 } }, "months_backward": { "low_age_low_education": { "mean": 10.3, "std": 4.5 }, "low_age_high_education": { "mean": 9.8, "std": 3.2 }, "high_age_low_education": { "mean": 10.0, "std": 3.2 }, "high_age_high_education": { "mean": 9.9, "std": 3.5 } }, "logicogrammatic": { "low_age_low_education": { "mean": 27.2, "std": 3.8 }, "low_age_high_education": { "mean": 27.4, "std": 3.1 }, "high_age_low_education": { "mean": 23.5, "std": 4.2 }, "high_age_high_education": { "mean": 27.2, "std": 3.3 } },"inference": { "low_age_low_education": { "mean": 28.2, "std": 2.4 }, "low_age_high_education": { "mean": 28.4, "std": 2.8 }, "high_age_low_education": { "mean": 25.2, "std": 3.9 }, "high_age_high_education": { "mean": 27.3, "std": 2.9 } }, "reading_speed": { "low_age_low_education": { "mean": 21.5, "std": 5.4 }, "low_age_high_education": { "mean": 27.3, "std": 5.5 }, "high_age_low_education": { "mean": 23.0, "std": 7.2 }, "high_age_high_education": { "mean": 28.8, "std": 5.2 } }, "decoding_words": { "low_age_low_education": { "mean": 107.6, "std": 27.8 }, "low_age_high_education": { "mean": 112.9, "std": 22.7 }, "high_age_low_education": { "mean": 111.4, "std": 26.1 }, "high_age_high_education": { "mean": 122.9, "std": 28.2 } }, "decoding_non_words": { "low_age_low_education": { "mean": 95.8, "std": 26.6 }, "low_age_high_education": { "mean": 105.4, "std": 29.5 }, "high_age_low_education": { "mean": 103.4, "std": 25.2 }, "high_age_high_education": { "mean": 118.0, "std": 26.8 } }, # jönsson och winnerstam 2012 "pataka": { "low_age_low_education": { "mean": 5.8, "std": 1.0 }, "low_age_high_education": { "mean": 5.8, "std": 1.0 }, "high_age_low_education": { "mean": 5.8, "std": 1.0 }, "high_age_high_education": { "mean": 5.8, "std": 1.0 } } } # Function to calculate z-score def calculate_z_score(test_score, mean, std_dev): return (test_score - mean) / std_dev def z_score_calculator(value, norm_mean, norm_sd, invert=False): z_value = (value - norm_mean) / norm_sd if invert: z_value = -z_value stanine_value = zscore_to_stanine(z_value) z_score = round(z_value, 2) return z_score, stanine_value def zscore_to_stanine(zscore): if zscore <= -1.76: return 1 elif zscore <= -1.26: return 2 elif zscore <= -0.76: return 3 elif zscore <= -0.26: return 4 elif zscore <= 0.25: return 5 elif zscore <= 0.75: return 6 elif zscore <= 1.25: return 7 elif zscore <= 1.75: return 8 else: return 9 def test_calculator(age, education, values, test, invert=False): if age <= 60 and education <= 12: norm_mean = test_dict[test]["low_age_low_education"]["mean"] norm_sd = test_dict[test]["low_age_low_education"]["std"] z_score, stanine_value = z_score_calculator(values, norm_mean, norm_sd, invert=invert) return norm_mean, norm_sd, z_score, stanine_value elif age <= 60 and education > 12: norm_mean = test_dict[test]["low_age_high_education"]["mean"] norm_sd = test_dict[test]["low_age_high_education"]["std"] z_score, stanine_value = z_score_calculator(values, norm_mean, norm_sd, invert=invert) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education <= 12: norm_mean = test_dict[test]["high_age_low_education"]["mean"] norm_sd = test_dict[test]["high_age_low_education"]["std"] z_score, stanine_value = z_score_calculator(values, norm_mean, norm_sd, invert=invert) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education > 12: norm_mean = test_dict[test]["high_age_high_education"]["mean"] norm_sd = test_dict[test]["high_age_high_education"]["std"] z_score, stanine_value = z_score_calculator(values, norm_mean, norm_sd, invert=invert) return norm_mean, norm_sd, z_score, stanine_value else: print("missing value/ wrong format") def isw_calculator(age, education, isw): print("the isw is", isw) isw_score = 74.38 + isw*0.66 - age*0.20 + education*1.77 # round to nearest integer isw_score = round(isw_score) print("the score is: ", isw_score) isw_score_stanine = isw_stanine_calculator(isw_score) return 0, 0, isw_score, isw_score_stanine def isw_stanine_calculator(isw_score): print("the score is: ", isw_score) if isw_score <= 73: return 1 elif isw_score <= 81: return 2 elif isw_score <= 88: return 3 elif isw_score <= 96: return 4 elif isw_score <= 103: return 5 elif isw_score <= 111: return 6 elif isw_score <= 118: return 7 elif isw_score <= 126: return 8 elif isw_score >= 127: return 9 def bnt_calculator(age, education, bnt): if age <= 60 and education <= 12: norm_mean = 54.5 norm_sd = 3.2 z_score, stanine_value = z_score_calculator(bnt, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age <= 60 and education > 12: norm_mean = 54.0 norm_sd = 4.4 z_score, stanine_value = z_score_calculator(bnt, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education <= 12: norm_mean = 54.8 norm_sd = 3.3 z_score, stanine_value = z_score_calculator(bnt, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education > 12: norm_mean = 56.2 norm_sd = 3.4 z_score, stanine_value = z_score_calculator(bnt, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value else: print("missing value/ wrong format") def fas_calculator(age, education, fas): if age <= 60 and education <= 12: norm_mean = 42.7 norm_sd = 13.7 z_score, stanine_value = z_score_calculator(fas, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age <= 60 and education > 12: norm_mean = 46.7 norm_sd = 13.7 z_score, stanine_value = z_score_calculator(fas, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education <= 12: norm_mean = 46.9 norm_sd = 10.4 z_score, stanine_value = z_score_calculator(fas, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value elif age > 60 and education > 12: norm_mean = 51.6 norm_sd = 12.6 z_score, stanine_value = z_score_calculator(fas, norm_mean, norm_sd) return norm_mean, norm_sd, z_score, stanine_value else: print("missing value/ wrong format") def generate_graph(test_dict): # Create a plot fig, ax = plt.subplots() # Adjust the margins to fix the labels being cut off fig.subplots_adjust(left=0.25) # Set axis labels and title ax.set_xlabel('Stanine values') ax.set_ylabel('Test') # Set the x-axis to display the stanine values ax.set_xticks([1, 2, 3, 4, 5, 6, 7, 8, 9]) ax.set_xticklabels(['1', '2', '3', '4', '5', '6', '7', '8', '9']) # Set the y-axis to display the tests ax.set_yticks([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) # Set the test labels to the keys in test_dict (in reverse order) ax.set_yticklabels(reversed(list(test_dict.keys()))) # Set the range of the x-axis ax.set_xlim([0, 10]) i = 1 for test in reversed(list(test_dict.keys())): #print("the test is", test) # check if values are not empty ax.scatter(test_dict[test][4], i, s=50, color='black', label=test) i = i + 1 # Save the graph as a png file fig.savefig('test_profile.png') return 'test_profile.png' def create_pdf(test_dict, logo_path, plot_path): pdf = FPDF() pdf.add_page() pdf.set_xy(0, 0) pdf.set_font("Arial", size=12) # Add logos x_positions = [25, 85, 145] for i, logo_path in enumerate(logo_paths): pdf.image(logo_path, x=x_positions[i], y=8, w=40) pdf.set_xy(10, 50) # Add title and center it title = "Patient Summary" pdf.set_font("Times", style="B", size=12) title_width = pdf.get_string_width(title) + 6 pdf.cell((210 - title_width) / 2) pdf.cell(title_width, 10, title, 0, 1, "C") # Add z-score and center it pdf.set_font("Times", size=12) tests_per_line = 1 tests_count = len(test_dict) line_count = tests_count // tests_per_line + (1 if tests_count % tests_per_line > 0 else 0) test_index = 0 left_margin = 15 col_width = (210 - 2 * left_margin) / tests_per_line for line in range(line_count): pdf.set_x(left_margin) for test in list(test_dict.keys())[test_index:test_index + tests_per_line]: # Set the font to bold for the test value pdf.set_font("Arial", style="B", size=8) test_text = "{}: ".format(test) test_width = pdf.get_string_width(test_text) pdf.cell(test_width, 6, test_text, 0, 0, "C") # Changed the line height to 6 # Set the font to normal for the rest of the text pdf.set_font("Arial", size=8) z_score_text = "{:.2f} (Mean: {:.2f}, Std: {:.2f})".format( test_dict[test][3], test_dict[test][1], test_dict[test][2] ) z_score_width = pdf.get_string_width(z_score_text) + 2 # Reduced the additional width to 2 pdf.cell((210 / tests_per_line - test_width - z_score_width) / 2) pdf.cell(z_score_width, 6, z_score_text, 0, 0, "C") # Changed the line height to 6 test_index += 1 pdf.ln(12) # Reduced the line spacing to 12 # Add logo pdf.add_page() pdf.image(plot_path, x=10, y=20, w=200) # pdf.set_xy(10, 40) # Add tool description and center it pdf.set_xy(10, 200) pdf.set_font("Arial", size=10) description = "This PDF report was generated using the Patient Summary App." pdf.multi_cell(0, 10, description, 0, "C") # Add explanatory text about the collaboration between KI and KTH pdf.set_xy(10, 220) pdf.set_font("Arial", size=8) collaboration_text = ( "Den här PDF:en är en del av ett samarbetsprojekt mellan Karolinska Institutet (KI) och " "Kungliga Tekniska Högskolan (KTH) med målsättningen att använda artificiell intelligens (AI) och " "teknik för att minska administration i sjukhusarbete. Projektet fokuserar på att utveckla och " "implementera AI-baserade lösningar för att förbättra arbetsflöden, öka effektiviteten och " "minska den administrativa bördan för sjukvårdspersonal. För frågor om formuläret kontakta Fredrik Sand fredrik.sand-aronsson@regionstockholm.se, för frågor om teknik kontakta Birger Moëll bmoell@kth.se." ) line_width = 190 line_height = pdf.font_size_pt * 0.6 lines = collaboration_text.split(' ') current_line = '' for word in lines: if pdf.get_string_width(current_line + word) < line_width: current_line += word + ' ' else: pdf.cell(line_width, line_height, current_line, 0, 1) current_line = word + ' ' pdf.cell(line_width, line_height, current_line, 0, 1) return pdf def pdf_to_base64(pdf): with open(pdf, "rb") as file: return base64.b64encode(file.read()).decode('utf-8') # Title and description st.title("Language profile") st.write("Enter your test score, to calculate scores and create a PDF with the results.") # Input fields #test_score = st.number_input("Test Score", min_value=0, value=0, step=1) age = st.number_input("Age", min_value=0, value=18, step=1) education_level = st.number_input("Education Level in years", min_value=0, value=18, step=1) isw = st.number_input("ISW", min_value=0.0, value=0.0, step=0.01) bnt = st.number_input("BNT", min_value=0, value=0, step=1) fas = st.number_input("FAS", min_value=0, value=0, step=1) animal = st.number_input("Animal", min_value=0, value=0, step=1) verb = st.number_input("Verb", min_value=0, value=0, step=1) repetition = st.number_input("Repetition", min_value=0, value=0, step=1) logicogrammatic = st.number_input("Logicogrammatic", min_value=0, value=0, step=1) inference = st.number_input("Inference", min_value=0, value=0, step=1) reading_speed = st.number_input("Reading Speed", min_value=0, value=0, step=1) decoding_words = st.number_input("Decoding Words", min_value=0.0, value=0.0, step=0.01) decoding_non_words = st.number_input("Decoding Non-Words", min_value=0.0, value=0.0, step=0.01) months_backward = st.number_input("Months Backward", min_value=0.0, value=0.0, step=0.01) pataka = st.number_input("Pataka", min_value=0.0, value=0.0, step=0.01) # add all the tests # Calculate mean and standard deviation based on age and education level # For simplicity, we will use made-up values for mean and std_dev mean = np.random.randint(50, 100) std_dev = np.random.randint(10, 30) # Calculate z-score and display result if st.button("Calculate Z-Score"): profile = test_profile(age, education_level, isw, bnt, fas) # calculate the isw iq score isw_mean, isw_std, isw_iq, isw_stanine = isw_calculator(age, education_level, isw) # for each value in the profile, calculate the z-score bnt_mean, bnt_std, z_bnt, stanine_bnt = bnt_calculator(age, education_level, bnt) fas_mean, fas_std, z_fas, stanine_fas = fas_calculator(age, education_level, fas) animal_mean, animal_std, animal_z, animal_stanine = test_calculator(age, education_level, animal, "animal") verb_mean, verb_std, verb_z, verb_stanine = test_calculator(age, education_level, verb, "verb") repetition_mean, repetition_std, repetition_z, repetition_stanine = test_calculator(age, education_level, repetition, "repetition") months_backward_mean, months_backward_std, months_backward_z, months_backward_stanine = test_calculator(age, education_level, months_backward, "months_backward", invert=True) # months_backward_z = months_backward_z * -1 # print("the months backw z is: ", months_backward_z) logicogrammatic_mean, logicogrammatic_std, logicogrammatic_z, logicogrammatic_stanine = test_calculator(age, education_level, logicogrammatic, "logicogrammatic") inference_mean, inference_std, inference_z, inference_stanine = test_calculator(age, education_level, inference, "inference") reading_speed_mean, reading_speed_std, reading_speed_z, reading_speed_stanine = test_calculator(age, education_level, reading_speed, "reading_speed") decoding_words_mean, decoding_words_std, decoding_words_z, decoding_words_stanine = test_calculator(age, education_level, decoding_words, "decoding_words") decoding_non_words_mean, decoding_non_words_std, decoding_non_words_z, decoding_non_words_stanine = test_calculator(age, education_level, decoding_non_words, "decoding_non_words") pataka_mean, pataka_std, pataka_z, pataka_stanine = test_calculator(age, education_level, pataka, "pataka") ## add all the tests with their values to an array ## CREATA A DICTORINARY WITH ALL THE TESTS AND THEIR Z-SCORES AND STANINE VALUES test_dict = { "bnt": [bnt, bnt_mean, bnt_std, z_bnt, stanine_bnt], "fas": [fas, fas_mean, fas_std, z_fas, stanine_fas], "isw": [isw, isw_mean, isw_std, isw_iq, isw_stanine], "animal": [animal, animal_mean, animal_std, animal_z, animal_stanine], "verb": [verb, verb_mean, verb_std, verb_z, verb_stanine], "repetition": [repetition, repetition_mean, repetition_std, repetition_z, repetition_stanine], "months_backward": [months_backward, months_backward_mean, months_backward_std, months_backward_z, months_backward_stanine], "logicogrammatic": [logicogrammatic, logicogrammatic_mean, logicogrammatic_std, logicogrammatic_z, logicogrammatic_stanine], "inference": [inference, inference_mean, inference_std, inference_z, inference_stanine], "reading_speed": [reading_speed, reading_speed_mean, reading_speed_std, reading_speed_z, reading_speed_stanine], "decoding_words": [decoding_words, decoding_words_mean, decoding_words_std, decoding_words_z, decoding_words_stanine], "decoding_non_words": [decoding_non_words, decoding_non_words_mean, decoding_non_words_std, decoding_non_words_z, decoding_non_words_stanine], "pataka": [pataka, pataka_mean, pataka_std, pataka_z, pataka_stanine] } # z_values = [z_bnt, z_fas, animal_z, verb_z, repetition_z, months_backward_z, logicogrammatic_z, inference_z, reading_speed_z, decoding_words_z, decoding_non_words_z, pataka_z] # ## add all the stanines to a list # stanine_values = [stanine_bnt, stanine_fas, animal_stanine, verb_stanine, repetition_stanine, months_backward_stanine, logicogrammatic_stanine, inference_stanine, reading_speed_stanine, decoding_words_stanine, decoding_non_words_stanine, pataka_stanine] # loop over and write out all the z values # loop over all the values in test dict and print out the z-score, mean and std_dev and stanine for key, value in test_dict.items(): st.write(f"Your {key} z-score is: {value[3]:.2f}") st.write(f"Mean: {value[1]}, Standard Deviation: {value[2]}") st.write(f"Stanine: {value[4]}") # Create PDF logo_paths = ["logo.jpg", "logo2.jpg", "logo3.jpg"] # create the plot from the dataframe # check if education level is more than 12 years, if more than 12, set value to one, otherwise zero plot_path = generate_graph(test_dict) # create an image from the plot and add to streamlit display image = Image.open(plot_path) st.image(image, caption='Stanine plot', use_column_width=True) pdf_filename = "z_score_report.pdf" pdf = create_pdf(test_dict, logo_paths, plot_path) pdf.output(name=pdf_filename) # Download PDF with open(pdf_filename, "rb") as file: base64_pdf = base64.b64encode(file.read()).decode('utf-8') pdf_display = f'Download PDF' st.markdown(pdf_display, unsafe_allow_html=True) # Remove PDF file after download if os.path.exists(pdf_filename): os.remove(pdf_filename)