Source code for xpecgen.xpecgenGUI

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""xpecgenGUI.py: A GUI for the xpecgen module"""

from __future__ import print_function

from tkinter import *
from tkinter.ttk import *
import tkinter.filedialog
from tkinter import messagebox

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure

import threading
import queue
from traceback import print_exc

import os

from glob import glob

import re  # Regular expressions, used for sorting

from . import xpecgen as xg

__author__ = "Dih5"

_elements = ['Nihil', 'Hydrogen', 'Helium', 'Lithium', 'Beryllium', 'Boron', 'Carbon, Graphite', 'Nitrogen', 'Oxygen',
             'Fluorine', 'Neon', 'Sodium', 'Magnesium', 'Aluminum', 'Silicon', 'Phosphorus', 'Sulfur', 'Chlorine',
             'Argon', 'Potassium', 'Calcium', 'Scandium', 'Titanium', 'Vanadium', 'Chromium', 'Manganese', 'Iron',
             'Cobalt', 'Nickel', 'Copper', 'Zinc', 'Gallium', 'Germanium', 'Arsenic', 'Selenium', 'Bromine', 'Krypton',
             'Rubidium', 'Strontium', 'Yttrium', 'Zirconium', 'Niobium', 'Molybdenum', 'Technetium', 'Ruthenium',
             'Rhodium', 'Palladium', 'Silver', 'Cadmium', 'Indium', 'Tin', 'Antimony', 'Tellurium', 'Iodine', 'Xenon',
             'Cesium', 'Barium', 'Lanthanum', 'Cerium', 'Praseodymium', 'Neodymium', 'Promethium', 'Samarium',
             'Europium', 'Gadolinium', 'Terbium', 'Dysprosium', 'Holmium', 'Erbium', 'Thulium', 'Ytterbium', 'Lutetium',
             'Hafnium', 'Tantalum', 'Tungsten', 'Rhenium', 'Osmium', 'Iridium', 'Platinum', 'Gold', 'Mercury',
             'Thallium', 'Lead', 'Bismuth', 'Polonium', 'Astatine', 'Radon', 'Francium', 'Radium', 'Actinium',
             'Thorium', 'Protactinium', 'Uranium']


def _add_element_name(material):
    """Check if the string is a integer. If so, convert to element name, e.g., 13-> 13: Aluminum"""
    try:
        int(material)
        return "%d: %s" % (int(material), _elements[int(material)])
    except ValueError:
        return material


def _remove_element_name(material):
    """Revert _add_element_name"""
    if re.match("[0-9]+: .*", material):
        return material.split(":")[0]
    else:
        return material

[docs]class CreateToolTip(object): """ A tooltip for a given widget. """ # Based on the content from this post: # http://stackoverflow.com/questions/3221956/what-is-the-simplest-way-to-make-tooltips-in-tkinter def __init__(self, widget, text, color="#ffe14c"): """ Create a tooltip for an existent widget. Args: widget: The widget the tooltip is applied to. text (str): The text of the tooltip. color: The color of the tooltip. """ self.waittime = 500 # miliseconds self.wraplength = 180 # pixels self.widget = widget self.text = text self.color = color self.widget.bind("<Enter>", self.enter) self.widget.bind("<Leave>", self.leave) self.widget.bind("<ButtonPress>", self.leave) self.id = None self.tw = None def enter(self, event=None): self.schedule() def leave(self, event=None): self.unschedule() self.hidetip() def schedule(self): self.unschedule() self.id = self.widget.after(self.waittime, self.showtip) def unschedule(self): id = self.id self.id = None if id: self.widget.after_cancel(id) def showtip(self, event=None): x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 20 # creates a toplevel window self.tw = Toplevel(self.widget) # Leaves only the label and removes the app window self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) label = Label(self.tw, text=self.text, justify='left', background=self.color, relief='solid', borderwidth=1, wraplength=self.wraplength) label.pack(ipadx=1) def hidetip(self): tw = self.tw self.tw = None if tw: tw.destroy()
[docs]class ParBox: """A parameter entry with labels preceding and succeeding it and an optional tooltip""" def __init__(self, master=None, textvariable=0, lblText="", unitsTxt="", helpTxt="", row=0, read_only=False): """ Create the parameter box. Args: master: the master widget. textvariable (:obj:`tkinter.Variable`): The variable associated with the parameter. lblText (str): The text preceding the text entry. unitsTxt (str): The text succeeding the text entry, typically the units. helpTxt (str): The help text to show in the tooltip. If "", no tooltip is shown. row (int): The row where the widgets are set in the grid. read_only (bool): Whether the entry is read_only. """ self.lbl = Label(master, text=lblText) self.lbl.grid(row=row, column=0, sticky=W) self.txt = Entry(master, textvariable=textvariable) self.txt.grid(row=row, column=1, sticky=W + E) self.units = Label(master, text=unitsTxt, anchor=W) self.units.grid(row=row, column=2, sticky=W) if helpTxt != "": self.lblTT = CreateToolTip(self.lbl, helpTxt) self.txtTT = CreateToolTip(self.txt, helpTxt) if read_only: self.txt["state"] = "readonly"
def _human_order_key(text): """ Key function to sort in human order. """ # This is based in http://nedbatchelder.com/blog/200712/human_sorting.html return [int(c) if c.isdigit() else c for c in re.split('(\d+)', text)]
[docs]class XpecgenGUI(Notebook): """Tk-based GUI for the xpecgen package.""" def __init__(self, master=None): """ Create the GUI. Args: master: the tk master of the GUI. """ Notebook.__init__(self, master) Grid.rowconfigure(master, 0, weight=1) Grid.columnconfigure(master, 0, weight=1) self.grid(row=0, column=0, sticky=N + S + E + W) self.master.title("".join(('xpecgen v', xg.__version__, " GUI"))) # self.master.minsize(800, 600) self.spectra = [] self.spectra_labels = [] self.active_spec = 0 # The active spectrum from the list # Interpolations used to calculate HVL self.fluence_to_dose = xg.get_fluence_to_dose() self.mu_Al = xg.get_mu(13) self.mu_Cu = xg.get_mu(29) self.initVariables() self.createWidgets() self.history_poll()
[docs] def history_poll(self): """ Polling method to update changes in spectrum list. """ # Note the Tk manual advised for polling instead of binding all methods that are able to change a listbox try: now = self.lstHistory.curselection()[0] if now != self.active_spec: self.active_spec = now self.update_plot() except IndexError: pass self.after(150, self.history_poll)
[docs] def initVariables(self): """Create and initialize interface variables""" # Calculation-related variables self.E0 = DoubleVar() self.E0.set(100.0) self.Theta = DoubleVar() self.Theta.set(12.0) self.Phi = DoubleVar() self.Phi.set(0.0) self.Z = StringVar() self.Z.set(_add_element_name("74")) self.EMin = DoubleVar() self.EMin.set(3.0) self.NumE = IntVar() self.NumE.set(50) self.Eps = DoubleVar() self.Eps.set(0.5) # Operation-related variables self.AttenMaterial = StringVar() self.AttenMaterial.set(_add_element_name("13")) self.AttenThick = DoubleVar() self.AttenThick.set(0.1) self.NormCriterion = StringVar() self.NormCriterion.set("Number") self.NormValue = DoubleVar() self.NormValue.set(1.0) # Output HVLs self.HVL1 = StringVar() self.HVL1.set("0") self.HVL2 = StringVar() self.HVL2.set("0") self.HVL3 = StringVar() self.HVL3.set("0") self.HVL4 = StringVar() self.HVL4.set("0") # Output Norms self.number = StringVar() self.number.set("0") self.energy = StringVar() self.energy.set("0") self.dose = StringVar() self.dose.set("0")
[docs] def createWidgets(self): """Create the widgets in the GUI""" self.frmCalc = Frame(self) self.frmAnal = Frame(self) self.add(self.frmCalc, text='Calculate') self.add(self.frmAnal, text='Analyze') # self.frmCalc.grid(sticky=N+S+E+W, column=0, row=0) # Calculate Tab # -Physical parameters self.frmPhysPar = LabelFrame(self.frmCalc, text="Physical parameters") self.frmPhysPar.grid(row=0, column=0, sticky=N + S + E + W) self.ParE0 = ParBox(self.frmPhysPar, self.E0, lblText="Electron Energy (E0)", unitsTxt="keV", helpTxt="Electron kinetic energy in keV.", row=0) self.ParTheta = ParBox(self.frmPhysPar, self.Theta, lblText=u"Angle (\u03b8)", unitsTxt="º", helpTxt="X-rays emission angle. The anode's normal is at 90º.", row=1) self.ParPhi = ParBox(self.frmPhysPar, self.Phi, lblText=u"Elevation angle (\u03c6)", unitsTxt="º", helpTxt="X-rays emission altitude. The anode's normal is at 0º.", row=2) self.lblZ = Label(self.frmPhysPar, text="Target atomic number") self.lblZTT = CreateToolTip(self.lblZ, "Atomic number of the target. IMPORTANT: Only used in the cross-section and distance scaling. Fluence uses a tugsten model, but the range is increased in lower Z materials. Besides, characteristic radiation is only calculated for tugsten.") self.lblZ.grid(row=3, column=0, sticky=W) self.cmbZ = Combobox(self.frmPhysPar, textvariable=self.Z) self.cmbZ.grid(row=3, column=1, sticky=W + E) self.cmbZTT = CreateToolTip(self.cmbZ, "Atomic number of the target. IMPORTANT: Only used in the cross-section and distance scaling. Fluence uses a tugsten model, but the range is increased in lower Z materials. Besides, characteristic radiation is only calculated for tugsten.") # Available cross-section data target_list = list(map(lambda x: (os.path.split(x)[1]).split( ".csv")[0], glob(os.path.join(xg.data_path, "cs", "*.csv")))) target_list.remove("grid") # Available csda-data csda_list = list(map(lambda x: (os.path.split(x)[1]).split( ".csv")[0], glob(os.path.join(xg.data_path, "csda", "*.csv")))) # Available attenuation data mu_list = list(map(lambda x: (os.path.split(x)[1]).split( ".csv")[0], glob(os.path.join(xg.data_path, "mu", "*.csv")))) mu_list.sort(key=_human_order_key) # Used later available_list = list(set(target_list) & set(csda_list) & set(mu_list)) available_list.sort(key=_human_order_key) self.cmbZ["values"] = list(map(_add_element_name, available_list)) Grid.columnconfigure(self.frmPhysPar, 0, weight=0) Grid.columnconfigure(self.frmPhysPar, 1, weight=1) Grid.columnconfigure(self.frmPhysPar, 2, weight=1) Grid.columnconfigure(self.frmPhysPar, 3, weight=0) # -Numerical Parameters self.frmNumPar = LabelFrame(self.frmCalc, text="Numerical parameters") self.frmNumPar.grid(row=0, column=1, sticky=N + S + E + W) self.ParEMin = ParBox(self.frmNumPar, self.EMin, lblText="Min energy", unitsTxt="keV", helpTxt="Minimum kinetic energy in the bremsstrahlung calculation. Note this might influence the characteristic peaks prediction.", row=0) self.ParNumE = ParBox(self.frmNumPar, self.NumE, lblText="Number of points", unitsTxt="", helpTxt="Amount of points for the mesh were the bremsstrahlung spectrum is calculated.\nBremsstrahlung component is extended by interpolation.", row=1) self.ParEps = ParBox(self.frmNumPar, self.Eps, lblText="Integrating tolerance", unitsTxt="", helpTxt="A numerical tolerance parameter used in numerical integration. Values around 0.5 provide fast and accurate calculations. If you want insanely accurate (and physically irrelevant) numerical integration you can reduce this value, increasing computation time.", row=2) Grid.columnconfigure(self.frmNumPar, 0, weight=0) Grid.columnconfigure(self.frmNumPar, 1, weight=1) Grid.columnconfigure(self.frmNumPar, 2, weight=0) # -Buttons, status bar... self.cmdCalculate = Button(self.frmCalc, text="Calculate") self.cmdCalculate["command"] = self.calculate self.cmdCalculate.bind('<Return>', lambda event: self.calculate()) self.cmdCalculate.bind( '<KP_Enter>', lambda event: self.calculate()) # Enter (num. kb) self.cmdCalculate.grid(row=1, column=0, sticky=E + W) self.barProgress = Progressbar( self.frmCalc, orient="horizontal", length=100, mode="determinate") self.barProgress.grid(row=1, column=1, columnspan=1, sticky=E + W) Grid.columnconfigure(self.frmCalc, 0, weight=1) Grid.columnconfigure(self.frmCalc, 1, weight=1) Grid.rowconfigure(self.frmCalc, 0, weight=1) Grid.rowconfigure(self.frmCalc, 1, weight=0) # Analyze tab # -History frame self.frmHist = LabelFrame(self.frmAnal, text="History") self.frmHist.grid(row=0, column=0, sticky=N + S + E + W) self.lstHistory = Listbox(self.frmHist, selectmode=BROWSE) self.lstHistory.grid(row=0, column=0, sticky=N + S + E + W) self.scrollHistory = Scrollbar(self.frmHist, orient=VERTICAL) self.scrollHistory.grid(row=0, column=1, sticky=N + S) self.lstHistory.config(yscrollcommand=self.scrollHistory.set) self.scrollHistory.config(command=self.lstHistory.yview) self.cmdCleanHistory = Button(self.frmHist, text="Revert to selected", state=DISABLED) self.cmdCleanHistory["command"] = self.clean_history self.cmdCleanHistory.grid(row=1, column=0, columnspan=2, sticky=E + W) self.cmdExport = Button(self.frmHist, text="Export selected", state=DISABLED) self.cmdExport["command"] = self.export self.cmdExport.grid(row=2, column=0, columnspan=2, sticky=E + W) Grid.rowconfigure(self.frmHist, 0, weight=1) Grid.columnconfigure(self.frmHist, 0, weight=1) Grid.columnconfigure(self.frmHist, 1, weight=0) # -Operations frame self.frmOper = LabelFrame(self.frmAnal, text="Spectrum operations") self.frmOper.grid(row=1, column=0, sticky=N + S + E + W) # --Attenuation self.frmOperAtten = LabelFrame(self.frmOper, text="Attenuate") self.frmOperAtten.grid(row=0, column=0, sticky=N + S + E + W) self.lblAttenMaterial = Label(self.frmOperAtten, text="Material") self.lblAttenMaterial.grid() self.cmbAttenMaterial = Combobox(self.frmOperAtten, textvariable=self.AttenMaterial) self.cmbAttenMaterial["values"] = list(map(_add_element_name, mu_list)) self.cmbAttenMaterial.grid(row=0, column=1, sticky=E + W) self.ParAttenThick = ParBox( self.frmOperAtten, self.AttenThick, lblText="Thickness", unitsTxt="cm", row=1) self.cmdAtten = Button(self.frmOperAtten, text="Add attenuation", state=DISABLED) self.cmdAtten["command"] = self.attenuate self.cmdAtten.grid(row=2, column=0, columnspan=3, sticky=E + W) Grid.columnconfigure(self.frmOperAtten, 0, weight=0) Grid.columnconfigure(self.frmOperAtten, 1, weight=1) Grid.columnconfigure(self.frmOperAtten, 2, weight=0) # --Normalize self.frmOperNorm = LabelFrame(self.frmOper, text="Normalize") self.frmOperNorm.grid(row=1, column=0, sticky=N + S + E + W) self.lblNormCriterion = Label(self.frmOperNorm, text="Criterion") self.lblNormCriterion.grid() self.cmbNormCriterion = Combobox( self.frmOperNorm, textvariable=self.NormCriterion) self.criteriaList = ["Number", "Energy (keV)", "Dose (mGy)"] self.cmbNormCriterion["values"] = self.criteriaList self.cmbNormCriterion.grid(row=0, column=1, sticky=E + W) self.ParNormValue = ParBox( self.frmOperNorm, self.NormValue, lblText="Value", unitsTxt="", row=1) self.cmdNorm = Button(self.frmOperNorm, text="Normalize", state=DISABLED) self.cmdNorm["command"] = self.normalize self.cmdNorm.grid(row=2, column=0, columnspan=3, sticky=E + W) Grid.columnconfigure(self.frmOperNorm, 0, weight=0) Grid.columnconfigure(self.frmOperNorm, 1, weight=1) Grid.columnconfigure(self.frmOperNorm, 2, weight=0) Grid.columnconfigure(self.frmOper, 0, weight=1) Grid.rowconfigure(self.frmOper, 0, weight=1) # -Plot frame self.frmPlot = Frame(self.frmAnal) try: self.fig = Figure(figsize=(5, 4), dpi=100, facecolor=self.master["bg"]) self.subfig = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, master=self.frmPlot) self.canvas.show() self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.canvasToolbar = NavigationToolbar2TkAgg( self.canvas, self.frmPlot) self.canvasToolbar.update() self.canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1) self.frmPlot.grid(row=0, column=1, rowspan=3, sticky=N + S + E + W) self.matplotlib_embedded = True except Exception: self.matplotlib_embedded = False # self.cmdShowPlot = Button(self.frmPlot,text="Open plot window") # self.cmdShowPlot["command"] = self.update_plot # self.cmdShowPlot.grid(row=0,column=0) print("WARNING: Matplotlib couldn't be embedded in TkAgg.\nUsing independent window instead", file=sys.stderr) # --Spectral parameters frame self.frmSpectralParameters = LabelFrame(self.frmAnal, text="Spectral parameters") self.frmSpectralParameters.grid(row=2, column=0, sticky=S + E + W) self.ParHVL1 = ParBox(self.frmSpectralParameters, self.HVL1, lblText="1HVL Al", unitsTxt="cm", row=0, read_only=True, helpTxt="Thickness of Al at which the dose produced by the spectrum is halved, according to the exponential attenuation model.") self.ParHVL2 = ParBox(self.frmSpectralParameters, self.HVL2, lblText="2HVL Al", unitsTxt="cm", row=1, read_only=True, helpTxt="Thickness of Al at which the dose produced by the spectrum after crossing a HVL is halved again, according to the exponential attenuation model.") self.ParHVL3 = ParBox(self.frmSpectralParameters, self.HVL3, lblText="1HVL Cu", unitsTxt="cm", row=2, read_only=True, helpTxt="Thickness of Cu at which the dose produced by the spectrum is halved, according to the exponential attenuation model.") self.ParHVL4 = ParBox(self.frmSpectralParameters, self.HVL4, lblText="2HVL Cu", unitsTxt="cm", row=3, read_only=True, helpTxt="Thickness of Cu at which the dose produced by the spectrum after crossing a HVL is halved again, according to the exponential attenuation model.") self.ParNorm = ParBox(self.frmSpectralParameters, self.number, lblText="Photon number", unitsTxt="", row=4, read_only=True, helpTxt="Number of photons in the spectrum.") self.ParEnergy = ParBox(self.frmSpectralParameters, self.energy, lblText="Energy", unitsTxt="keV", row=5, read_only=True, helpTxt="Total energy in the spectrum.") self.ParDose = ParBox(self.frmSpectralParameters, self.dose, lblText="Dose", unitsTxt="mGy", row=6, read_only=True, helpTxt="Dose produced in air by the spectrum, assuming it is describing the differential fluence in particles/keV/cm^2.") Grid.columnconfigure(self.frmAnal, 0, weight=1) if self.matplotlib_embedded: # If not embedding, use the whole window Grid.columnconfigure(self.frmAnal, 1, weight=3) Grid.rowconfigure(self.frmAnal, 0, weight=1) Grid.rowconfigure(self.frmAnal, 1, weight=1)
[docs] def enable_analyze_buttons(self): """ Enable widgets requiring a calculated spectrum to work. """ self.cmdCleanHistory["state"] = "normal" self.cmdExport["state"] = "normal" self.cmdAtten["state"] = "normal" self.cmdNorm["state"] = "normal"
[docs] def update_plot(self): """ Update the canvas after plotting something. If matplotlib is not embedded, show it in an independent window. """ if self.matplotlib_embedded: self.subfig.clear() self.spectra[self.active_spec].get_plot(self.subfig) self.canvas.draw() self.canvasToolbar.update() self.fig.tight_layout() else: # FIXME: Update if independent window is opened self.spectra[self.active_spec].show_plot(block=False) self.update_param()
[docs] def update_param(self): """ Update parameters calculated from the active spectrum. """ hvlAl = self.spectra[self.active_spec].hvl(0.5, self.fluence_to_dose, self.mu_Al) qvlAl = self.spectra[self.active_spec].hvl(0.25, self.fluence_to_dose, self.mu_Al) hvlCu = self.spectra[self.active_spec].hvl(0.5, self.fluence_to_dose, self.mu_Cu) qvlCu = self.spectra[self.active_spec].hvl(0.25, self.fluence_to_dose, self.mu_Cu) # TODO: (?) cache the results self.HVL1.set('%s' % float('%.3g' % hvlAl)) self.HVL2.set('%s' % float('%.3g' % (qvlAl - hvlAl))) self.HVL3.set('%s' % float('%.3g' % hvlCu)) self.HVL4.set('%s' % float('%.3g' % (qvlCu - hvlCu))) self.number.set('%s' % float('%.3g' % (self.spectra[self.active_spec].get_norm()))) self.energy.set('%s' % float('%.3g' % (self.spectra[self.active_spec].get_norm(lambda x: x)))) self.dose.set('%s' % float('%.3g' % (self.spectra[self.active_spec].get_norm(self.fluence_to_dose))))
[docs] def monitor_bar(self, a, b): """ Update the progress bar. Args: a (int): The number of items already calculated. b (int): The total number of items to calculate. """ self.barProgress["value"] = a self.barProgress["maximum"] = b
[docs] def clean_history(self): """ Clean the spectra history. """ try: now = int(self.lstHistory.curselection()[0]) if now == len(self.spectra) - 1: # No need to slice return self.spectra = self.spectra[0:now + 1] self.lstHistory.delete(now + 1, END) except IndexError: # Ignore if nothing selected pass
[docs] def export(self): """ Export the selected spectrum in xlsx format, showing a file dialog to choose the route. """ if self.lstHistory.curselection() == (): selection = -1 else: selection = int(self.lstHistory.curselection()[0]) file_opt = options = {} options['defaultextension'] = '.xlsx' options['filetypes'] = [('Excel Spreadsheet', '.xlsx'), ('Comma-separated values', '.csv')] options['initialfile'] = 'spectrum.xlsx' options['parent'] = self options['title'] = 'Export spectrum' filename = tkinter.filedialog.asksaveasfilename(**file_opt) if not filename: # Ignore if canceled return ext = filename.split(".")[-1] if ext == "xlsx": self.spectra[selection].export_xlsx(filename) elif ext == "csv": self.spectra[selection].export_csv(filename) else: messagebox.showerror("Error", "Unknown file extension: " + ext + "\nUse the file types from the dialog to export.")
[docs] def calculate(self): """ Calculates a new spectrum using the parameters in the GUI. """ # If a calculation was being held, abort it instead try: if self.calc_thread.is_alive(): self.abort_calculation = True self.cmdCalculate["text"] = "(Aborting)" self.cmdCalculate["state"] = "disabled" return except AttributeError: # If there is no calculation thread, there is nothing to worry about pass self.calculation_count = 0 self.calculation_total = self.NumE.get() z = int(_remove_element_name(self.Z.get())) def monitor(a, b): # Will be executed in calculation thread. Values are only collected, # Tk must be updated from main thread only. self.calculation_count = a self.calculation_total = b if self.abort_calculation: self.queue_calculation.put(False) exit(1) def callback(): # Carry the calculation in a different thread to avoid blocking try: s = xg.calculate_spectrum(self.E0.get(), self.Theta.get(), self.EMin.get(), self.NumE.get(), phi=self.Phi.get(), epsrel=self.Eps.get(), monitor=monitor, z=z) self.spectra = [s] self.queue_calculation.put(True) except Exception as e: print_exc() self.queue_calculation.put(False) messagebox.showerror("Error", "An error occurred during the calculation:\n%s\nCheck the parameters are valid."%str(e)) self.queue_calculation = queue.Queue(maxsize=1) # The child will fill the queue with a value indicating whether an error occured. self.abort_calculation = False # Ask the calculation thread to end (when monitor is executed) self.calc_thread = threading.Thread(target=callback) self.calc_thread.setDaemon(True) self.calc_thread.start() self.cmdCalculate["text"] = "Abort" self.after(250, self.wait_for_calculation)
[docs] def wait_for_calculation(self): """ Polling method to wait for the calculation thread to finish. Also updates monitor_bar. """ self.monitor_bar(self.calculation_count, self.calculation_total) if self.queue_calculation.full(): # Calculation ended self.cmdCalculate["text"] = "Calculate" self.cmdCalculate["state"] = "normal" self.monitor_bar(0, 0) if self.queue_calculation.get_nowait(): # Calculation ended successfully self.lstHistory.delete(0, END) self.lstHistory.insert(END, "Calculated") self.enable_analyze_buttons() self.active_spec = 0 self.update_plot() self.select(1) # Open analyse tab else: pass else: self.after(250, self.wait_for_calculation)
[docs] def attenuate(self): """ Attenuate the active spectrum according to the parameters in the GUI. """ s2 = self.spectra[-1].clone() s2.attenuate(self.AttenThick.get(), xg.get_mu(_remove_element_name(self.AttenMaterial.get()))) self.spectra.append(s2) self.lstHistory.insert( END, "Attenuated: " + str(self.AttenThick.get()) + "cm of " + self.AttenMaterial.get()) self.lstHistory.selection_clear(0, len(self.spectra) - 2) self.lstHistory.selection_set(len(self.spectra) - 1) self.update_plot() pass
[docs] def normalize(self): """ Normalize the active spectrum according to the parameters in the GUI. """ value = self.NormValue.get() crit = self.NormCriterion.get() if value <= 0: messagebox.showerror("Error", "The norm of a spectrum must be a positive number.") return if crit not in self.criteriaList: messagebox.showerror("Error", "An unknown criterion was selected.") return s2 = self.spectra[-1].clone() if crit == self.criteriaList[0]: s2.set_norm(value) elif crit == self.criteriaList[1]: s2.set_norm(value, lambda x: x) else: # criteriaList[2] s2.set_norm(value, self.fluence_to_dose) self.spectra.append(s2) self.lstHistory.insert( END, "Normalized: " + crit + " = " + str(value)) self.lstHistory.selection_clear(0, len(self.spectra) - 2) self.lstHistory.selection_set(len(self.spectra) - 1) self.update_plot() pass
[docs]def main(): """ Start an instance of the GUI. """ root = Tk() app = XpecgenGUI(master=root) app.mainloop()
if __name__ == "__main__": main()