Download PyBDC/PyBDC.py from didsr/PyBDC-Container: direct link, hf CLI and curl.
- Browser
- Download file 34.4 kB
-
https://huggingface.co/didsr/PyBDC-Container/resolve/main/PyBDC/PyBDC.py
- Command line
-
hf download hf://didsr/PyBDC-Container/PyBDC/PyBDC.py
-
curl -L -o PyBDC.py https://huggingface.co/didsr/PyBDC-Container/resolve/main/PyBDC/PyBDC.py
34.4 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Created on Tue Feb 28 13:21:41 2023 | |
| @author: DIDSR | |
| """ | |
| # %% import necessary modules | |
| import argparse | |
| from tkinter import filedialog, Menu, IntVar, W | |
| import customtkinter | |
| import numpy as np | |
| from matplotlib.pyplot import ( | |
| style as plt_style, | |
| ioff as plt_ioff, | |
| figure as plt_figure, | |
| rcParams as params, | |
| ) | |
| plt_ioff() # suppress pyplot popups | |
| from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg | |
| import tomli | |
| from dose_equations import ( | |
| Sarno_mono_dgn, | |
| Sarno_poly_dgn, | |
| sarno_dgnct, | |
| Hernandez_hetero_mono_dgn, | |
| exposure_per_fluence, | |
| Sechopoulos_poly_dgn, | |
| ) | |
| # %% to set more appropriate font size for the UI (example, running from container may require UI to have larger font size) | |
| dict_font_size_set = {1: 14, 2: 24} | |
| font_size = None | |
| # %% important functions | |
| # quit me function | |
| def quit_me(): | |
| root.quit() | |
| root.destroy() | |
| # calculate polyenergetic normalized glandular dose coefficients (pDgN) from specified breast models | |
| def calculate_pDgNct(*values): | |
| keV = values[2] | |
| I = values[3] | |
| psiE = np.array(list(map(exposure_per_fluence, keV))) | |
| if values[0] == "Sarno Koning": | |
| variables = values[1] | |
| DgNctE = np.array( | |
| list( | |
| map( | |
| sarno_dgnct, | |
| variables[:, 0], | |
| variables[:, 1], | |
| variables[:, 2], | |
| variables[:, 3], | |
| variables[:, 4], | |
| variables[:, 5], | |
| variables[:, 6], | |
| variables[:, 7], | |
| keV, | |
| ) | |
| ) | |
| ) | |
| pDgN = np.sum(I * psiE * DgNctE) / np.sum(I * psiE) | |
| elif values[0] == "Hernandez": | |
| DgN_list = np.array(values[1]) | |
| pDgN = np.sum(I * psiE * DgN_list) / (np.sum(I * psiE)) | |
| return pDgN | |
| # read method outputs display in GUI specifications for certian methods | |
| with open("method_specific_inputs.toml", "rb") as file_method_specific_inputs: | |
| config_method_specific_inputs = tomli.load(file_method_specific_inputs) | |
| # %%% Main code | |
| class Main_Window: | |
| # %% build the gui frames | |
| def __init__(self, master): | |
| self.master = master | |
| # create the menubar | |
| menubar = Menu(master) | |
| root.config(menu=menubar) | |
| helpmenu = Menu(menubar, tearoff=0) | |
| helpmenu.add_command(label="Quick Start Guide", command=self.help_command) | |
| menubar.add_cascade(label="Help", menu=helpmenu) | |
| menubar.add_command(label="Exit", command=lambda: quit_me()) | |
| # create the different frames | |
| self.methods_frame = customtkinter.CTkFrame(master=master) | |
| self.methods_frame.grid(row=0, column=0, ipady=36) | |
| self.inputs_frame = customtkinter.CTkFrame(master=master) | |
| self.inputs_frame.grid(row=0, column=1, padx=10, pady=5, ipadx=6) | |
| self.kerma_spec_frame = customtkinter.CTkFrame(master=master) | |
| self.kerma_spec_frame.grid(row=0, column=2, pady=5, ipady=24, ipadx=4) | |
| self.output_frame = customtkinter.CTkFrame(master=master) | |
| self.output_frame.grid(row=1, column=0, ipady=12) | |
| self.graph_frame = customtkinter.CTkFrame(master=master) | |
| self.graph_frame.grid(row=1, column=1, columnspan=2, padx=4, ipady=14) | |
| # create figure | |
| self.keV = [] | |
| self.I = [] | |
| self.plot_spectra() | |
| # %% add the different methods | |
| self.method_label = customtkinter.CTkLabel( | |
| master=self.methods_frame, | |
| text="Choose any of the following BCT methods:", | |
| font=("Roman", font_size), | |
| ) | |
| self.method_chosen = IntVar(root) | |
| self.method_chosen.set(2) | |
| self.current_method = 1 | |
| self.Sarno_radiobutton = customtkinter.CTkRadioButton( | |
| master=self.methods_frame, | |
| text="Sarno 49 kVp W Spectra", | |
| command=lambda: self.change_inputs(), | |
| variable=self.method_chosen, | |
| value=1, | |
| radiobutton_width=14, | |
| radiobutton_height=14, | |
| font=("Roman", font_size), | |
| ) | |
| self.Sarno_incident_radiobutton = customtkinter.CTkRadioButton( | |
| master=self.methods_frame, | |
| text="Sarno Any Spectrum", | |
| command=lambda: self.change_inputs(), | |
| variable=self.method_chosen, | |
| value=2, | |
| radiobutton_width=14, | |
| radiobutton_height=14, | |
| font=("Roman", font_size), | |
| ) | |
| self.Hernandez_radiobutton = customtkinter.CTkRadioButton( | |
| master=self.methods_frame, | |
| text="Hernandez Any Spectrum", | |
| command=lambda: self.change_inputs(), | |
| variable=self.method_chosen, | |
| value=3, | |
| radiobutton_width=14, | |
| radiobutton_height=14, | |
| font=("Roman", font_size), | |
| ) | |
| self.Sechopoulos_radiobutton = customtkinter.CTkRadioButton( | |
| master=self.methods_frame, | |
| text="Sechopoulos 49 kVp W Spectra", | |
| command=lambda: self.change_inputs(), | |
| variable=self.method_chosen, | |
| value=4, | |
| radiobutton_width=14, | |
| radiobutton_height=14, | |
| font=("Roman", font_size), | |
| ) | |
| self.method_label.pack(padx=20, pady=5, anchor=W) | |
| self.Sarno_incident_radiobutton.pack(padx=20, pady=5, anchor=W) | |
| self.Hernandez_radiobutton.pack(padx=20, pady=5, anchor=W) | |
| self.Sarno_radiobutton.pack(padx=20, pady=5, anchor=W) | |
| self.Sechopoulos_radiobutton.pack(padx=20, pady=5, anchor=W) | |
| # %% fill in the main input box | |
| self.Breast_diameter_label = customtkinter.CTkLabel( | |
| master=self.inputs_frame, | |
| text="Breast Diameter (cm):", | |
| font=("Roman", font_size), | |
| ) | |
| self.Breast_diameter_combo = customtkinter.CTkComboBox( | |
| master=self.inputs_frame, | |
| values=["8", "10", "12", "14", "19", "18"], | |
| width=120, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.Breast_diameter_combo.set("8") | |
| self.Breast_height_label = customtkinter.CTkLabel( | |
| master=self.inputs_frame, text="Breast Height:", font=("Roman", font_size) | |
| ) | |
| self.Breast_height_combo = customtkinter.CTkComboBox( | |
| master=self.inputs_frame, | |
| values=["1 x radius", "1.5 x radius", "2 x radius"], | |
| width=120, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.Breast_height_combo.set("1 x radius") | |
| self.Breast_glandularity_label = customtkinter.CTkLabel( | |
| master=self.inputs_frame, | |
| text="Breast Glandularity:", | |
| font=("Roman", font_size), | |
| ) | |
| self.Breast_glandularity_combo = customtkinter.CTkComboBox( | |
| master=self.inputs_frame, | |
| values=["0.1%", "14.3%", "25%", "50%", "100%"], | |
| width=120, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.Breast_glandularity_combo.set("0.1%") | |
| self.HVL_label = customtkinter.CTkLabel( | |
| master=self.inputs_frame, text="HVL (mm Al):", font=("Roman", font_size) | |
| ) | |
| self.HVL_combo = customtkinter.CTkComboBox( | |
| master=self.inputs_frame, | |
| values=["1.25", "1.30", "1.35", "1.40", "1.45", "1.50"], | |
| width=120, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.HVL_combo.set("1.25") | |
| self.VGF_label = customtkinter.CTkLabel( | |
| master=self.inputs_frame, | |
| text="Heterogeneous Categories:", | |
| font=("Roman", font_size), | |
| ) | |
| self.VGF_combo = customtkinter.CTkComboBox( | |
| master=self.inputs_frame, | |
| values=["V1 = 19.9%", "V3 = 9.5%", "V5 = 3.8%"], | |
| width=120, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) # V1 = 19.9%, V3 = 9.5%, V5 = 3.8% | |
| self.VGF_combo.set("V1 = 19.9%") | |
| self.input_spectra_button = customtkinter.CTkButton( | |
| master=self.inputs_frame, | |
| fg_color=("black", "lightgray"), | |
| width=100, | |
| border_width=0, | |
| corner_radius=2, | |
| text="Upload Incident Spectrum File", | |
| font=("Roman", font_size), | |
| command=lambda: self.browse_files(), | |
| ) | |
| self.Breast_diameter_label.grid(row=0, column=0, pady=5, padx=4, sticky=W) | |
| self.Breast_diameter_combo.grid(row=0, column=1, pady=5, padx=10, sticky=W) | |
| self.Breast_height_label.grid(row=1, column=0, pady=5, padx=4, sticky=W) | |
| self.Breast_height_combo.grid(row=1, column=1, pady=5) | |
| self.Breast_glandularity_label.grid(row=2, column=0, pady=5, padx=4, sticky=W) | |
| self.Breast_glandularity_combo.grid(row=2, column=1, pady=5) | |
| self.HVL_label.grid(row=3, column=0, pady=5, padx=4, sticky=W) | |
| self.HVL_combo.grid(row=3, column=1, pady=5) | |
| self.HVL_combo.configure(state="disabled") | |
| self.VGF_label.grid(row=4, column=0, pady=5, padx=4, sticky=W) | |
| self.VGF_combo.grid(row=4, column=1, pady=5, padx=12, sticky=W) | |
| self.VGF_combo.configure(state="disabled") | |
| self.input_spectra_button.grid(row=5, column=0, pady=5, columnspan=2) | |
| # %% create text box and buttons for output frame | |
| self.output_textbox = customtkinter.CTkTextbox( | |
| self.output_frame, width=285, height=305, font=("Roman", font_size) | |
| ) | |
| self.output_textbox.tag_config("green", foreground="green") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.insert( | |
| "end", | |
| f'{"".join(config_method_specific_inputs["method_specific_inputs"]["Sarno_specific_outputs"])}', | |
| "green", | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| self.clear_button = customtkinter.CTkButton( | |
| master=self.output_frame, | |
| width=120, | |
| border_width=0, | |
| corner_radius=8, | |
| text="Clear Text", | |
| font=("Roman", font_size), | |
| command=lambda: self.clear_text(), | |
| ) | |
| self.calculate_button = customtkinter.CTkButton( | |
| master=self.output_frame, | |
| width=120, | |
| border_width=0, | |
| corner_radius=8, | |
| text="Calculate Dose", | |
| font=("Roman", font_size), | |
| command=lambda: self.calculate_dose(), | |
| ) | |
| self.clear_button.grid(row=1, column=0, pady=5, padx=4) | |
| self.output_textbox.grid(row=0, column=0, columnspan=3) | |
| self.output_textbox.configure(state="disabled") # insert at line 0 character 0 | |
| self.calculate_button.grid(row=1, column=1, pady=5, padx=4) | |
| # %% create air kerma inputs | |
| self.mAs_label = customtkinter.CTkLabel( | |
| master=self.kerma_spec_frame, | |
| text="mAs per Projection:", | |
| font=("Roman", font_size), | |
| ) | |
| self.mAs_entry = customtkinter.CTkEntry(master=self.kerma_spec_frame, width=80) | |
| self.mAs_units_combo = customtkinter.CTkComboBox( | |
| master=self.kerma_spec_frame, | |
| values=["mAs"], | |
| width=80, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.air_kerma_label = customtkinter.CTkLabel( | |
| master=self.kerma_spec_frame, | |
| text="Air kerma per Projection:", | |
| font=("Roman", font_size), | |
| ) | |
| self.air_kerma_entry = customtkinter.CTkEntry( | |
| master=self.kerma_spec_frame, width=80 | |
| ) | |
| self.input_label = customtkinter.CTkLabel( | |
| master=self.kerma_spec_frame, | |
| text="Air kerma Units:", | |
| font=("Roman", font_size), | |
| ) | |
| self.air_kerma_units_combo = customtkinter.CTkComboBox( | |
| master=self.kerma_spec_frame, | |
| values=["mrad", "mGy", "R", "mR"], | |
| width=80, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.air_kerma_units_combo.set("R") | |
| self.air_kerma_output_label = customtkinter.CTkLabel( | |
| master=self.kerma_spec_frame, | |
| text="MGD Units:", | |
| anchor=W, | |
| font=("Roman", font_size), | |
| ) | |
| self.output_units = customtkinter.CTkComboBox( | |
| master=self.kerma_spec_frame, | |
| values=["mrad", "mGy"], | |
| width=80, | |
| state="readonly", | |
| font=("Roman", font_size), | |
| ) | |
| self.output_units.set("mrad") | |
| self.number_projections_label = customtkinter.CTkLabel( | |
| master=self.kerma_spec_frame, | |
| text="Number of Projections: ", | |
| font=("Roman", font_size), | |
| ) | |
| self.number_projections_entry = customtkinter.CTkEntry( | |
| master=self.kerma_spec_frame, width=80 | |
| ) | |
| self.graph_spectra = customtkinter.CTkButton( | |
| master=self.kerma_spec_frame, | |
| width=220, | |
| border_width=1, | |
| corner_radius=10, | |
| text="Graph Spectrum", | |
| font=("Roman", font_size), | |
| command=self.plot_spectra, | |
| ) | |
| self.air_kerma_label.grid(row=1, column=0, pady=5, padx=4, sticky=W) | |
| self.air_kerma_entry.grid(row=1, column=1, pady=5, padx=12, sticky=W) | |
| self.number_projections_label.grid(row=2, column=0, pady=5, padx=4, sticky=W) | |
| self.number_projections_entry.grid(row=2, column=1, pady=5, padx=12, sticky=W) | |
| self.mAs_label.grid(row=5, column=0, pady=5, padx=4, sticky=W) | |
| self.mAs_entry.grid(row=5, column=1, pady=5, padx=12, sticky=W) | |
| self.input_label.grid(row=3, column=0, pady=5, padx=4, sticky=W) | |
| self.air_kerma_units_combo.grid(row=3, column=1, pady=5, padx=12, sticky=W) | |
| self.air_kerma_output_label.grid(row=4, column=0, pady=5, padx=4, sticky=W) | |
| self.output_units.grid(row=4, column=1, pady=5, padx=12, sticky=W) | |
| self.graph_spectra.grid(row=6, column=0, pady=5, columnspan=2) | |
| self.graph_spectra.configure(state="disabled") | |
| # %% important functions | |
| # add help command | |
| def help_command(self): | |
| pop_up = customtkinter.CTkToplevel() | |
| textbox = customtkinter.CTkTextbox(master=pop_up, width=800, height=500) | |
| textbox.pack(fill="both") | |
| with open("CT_Dose_Calculate_Quick_Guide.txt", "r") as file: | |
| data = file.read() | |
| textbox.insert("end", f"{data}") | |
| textbox.configure(state="disabled") | |
| # change the method specific inputs based on selection | |
| def change_inputs(self): | |
| current_method = self.method_chosen.get() | |
| if current_method == 1: # Sarno 49 kVp W Spectra | |
| # enable and disable buttons | |
| self.VGF_combo.configure(state="disabled") | |
| self.input_spectra_button.configure(state="disabled") | |
| self.Breast_diameter_combo.configure(state="normal") | |
| self.Breast_height_combo.configure(state="normal") | |
| self.Breast_glandularity_combo.configure(state="normal") | |
| self.HVL_combo.configure(state="normal") | |
| self.graph_spectra.configure(state="disabled") | |
| self.Breast_glandularity_combo.configure( | |
| values=["0.1%", "14.3%", "25%", "50%", "75%", "100%"] | |
| ) | |
| self.Breast_glandularity_combo.set("0.1%") | |
| self.Breast_diameter_combo.configure( | |
| values=["8", "10", "12", "14", "19", "18"] | |
| ) | |
| self.Breast_diameter_combo.set("8") | |
| self.Breast_height_combo.configure( | |
| values=["1 x radius", "1.5 x radius", "2 x radius"] | |
| ) | |
| self.Breast_height_combo.set("1 x radius") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.delete("0.0", "end") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.insert( | |
| "end", | |
| f'{"".join(config_method_specific_inputs["method_specific_inputs"]["Sarno_49_specific_output"])}', | |
| "green", | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| self.keV = [] | |
| self.I = [] | |
| self.plot_spectra() | |
| elif current_method == 2: # Sarno any spectrum | |
| self.VGF_combo.configure(state="disabled") | |
| self.input_spectra_button.configure(state="normal") | |
| self.Breast_diameter_combo.configure(state="normal") | |
| self.Breast_height_combo.configure(state="normal") | |
| self.Breast_glandularity_combo.configure(state="normal") | |
| self.HVL_combo.configure(state="disabled") | |
| self.graph_spectra.configure(state="disabled") | |
| self.Breast_glandularity_combo.configure( | |
| values=["0.1%", "14.3%", "25%", "50%", "75%", "100%"] | |
| ) | |
| self.Breast_glandularity_combo.set("0.1%") | |
| self.Breast_diameter_combo.configure( | |
| values=["8", "10", "12", "14", "19", "18"] | |
| ) | |
| self.Breast_diameter_combo.set("8") | |
| self.Breast_height_combo.configure( | |
| values=["1 x radius", "1.5 x radius", "2 x radius"] | |
| ) | |
| self.Breast_height_combo.set("1 x radius") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.delete("0.0", "end") | |
| self.output_textbox.insert( | |
| "end", | |
| f'{"".join(config_method_specific_inputs["method_specific_inputs"]["Sarno_specific_outputs"])}', | |
| "green", | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| self.keV = [] | |
| self.I = [] | |
| self.plot_spectra() | |
| elif current_method == 3: # Hernandez any spectrum | |
| self.VGF_combo.configure(state="normal") | |
| self.input_spectra_button.configure(state="normal") | |
| self.Breast_diameter_combo.configure(state="disabled") | |
| self.Breast_height_combo.configure(state="disabled") | |
| self.Breast_glandularity_combo.configure(state="disabled") | |
| self.HVL_combo.configure(state="disabled") | |
| self.graph_spectra.configure(state="disabled") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.delete("0.0", "end") | |
| self.output_textbox.insert( | |
| "end", | |
| f'{"".join(config_method_specific_inputs["method_specific_inputs"]["Hernandez_specific_output"])}', | |
| "green", | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| self.keV = [] | |
| self.I = [] | |
| self.plot_spectra() | |
| else: # Sechopoulus method | |
| self.VGF_combo.configure(state="disabled") | |
| self.input_spectra_button.configure(state="disabled") | |
| self.Breast_diameter_combo.configure(state="normal") | |
| self.Breast_height_combo.configure(state="normal") | |
| self.Breast_glandularity_combo.configure(state="normal") | |
| self.HVL_combo.configure(state="disabled") | |
| self.graph_spectra.configure(state="disabled") | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.delete("0.0", "end") | |
| self.output_textbox.insert( | |
| "end", | |
| f'{"".join(config_method_specific_inputs["method_specific_inputs"]["Sechopoulos_specific_output"])}', | |
| "green", | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| self.Breast_glandularity_combo.configure( | |
| values=["1%", "14.3%", "25%", "50%", "75%", "100%"] | |
| ) | |
| self.Breast_glandularity_combo.set("1%") | |
| self.Breast_diameter_combo.configure(values=["10", "12", "14", "19", "18"]) | |
| self.Breast_diameter_combo.set("10") | |
| self.Breast_height_combo.configure( | |
| values=["0.5 x diameter", "0.75 x diameter", "1 x diameter"] | |
| ) | |
| self.Breast_height_combo.set("0.5 x diameter") | |
| self.keV = [] | |
| self.I = [] | |
| self.plot_spectra() | |
| # browse for incident spectrum | |
| def browse_files(self): | |
| # read and output textfile | |
| self.input_txt_file = filedialog.askopenfilename( | |
| initialdir="/", title="Select a File", filetypes=[("all files", "*.*")] | |
| ) | |
| self.display_txt_file = self.input_txt_file.split("/")[-1] | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.insert( | |
| "end", f"\nInputted incident spectrum: \n{self.display_txt_file}", "\n" | |
| ) | |
| self.output_textbox.configure(state="disabled") | |
| try: | |
| # get keV and I | |
| self.keV, self.I = self.read_input_spectra() | |
| self.minimum = min(self.keV) | |
| self.maximum = max(self.keV) | |
| method = self.method_chosen.get() | |
| if method == 3: | |
| if self.minimum < 9 or self.maximum > 70: | |
| raise (ValueError) | |
| elif method == 2: | |
| if self.minimum < 8 or self.maximum > 80: | |
| raise (ValueError) | |
| self.graph_spectra.configure(state="normal") | |
| # account for possible errors | |
| except UnicodeDecodeError: | |
| pop_up = customtkinter.CTkToplevel() | |
| customtkinter.CTkLabel( | |
| pop_up, text="Please enter a valid text file", font=("Roman", font_size) | |
| ).pack() | |
| except ValueError: | |
| pop_up = customtkinter.CTkToplevel() | |
| customtkinter.CTkLabel( | |
| pop_up, | |
| text="For Hernadez Any spectrum, please enter spectrum ranging from 9 to 70 keV", | |
| font=("Roman", font_size), | |
| ).pack() | |
| customtkinter.CTkLabel( | |
| pop_up, | |
| text="For Sarno Any Spectrum, please enter spectrum ranging from 8 to 80 keV", | |
| font=("Roman", font_size), | |
| ).pack() | |
| # read input spectra | |
| def read_input_spectra(self): | |
| with open(self.input_txt_file, "r") as file: | |
| keV = [] | |
| I = [] | |
| data = file.readlines() | |
| for line in data: | |
| line = line.split() | |
| keV.append(float(line[0])) | |
| I.append(float(line[1])) | |
| return keV, I | |
| # clear text | |
| def clear_text(self): | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.delete("0.0", "end") | |
| self.output_textbox.configure(state="disabled") | |
| # update plot | |
| def plot_spectra(self): | |
| try: | |
| y_end = max(self.I) * 1.1 | |
| x_end = max(self.keV) * 1.1 | |
| file_name = self.display_txt_file | |
| except: | |
| x_end = 5 | |
| y_end = 5 | |
| file_name = "Input Incident Spectrum" | |
| plt_style.use(["dark_background"]) | |
| params["figure.figsize"] = [7.50, 3.50] | |
| params["figure.autolayout"] = True | |
| self.figure = plt_figure(figsize=(7.6, 4.2), dpi=100) | |
| axes = self.figure.add_subplot(111) | |
| axes.plot(self.keV, self.I, "b-") | |
| axes.set_title(f"{file_name}") | |
| axes.set_xlabel("Energy (keV)", fontsize=11) | |
| axes.set_ylabel("Intensity (counts)", fontsize=11) | |
| axes.set_xlim(0, x_end) | |
| axes.set_ylim([0, y_end]) | |
| chart = FigureCanvasTkAgg(self.figure, master=self.graph_frame) | |
| chart.get_tk_widget().grid(row=0, column=0, columnspan=2) | |
| # calculate mean glandur dosage from user specificed inputs | |
| def calculate_mgd( | |
| self, air_kerma_input, air_kerma, dgn, output_units, number_of_projections | |
| ): | |
| # unit conversions based on user selection | |
| if air_kerma_input != "mGy": | |
| if air_kerma_input == "mrad": | |
| air_kerma = air_kerma * 0.01 # convert from mrad air kerma to mGy | |
| elif air_kerma_input == "R": | |
| air_kerma = air_kerma * 8.77 # convert from R to mGy | |
| elif air_kerma_input == "mR": | |
| air_kerma = air_kerma * 0.00877 # convert from mR to mGy | |
| mgd = air_kerma * dgn * float(number_of_projections) # in mGy/mGy | |
| if output_units == "mrad": | |
| mgd = mgd * 100 # converts mgd to mrad | |
| return mgd | |
| # write inputs to textbox | |
| def output_dose(self, *values): | |
| self.output_textbox.configure(state="normal") | |
| self.output_textbox.insert( | |
| "end", f"\n\nMGD = {self.mgd:.4f} {self.output_units.get()}" | |
| ) | |
| self.output_textbox.insert( | |
| "end", f" with {self.number_projections_entry.get()} projections" | |
| ) | |
| self.output_textbox.insert("end", f" with {self.mAs_entry.get()} mAs") | |
| for index in range(len(values)): | |
| if index == 0 or index % 2 == 0: | |
| self.output_textbox.insert("end", f"\n{values[index]}") | |
| else: | |
| self.output_textbox.insert("end", f"{values[index]}") | |
| self.output_textbox.configure(state="disabled") | |
| # function to calculate dose | |
| def calculate_dose(self): | |
| # get inputs | |
| current_method = self.method_chosen.get() | |
| air_kerma_input_units = self.air_kerma_units_combo.get() | |
| output_units = self.output_units.get() | |
| air_kerma = self.air_kerma_entry.get() | |
| number_of_projections = self.number_projections_entry.get() | |
| try: | |
| # numbers only | |
| air_kerma_check = len(air_kerma) | |
| number_of_projections_check = len(number_of_projections) | |
| air_kerma = float(air_kerma) # check if only numbers | |
| mAs_per_projection = float(self.mAs_entry.get()) | |
| air_kerma = mAs_per_projection * air_kerma | |
| if air_kerma_check == 0 or number_of_projections_check == 0: | |
| raise (ValueError) | |
| elif current_method == 2 or current_method == 3: | |
| kev_check = len(self.keV) | |
| if kev_check == 0: | |
| raise (TypeError) | |
| # calculate mgd based on specified model | |
| if current_method == 1: # Sarno 49 kVp W spectra | |
| # get parameters | |
| HVL = self.HVL_combo.get() | |
| breast_diameter = self.Breast_diameter_combo.get() | |
| breast_glandularity = self.Breast_glandularity_combo.get() | |
| breast_height = "".join(self.Breast_height_combo.get().split(" ")[0:2]) | |
| # calculate polyenergetic dgn coefficients | |
| dgn_subtable_breast_height = Sarno_poly_dgn.groupby( | |
| ["breast height"] | |
| ).get_group(breast_height) | |
| dgn_subtable_glandularity = dgn_subtable_breast_height.groupby( | |
| ["Glandularity"] | |
| ).get_group(breast_glandularity) | |
| dgn = dgn_subtable_glandularity.loc[float(HVL), str(breast_diameter)] | |
| # calculate mgd based on pdgn and input parameters | |
| self.mgd = self.calculate_mgd( | |
| air_kerma_input_units, | |
| air_kerma, | |
| dgn, | |
| output_units, | |
| number_of_projections, | |
| ) | |
| self.output_dose( | |
| "Breast diameter: ", | |
| breast_diameter, | |
| "HVL: ", | |
| HVL, | |
| "Breast_glandularity: ", | |
| breast_glandularity, | |
| "Breast Height: ", | |
| self.Breast_height_combo.get(), | |
| ) | |
| elif current_method == 2: # Sarno any spectrum | |
| breast_diameter = self.Breast_diameter_combo.get() | |
| breast_glandularity = self.Breast_glandularity_combo.get() | |
| breast_height = "".join(self.Breast_height_combo.get().split(" ")[0:2]) | |
| # get dgn coefficients | |
| dgn_subtable_breast_height = Sarno_mono_dgn.groupby( | |
| ["breast height"] | |
| ).get_group(breast_height) | |
| dgn_subtable_glandularity = dgn_subtable_breast_height.groupby( | |
| ["Glandularity"] | |
| ).get_group(breast_glandularity) | |
| values = dgn_subtable_glandularity[str(breast_diameter)].tolist() | |
| variables = np.zeros((len(self.keV), len(values))) | |
| for index in range(0, len(values)): | |
| variables[:, index] = [float(values[index])] # *len(self.keV) | |
| # calulate dgn | |
| dgn = calculate_pDgNct( | |
| "Sarno Koning", variables, self.keV, self.I | |
| ) # units of mGy/mGy | |
| self.mgd = self.calculate_mgd( | |
| air_kerma_input_units, | |
| air_kerma, | |
| dgn, | |
| output_units, | |
| number_of_projections, | |
| ) | |
| self.output_dose( | |
| "Breast diameter: ", | |
| breast_diameter, | |
| "Breast Glandularity: ", | |
| breast_glandularity, | |
| "Breast Height: ", | |
| self.Breast_height_combo.get(), | |
| ) | |
| elif current_method == 3: # Hernandez any spectrum | |
| # get the VGF and DgN list from text file | |
| VGF = self.VGF_combo.get().split("=")[0].strip() | |
| DgN_list = Hernandez_hetero_mono_dgn.loc[:, VGF].tolist() | |
| # index the list to fit keV | |
| start_index = int(abs(self.minimum - 9)) | |
| if self.maximum != 70: | |
| end_index = int(abs(self.maximum - 9)) + 1 | |
| else: | |
| end_index = -1 | |
| # interpolate the DgN list if necessary | |
| Hernandez_keV = list(np.arange(self.minimum, self.maximum + 1)) | |
| DgN_list = DgN_list[start_index:end_index] | |
| interp_DgN_list = np.interp(self.keV, Hernandez_keV, DgN_list) | |
| # get the DgN coefficients in mGy/mGy | |
| dgn = calculate_pDgNct("Hernandez", interp_DgN_list, self.keV, self.I) | |
| print(dgn) | |
| # print(dgn) | |
| self.mgd = self.calculate_mgd( | |
| air_kerma_input_units, | |
| air_kerma, | |
| dgn, | |
| output_units, | |
| number_of_projections, | |
| ) | |
| self.output_dose("VGF: ", self.VGF_combo.get()) | |
| else: # Sechopoulos method | |
| # get parameters | |
| breast_diameter = self.Breast_diameter_combo.get() | |
| breast_glandularity = self.Breast_glandularity_combo.get() | |
| breast_height = float( | |
| self.Breast_height_combo.get().split("x")[0] | |
| ) * float(breast_diameter) | |
| # calculate dgn for polyenergetic | |
| dgn_subtable_breast_height = Sechopoulos_poly_dgn.groupby( | |
| ["Chest wall-to-nipple distance"] | |
| ).get_group(breast_height) | |
| dgn = dgn_subtable_breast_height[breast_glandularity].tolist()[0] | |
| # calculate mgd | |
| self.mgd = self.calculate_mgd( | |
| air_kerma_input_units, | |
| air_kerma, | |
| dgn, | |
| output_units, | |
| number_of_projections, | |
| ) | |
| self.output_dose( | |
| "Breast diameter: ", | |
| breast_diameter, | |
| "Breast_glandularity: ", | |
| breast_glandularity, | |
| "Breast Height: ", | |
| self.Breast_height_combo.get(), | |
| ) | |
| except ValueError: # error in air kerma inputs | |
| pop_up = customtkinter.CTkToplevel() | |
| if air_kerma_check == 0: | |
| customtkinter.CTkLabel( | |
| pop_up, | |
| text="Please enter a numeric value into the air kerma entry box or into \nthe number of projections box", | |
| font=("Roman", font_size), | |
| ).pack() | |
| else: | |
| customtkinter.CTkLabel( | |
| pop_up, | |
| text="Please enter only numbers into the air kerma and ensure \nthat a numeric value is placed into the number of projections", | |
| font=("Roman", font_size), | |
| ).pack() | |
| except TypeError: # error in incident spectrum | |
| pop_up = customtkinter.CTkToplevel() | |
| customtkinter.CTkLabel( | |
| pop_up, | |
| text="Please enter an incident spectrum", | |
| font=("Roman", font_size), | |
| ).pack() | |
| # %%% Executable Code | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--font_size_set", | |
| nargs="?", | |
| default=1, | |
| type=int, | |
| help="select the font size set (default=1 is the first set)", | |
| ) | |
| args = parser.parse_args() | |
| font_size = dict_font_size_set[args.font_size_set] | |
| font_size = dict_font_size_set[args.font_size_set] | |
| customtkinter.set_appearance_mode("light") | |
| root = customtkinter.CTk() | |
| root.title("BCT Dose Calculator") | |
| CT_dose = Main_Window(root) | |
| root.protocol("WM_DELETE_WINDOW", quit_me) | |
| root.mainloop() | |