From dc888ad3228b1e3ad3a7320d12142e7d4c38de2e Mon Sep 17 00:00:00 2001 From: Wojciech Wojnowski <wojciech.wojnowski@pg.edu.pl> Date: Sat, 5 Oct 2024 13:04:39 +0000 Subject: [PATCH] Upload New File --- main.py | 416 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..4ed6b1b --- /dev/null +++ b/main.py @@ -0,0 +1,416 @@ +import tkinter as tk +from tkinter import ttk +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from tkinter import filedialog +from matplotlib import colormaps + +# Start the main app: +root = tk.Tk() +root.title('RAPI beta 0.7') +root.geometry('1000x600') +root.iconbitmap('Icon.ico') + +# Define the color palette. This can be based on https://materialui.co/colors +colors = { + 'foreground': '#ffffff', + 'background': '#EEEEEE', + 'accent': '#FFCC80', + 'text': 'black', + 'inactive': '#AAAAAA' # Grayed-out color for inactive elements +} + +# Configure the background: +root.configure(bg=colors['background'], padx=0, pady=0) + +# Create and configure a style to be used in tabs, labels, and other elements +s = ttk.Style() +s.theme_create("RAPI_theme", parent="alt", settings={ + 'TLabel': { + 'configure': { + 'background': colors['foreground'], + 'foreground': colors['text'], + 'padding': [0, 5, 7, 0]}}, + 'TFrame': { + 'configure': { + 'background': colors['foreground'], + 'borderwidth': 0, + 'highlightcolor': 'red'}}, + 'TRadiobutton': { + 'configure': { + 'background': colors['foreground'], + 'foreground': colors['text'], + 'padding': [5, 0, 0, 0], + }}, + 'TMenubutton': { + 'configure': { + 'background': colors['accent'], + 'foreground': colors['text'], + 'padding': [5, 0, 0, 0], + 'width': '30' + }}, + 'TMenu': { + 'configure': { + 'background': colors['accent'], + 'foreground': colors['text'], + 'width': '20', + 'arrowcolor': colors['text'], + }}, +}) + +s.configure('RAPI_theme', relief='flat', + background=colors['background'], + foreground=colors['text'], + insertcolor=colors['text'], + selectbackground=colors['accent'] + ) + +s.theme_use("RAPI_theme") + + +def save_image(): + ftypes = [('PNG file', '.png'), ('SVG file', '.svg'), ('All files', '*')] + filename = filedialog.asksaveasfilename(filetypes=ftypes, defaultextension='.png') + plt.savefig(filename, bbox_inches='tight', dpi=300) + + +def reset_scores(): + for criterion in criteria: + criterion.valuevar.set(0.0) + criterion.color.set('white') + for dropdown in dropdowns: + dropdown.reset_option() + + chromatography_var.set('no') + toggle_chromatography() # Resets state according to radio button + create_plot() + + +def call_info_popup(): + win = tk.Toplevel() + win_frame = tk.Frame(win, width=500, background=colors['foreground']) + win_frame.pack() + win.iconbitmap('Icon.ico') + win.wm_title('About and citation info') + text0 = ttk.Label(win_frame, text='RAPI is a metric tool for ...', + wraplength=380, justify='left') + text0.grid(column=0, row=0, padx=8, pady=8, sticky='w') + text1 = ttk.Label(win_frame, text='If you use RAPI, please cite:', wraplength=280, justify='left') + text1.grid(column=0, row=1, padx=8, pady=8, sticky='w') + text2 = tk.Text(win_frame, width=50, height=6, wrap='word', bg=colors['background']) + citation = 'PaweĹ Mateusz Nowak, Wojciech Wojnowski, Natalia Manousi, Victoria Samanidou, Justyna PĹotka-Wasylka,' \ + 'Red Analytical Performance Index (RAPI) and software: the missing tool for assessing methods' \ + 'in terms of analytical effectiveness, doi.org/XX.XXXX/XXXXXXXXXX' + text2.grid(column=0, row=2, padx=8, pady=8, sticky='w') + text2.insert(tk.END, citation) + text3 = ttk.Label(win_frame, text='Most recent version of the source code can be found at:', wraplength=320, + justify='left') + text3.grid(column=0, row=3, padx=8, pady=8, sticky='w') + text4 = tk.Text(win_frame, width=50, height=1, wrap='word', bg=colors['background']) + source_link = 'git.pg.edu.pl/p174235/rapi' + text4.grid(column=0, row=4, padx=8, pady=8, sticky='w') + text4.insert(tk.END, source_link) + text5 = ttk.Label(win_frame, text='Š 2024 P. Nowak, W. Wojnowski, N. Manousi, V. Samanidou, J. PĹotka-Waskylka, '\ + 'available under the MIT License', wraplength=380, + justify='left') + text5.grid(column=0, row=5, padx=8, pady=8, sticky='w') + + +def create_menu(): + menu = tk.Menu() + root.config(menu=menu) + file_menu = tk.Menu(menu) + file_menu.config(background=colors['background'], + activeborderwidth=5, + activeforeground=colors['text'], + foreground=colors['text'], + tearoff=0) + + file_menu.add_command(label='Save the image', command=save_image) + file_menu.add_command(label='Re-set', command=reset_scores) + about_menu = tk.Menu(menu) + about_menu.config(background=colors['background'], + activeborderwidth=5, + activeforeground=colors['text'], + foreground=colors['text'], + tearoff=0) + menu.add_cascade(label='File', menu=file_menu) + menu.add_cascade(label='About', menu=about_menu) + about_menu.add_command(label='Info', command=call_info_popup) + + +create_menu() + +coordinates = { + 0: [(-8.719, 12), (-14.107, -4.584), (0, -14.833), (14.107, -4.584), (8.719, 12)], + 1: [(8.719, 12), (21.796, 30), (-21.796, 30), (-8.719, 12)], + 2: [(20.497, 34), (8.199, 50.927), (-8.199, 50.927), (-20.497, 34)], + 3: [(-14.107, -4.584), (-35.267, -11.459), (0, -37.082), (0, -14.833)], + 4: [(0, -14.833), (0, -37.082), (35.267, -11.459), (14.107, -4.584)], + 5: [(14.107, -4.584), (35.267, -11.459), (21.796, 30), (8.719, 12)], + 6: [(-8.719, 12), (-21.796, 30), (-35.267, -11.459), (-14.107, -4.584)], + 7: [(-26.002, 30), (-45.901, 23.535), (-50.968, 7.94), (-38.67, -8.987)], + 8: [(38.67, -8.987), (50.968, 7.94), (45.901, 23.535), (26.002, 30)], + 9: [(-36.567, -15.459), (-36.567, -36.382), (-23.301, -46.02), (-3.402, -39.554)], + 10: [(36.567, -15.459), (36.567, -36.382), (23.301, -46.02), (3.402, -39.554)], +} + +color_values = { + 'white': 0, + '#fff5f0': 2.5, + '#fca183': 5, + '#e53228': 7.5, + '#67000d': 10} + +# Create the two main frames of the GUI +left_frame = tk.Frame(root, bd=0, padx=2, pady=0, width=500, height=500, bg=colors['foreground'], + highlightbackground=colors['background'], highlightthickness=8) +left_frame.pack(side='left', anchor='n', fill='both', expand=True) + +right_frame = tk.Frame(root, bd=0, padx=2, pady=0, width=500, height=500, bg=colors['background'], + highlightbackground=colors['background'], highlightthickness=8, + highlightcolor=colors['background']) +right_frame.pack(side='right', anchor='n') + + +class Criterion: + def __init__(self, number): + self.number = number + self.valuevar = tk.DoubleVar() + self.valuevar.set(0.0) + self.color = tk.StringVar() + self.color.set('white') + self.optionvar = tk.StringVar() + self.optionvar.set('select') + self.coordinates = coordinates[number] + + +criteria = [] +for i in range(0, 11): + criteria.append(Criterion(i)) + + +def clear_frame(frame): + frame.destroy() + global right_frame + right_frame = tk.Frame(root, bd=0, padx=2, pady=0, width=500, height=500, bg=colors['foreground'], + highlightbackground=colors['background'], highlightthickness=8, + highlightcolor=colors['background']) + right_frame.pack(side='right', anchor='n') + + +def calculate_score(): + active_criteria = criteria[1:9] if chromatography_var.get() == 'no' else criteria[1:11] + score = sum(color_values[c.color.get()] for c in active_criteria) + return round(score / len(active_criteria) * 10, 1) + + +def create_plot(event=None): + plt.close() + clear_frame(right_frame) + + fig, ax = plt.subplots(figsize=(10, 10), dpi=150, facecolor=colors['foreground']) + + for i, criterion in enumerate(criteria[1:], start=1): + face_color = criterion.color.get() + edge_color = 'black' + if chromatography_var.get() == 'no' and i in (9, 10): + edge_color = 'white' + face_color = 'white' # Hide polygons for criteria 9 and 10 + + polygon = plt.Polygon(criterion.coordinates, facecolor=face_color, edgecolor=edge_color, + joinstyle='round') + ax.add_patch(polygon) + + ax.autoscale() + ax.set_aspect('equal') + center_score = calculate_score() + + central_polygon_color = 'white' if center_score == 0 else colormaps['Reds'](int(255 * (center_score / 100))) + central_polygon = plt.Polygon(criteria[0].coordinates, facecolor=central_polygon_color, edgecolor='black') + ax.add_patch(central_polygon) + + text_color = 'white' if center_score >= 80 else 'black' + ax.text(0, 0, str(center_score), ha='center', va='center', fontsize=14, color=text_color) + + ax.axis('off') + + canvas = FigureCanvasTkAgg(figure=fig, master=right_frame) + plot_widget = canvas.get_tk_widget() + plot_widget.pack(side='top', padx=8, pady=8) + + +class Dropdown: + def __init__(self, criterion, options, label_text, row): + self.var = criterion.color + self.options = options + self.row = row + self.dropdown_var = tk.StringVar(value='not confirmed') + + # Create the dropdown menu + self.dropdown_menu = ttk.OptionMenu(left_frame, self.dropdown_var, self.dropdown_var.get(), + *self.options.keys()) + self.dropdown_menu.config(width=50) # Adjust the width here + self.dropdown_menu.grid(row=self.row + 1, column=0, padx=8, sticky='W') + + # Initialize label + self.label = ttk.Label(left_frame, text=label_text) + self.label.grid(row=self.row, column=0, padx=8, sticky='W') + + # Overlay label for inactive state + # Using a lighter gray to ensure text remains visible + self.overlay = tk.Label(left_frame, bg='#DDDDDD', width=20, height=1) + self.overlay.place_forget() # Initially hidden + + # Trace changes + self.dropdown_var.trace('w', self.change_dropdown) + + def change_dropdown(self, *args): + self.var.set(self.options[self.dropdown_var.get()]) + create_plot() + + def reset_option(self): + self.dropdown_var.set('not confirmed') + self.var.set('white') + + def set_inactive(self, inactive=True): + if inactive: + self.overlay.place(in_=self.dropdown_menu, relwidth=1, relheight=1) + else: + self.overlay.place_forget() + + +def toggle_chromatography(*args): + if chromatography_var.get() == 'yes': + dropdown9.label.config(foreground=colors['text']) # Active state color + dropdown10.label.config(foreground=colors['text']) + dropdown9.dropdown_menu.config(state='normal') + dropdown10.dropdown_menu.config(state='normal') + dropdown9.set_inactive(False) + dropdown10.set_inactive(False) + else: + dropdown9.reset_option() + dropdown10.reset_option() + dropdown9.dropdown_menu.config(state='disabled') + dropdown10.dropdown_menu.config(state='disabled') + dropdown9.label.config(foreground=colors['inactive']) # Inactive state color + dropdown10.label.config(foreground=colors['inactive']) + dropdown9.set_inactive(True) + dropdown10.set_inactive(True) + create_plot() + + + +# Dropdowns +dropdown1 = Dropdown(criterion=criteria[1], options={ + 'not confirmed': 'white', + 'error > 20%': '#fff5f0', + '10% < error ⤠20%': '#fca183', + '5% < error ⤠10%': '#e53228', + 'error ⤠5%': '#67000d'}, + row=1, + label_text='1. Trueness (relative error, %)') + +dropdown2 = Dropdown(criterion=criteria[2], options={ + 'not confirmed': 'white', + 'recovery < 80%, recovery > 120%': '#fff5f0', + 'recovery â (80% : 90%âŠ, â¨110% : 120%)': '#fca183', + 'recovery â (90% : 95%âŠ, â¨105% : 110%)': '#e53228', + 'recovery > 95%, recovery < 105%': '#67000d'}, + row=3, + label_text='2. Recovery (%)') + +dropdown3 = Dropdown(criterion=criteria[3], options={ + 'not confirmed': 'white', + 'RSD > 20%': '#fff5f0', + '10% < RSD ⤠20%': '#fca183', + '5% < RSD ⤠10%': '#e53228', + 'RSD ⤠5%': '#67000d'}, + row=5, label_text='3. Intra-day precision (RSD, %)') + +dropdown4 = Dropdown(criterion=criteria[4], options={ + 'not confirmed': 'white', + 'RSD > 25%': '#fff5f0', + '15% < RSD ⤠25%': '#fca183', + '7% < RSD ⤠15%': '#e53228', + 'RSD ⤠7%': '#67000d'}, + row=7, label_text='4. Inter-day precision (RSD, %)') + +dropdown5 = Dropdown(criterion=criteria[5], options={ + 'not confirmed': 'white', + 'LOQ > 50% e.c.': '#fff5f0', + '10% e.c. < LOQ ⤠50% e.c.': '#fca183', + '1% e.c. < LOQ ⤠10% e.c.': '#e53228', + 'LOQ ⤠1% e.c.': '#67000d'}, + row=9, label_text='5. LOQ (%)') + +dropdown6 = Dropdown(criterion=criteria[6], options={ + 'not confirmed': 'white', + '< (LOQ : 10ĂLOQ)': '#fff5f0', + '⼠(LOQ : 10ĂLOQ), ⤠(LOQ : 30ĂLOQ)': '#fca183', + '⼠(LOQ : 30ĂLOQ), ⤠(LOQ : 100ĂLOQ)': '#e53228', + '> (LOQ : 100ĂLOQ)': '#67000d'}, + row=11, label_text='6. Calibration (linearity) range') + +dropdown7 = Dropdown(criterion=criteria[7], options={ + 'not confirmed': 'white', + 'linearity < 0.900': '#fff5f0', + '0.990 > linearity ⼠0.900': '#fca183', + '0.999 > linearity ⼠0.990': '#e53228', + 'linearity ⼠0.999': '#67000d'}, + row=13, label_text='7. Linearity (R2)') + +dropdown8 = Dropdown(criterion=criteria[8], options={ + 'not confirmed': 'white', + 'confirmed against 1 factor': '#fff5f0', + 'confirmed against 2 factors': '#fca183', + 'confirmed against 3 factors': '#e53228', + 'confirmed against > 3 factors': '#67000d'}, + row=15, label_text='8. Robustness') + +chrom_label = ttk.Label(left_frame, text='Does the method involve chromatographic separation?') +chrom_label.grid(row=17, column=0, padx=8, sticky='W') + +chromatography_var = tk.StringVar(value='no') +chrom_radio1 = ttk.Radiobutton(left_frame, text='Yes', variable=chromatography_var, value='yes', style='TRadiobutton') +chrom_radio2 = ttk.Radiobutton(left_frame, text='No', variable=chromatography_var, value='no', style='TRadiobutton') +chrom_radio1.grid(row=18, column=0, sticky='W', padx=8) +chrom_radio2.grid(row=18, column=0, sticky='W', padx=58) + +# Configure the radio buttons to avoid dark frame highlight on selection +chrom_radio1.configure(takefocus=False) +chrom_radio2.configure(takefocus=False) + +chromatography_var.trace('w', toggle_chromatography) + +dropdown9 = Dropdown(criterion=criteria[9], options={ + 'not confirmed': 'white', + '⤠1 000': '#fff5f0', + 'â (1 000 : 10 000âŠ': '#fca183', + 'â (10 000 : 100 000âŠ': '#e53228', + '> 100 000': '#67000d'}, + row=19, label_text='9. Mean no. of theoretical plates') + +dropdown10 = Dropdown(criterion=criteria[10], options={ + 'not confirmed': 'white', + 'resolution < 0.5': '#fff5f0', + '1.0 > resolution ⼠0.5': '#fca183', + '1.5 > resolution ⼠1.0': '#e53228', + 'resolution ⼠1.5': '#67000d'}, + row=21, label_text='10. Resolution of the worst-separated peaks') + +# Set initial states for dropdown labels and states +toggle_chromatography() + +dropdowns = [dropdown1, dropdown2, dropdown3, dropdown4, dropdown5, dropdown6, dropdown7, dropdown8, dropdown9, + dropdown10] + +create_plot() + + +def main(): + while True: + root.mainloop() + + +main() -- GitLab