import matplotlib
#opens a window on your computer
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#implement the deault mpl key bindings
from matplotlib.backend_bases import key_press_handler,MouseEvent
import tkMessageBox as box
import tkFileDialog as Tkf
import numpy as np
import pyfits
#Scipy for spline
from scipy.interpolate import interp1d
#scipy for integration
from scipy.integrate import simps
#Astropymodeling for gauss fit
from astropy.modeling import models, fitting
import matplotlib.pyplot as plt
import sys,os
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
#dictionary to convert proton number into element symbol
edict = {}
edict['1'] = 'H'
edict['2'] = 'Hi'
edict['3'] = 'Li'
edict['4'] = 'Be'
edict['5'] = 'B'
edict['6'] = 'C'
edict['7'] = 'N'
edict['8'] = 'O'
edict['9'] = 'F'
edict['10'] = 'Ne'
edict['11'] = 'Na'
edict['12'] = 'Mg'
edict['13'] = 'Al'
edict['14'] = 'Si'
edict['15'] = 'P'
edict['16'] = 'S'
edict['17'] = 'Cl'
edict['18'] = 'Ar'
edict['19'] = 'K'
edict['20'] = 'Ca'
edict['21'] = 'Sc'
edict['22'] = 'Ti'
edict['23'] = 'V'
edict['24'] = 'Cr'
edict['25'] = 'Mn'
edict['26'] = 'Fe'
edict['27'] = 'Co'
edict['28'] = 'Ni'
edict['29'] = 'Cu'
edict['30'] = 'Zn'
edict['31'] = 'Ga'
edict['32'] = 'Ge'
edict['33'] = 'As'
edict['34'] = 'Se'
edict['35'] = 'Br'
edict['36'] = 'Kr'
edict['37'] = 'Rb'
edict['38'] = 'Sr'
edict['39'] = 'Y'
edict['40'] = 'Zr'
edict['41'] = 'Nb'
edict['42'] = 'Mo'
edict['43'] = 'Tc'
edict['44'] = 'Ru'
edict['45'] = 'Rh'
edict['46'] = 'Pd'
edict['47'] = 'Ag'
edict['48'] = 'Cd'
edict['49'] = 'In'
edict['50'] = 'Sn'
edict['51'] = 'Sb'
edict['52'] = 'Te'
edict['53'] = 'I'
edict['54'] = 'Xe'
edict['55'] = 'Cs'
edict['56'] = 'Ba'
edict['57'] = 'La'
edict['58'] = 'Ce'
edict['59'] = 'Pr'
edict['60'] = 'Nd'
edict['61'] = 'Pm'
edict['62'] = 'Sm'
edict['63'] = 'Eu'
edict['64'] = 'Gd'
edict['65'] = 'Tb'
edict['66'] = 'Dy'
edict['67'] = 'Ho'
edict['68'] = 'Er'
edict['69'] = 'Tm'
edict['70'] = 'Yb'
edict['71'] = 'Lu'
edict['72'] = 'Hf'
edict['73'] = 'Ta'
edict['74'] = 'W'
edict['75'] = 'Re'
edict['76'] = 'Os'
edict['77'] = 'Ir'
edict['78'] = 'Pt'
edict['79'] = 'Au'
edict['80'] = 'Hg'
edict['81'] = 'Tl'
edict['82'] = 'Pb'
edict['83'] = 'Bi'
edict['84'] = 'Bo'
edict['85'] = 'At'
edict['86'] = 'Rn'
edict['87'] = 'Tr'
edict['88'] = 'Ra'
edict['89'] = 'Ac'
edict['90'] = 'Th'
edict['91'] = 'Pa'
edict['92'] = 'U'
edict['93'] = 'Np'
edict['94'] = 'Pu'
edict['95'] = 'Am'
edict['96'] = 'Cm'
#dictionary for ionization level
idict = {}
idict['0'] = 'I'
idict['1'] = 'II'
idict['2'] = 'III'
idict['3'] = 'IV'
idict['4'] = 'V'
idict['5'] = 'VI'
idict['6'] = 'VII'
#Convert from air to vacuum wavelengths
def air_vac(wav0):
wav0 = wav0/(1.0 + 2.735182*10**(-4.0) + (131.4182/wav0**2.) + (2.76249*10.**8./wav0**4.))
return wav0
#correct wavelengths for rv
def rv_correct(lam0,rv):
cs = 2.99792458e5 #km/s
lam = lam0*(np.sqrt((1.+(rv/cs))/(1.-(rv/cs))))
return lam
#Line EW measurement edit popup
class ew_popup:
global root
def __init__(self,parent):
top = self.top = Tk.Toplevel(parent)
# self.myLabel = Tk.Label(top,text='EW Edit')
# self.myLabel.pack()
self.gButton = Tk.Button(top,text='Gauss Fit',command=self.sendg)
self.gButton.pack(side=Tk.LEFT)
self.gButton = Tk.Button(top,text='Simpson Rule Integration',command=self.sends)
self.gButton.pack(side=Tk.LEFT)
self.gButton = Tk.Button(top,text='Voigt Fit',command=self.sendv)
self.gButton.pack(side=Tk.LEFT)
self.gButton = Tk.Button(top,text='Delete',command=self.sendd)
self.gButton.pack(side=Tk.LEFT)
self.gButton = Tk.Button(top,text='Cancel',command=self.sendc)
self.gButton.pack(side=Tk.LEFT)
def sendd(self):
self.editparm = 'd'
self.top.destroy()
def sendg(self):
self.editparm = 'g'
self.top.destroy()
def sendc(self):
self.editparm = 'c'
self.top.destroy()
def sendv(self):
self.editparm = 'v'
self.top.destroy()
def sends(self):
self.editparm = 's'
self.top.destroy()
#Class for asking for order number to plot first
#No longer used switched to text box
class order_popup:
global root
def __init__(self,parent):
top = self.top = Tk.Toplevel(parent)
self.myLabel = Tk.Label(top,text='Order')
self.myLabel.pack()
self.myEntryBox = Tk.Entry(top)
self.myEntryBox.pack()
self.mySubmitButton = Tk.Button(top,text='Submit',command=self.send)
self.mySubmitButton.pack()
def send(self):
self.order = self.myEntryBox.get()
self.top.destroy()
#Class for asking for fits dimension with echelle data
#Only does this if echelle data not in zeroth dimension
class dimension_popup:
global root
def __init__(self,parent):
top = self.top = Tk.Toplevel(parent)
self.myLabel = Tk.Label(top,text='Fits Dimension')
self.myLabel.pack()
self.myEntryBox = Tk.Entry(top)
self.myEntryBox.pack()
self.mySubmitButton = Tk.Button(top,text='Submit',command=self.send)
self.mySubmitButton.pack()
def send(self):
self.order = self.myEntryBox.get()
self.top.destroy()
#Main gui create and modification loop
class gui_c(Tk.Frame):
def __init__(self,parent):
Tk.Frame.__init__(self,parent,background='white')
self.parent = parent
#Default setting for finding the fits dimension
self.fitsd = 0
#Intialize clicker
self.click = 0
#Initial gauss find tolerance
self.findtol = .1
#set default order
self.order = 1
#radial velocity corrections to line list
# self.rv = -269.16
# self.hrv = -2.14
#radial velocity corrections to line list
self.rv = 0.0
self.hrv = 0.0
#looking fore continuum
self.seeking = False
#line list input
self.linelistin = False
#EW have been measured
self.ewmeas = False
#Order has been measured
self.oewmeas = False
#Edit EW measurement
self.ewedit = False
#maximum line width in one direction for gauss fit
self.maxlinewidth = 2.
#Make sure file is saved before you destroy data
self.saved = True
#Variable for simpsons integration
self.sint = False
#Stellar parameters for Moog and Marcs someday
self.teff = 0.
self.feh = 0.
self.logg = 0.
self.vturb = 0.
#Start the creation of the window and GUI
self.centerWindow()
self.FigureWindow()
self.initUI()
#Create area and window for figure
def FigureWindow(self):
#Create the figure
self.f,self.a = plt.subplots(figsize=(8,10))
#Create window for the plot
self.canvas = FigureCanvasTkAgg(self.f,master=self)
#Draw the plot
self.canvas.draw()
#Turn on matplotlib widgets
self.canvas.get_tk_widget().pack(side=Tk.TOP,fill=Tk.BOTH,expand=1)
#Display matplotlib widgets
self.toolbar = NavigationToolbar2TkAgg(self.canvas,self)
self.toolbar.update()
self.canvas._tkcanvas.pack(side=Tk.TOP,fill=Tk.BOTH,expand=1)
#Connect mpl to mouse clicking
self.f.canvas.mpl_connect('button_press_event',self.on_click_event)
#Connect mpl to mouse clicking
self.f.canvas.mpl_connect('key_press_event',self.on_key_event)
#create button for initialization of Continuum normalization
specframe = Tk.Frame(self)
#Check box for vaccuum correction
self.checkvar = Tk.IntVar()
self.C1 = Tk.Checkbutton(master=specframe,text="Vaccum",variable=self.checkvar,onvalue=1,offvalue=0,command=self.vac_mod)
self.C1.pack(side=Tk.LEFT)
#create button to go up an order
upbutton = Tk.Button(master=specframe,text='Increase Order',command=self.increaseorder)
upbutton.pack(side=Tk.LEFT)
#create button to go down an order
downbutton = Tk.Button(master=specframe,text='Decrease Order',command=self.decreaseorder)
downbutton.pack(side=Tk.LEFT)
#Put in same frame as other spec information
downbutton = Tk.Button(master=specframe,text='Normalize Continuum',command=self.click_cont)
downbutton.pack(side=Tk.LEFT)
#Create button to ask for Moog formated line list
mooggrab = Tk.Button(master=specframe,text='Linelist',command=self.line_grab)
mooggrab.pack(side=Tk.LEFT)
specframe.pack(side=Tk.TOP)
#Create button to ask for Moog formated line list
moogsave = Tk.Button(master=specframe,text='Save EW',command=self.line_save)
moogsave.pack(side=Tk.RIGHT)
#Crate button to reset plot limits without clearing data
plotrbutton = Tk.Button(master=specframe,text='Reset Limits',command=self.reset_limits)
plotrbutton.pack(side=Tk.RIGHT)
#Crate button to reset plot limits without clearing data
openbutton = Tk.Button(master=specframe,text='Open File',command=self.open_file)
openbutton.pack(side=Tk.RIGHT)
#Pack the spec frame together
# frame2 = Tk.Frame(self)
specframe.pack(side=Tk.TOP)
#Resets the limits of the plot
def reset_limits(self):
self.a.set_xlim([self.wav0.min(),self.wav0.max()])
maxer = np.max(self.spec0[self.spec0.size/2-.1*self.spec0.size:self.spec0.size/2+.1*self.spec0.size])
self.a.set_ylim([0.,maxer*.1+maxer])
self.canvas.draw()
#Save Updated EW
def line_save(self):
self.linelistout = Tkf.asksaveasfile(mode='w')
if self.linelistout is None:
return
if self.checkvar.get() == 0:
#Write out line ew in MOOG format
for i,j in enumerate(self.line):
self.linelistout.write(' {0:10} {1:10} {2:10} {3:10} {5} {4:10}\n'.format(str(round(self.line[i],2)),str(self.lineel[i]),str(round(self.lineep[i],2)),str(round(self.linegf[i],2)),str(round(self.lineew[i],2)),' '*20))
#Always write out in air wavelengths
if self.checkvar.get() == 1:
#Write out line ew in MOOG format
for i,j in enumerate(self.aline):
self.linelistout.write(' {0:10} {1:10} {2:10} {3:10} {5} {4:10}\n'.format(str(round(self.aline[i],2)),str(self.lineel[i]),str(round(self.lineep[i],2)),str(round(self.linegf[i],2)),str(round(self.lineew[i],2)),' '*20))
#close the file
self.linelistout.close()
#Removes flag to warn user of unsave data
self.saved = True
#Grab Moog line list
def line_grab(self):
m = 0
#If the values already exist set the line ew measure to True
self.ewmeas = True
while m == 0:
self.linelist = self.get_file()
try:
fil = open(self.linelist,'r')
fils = fil.readlines()[1:]
line = []
lineel = []
lineels = []
lineep = []
linegf = []
lineew = []
for i in fils:
line.append(float(i[:10]))
lineel.append(i[10:20].replace(' ',''))
eltext = i[10:20].replace(' ','').split('.')
ion = idict[eltext[1]]
el = edict[eltext[0]]
lineels.append(el+ion)
lineep.append(float(i[20:30]))
linegf.append(float(i[30:40]))
#Try to grab EW from line list file
try:
lineew.append(round(float(i[50:70]),1))
#Append value -9999.9 if EW have not been measured
except IndexError:
#If the any values don't exist set the line ew measure to False
self.ewmeas = False
lineew.append(round(-9999.9,1))
m =1
except ValueError:
self.error = 7
self.onError()
#Convert the lists to numpy arrays and read them into a variable in the object
self.line = np.array(line)
self.lineel = np.array(lineel)
self.lineels = np.array(lineels)
self.lineep = np.array(lineep)
self.linegf = np.array(linegf)
self.lineew = np.array(lineew)
self.linelistin = True
self.text_loc()
self.plot_lines()
#Find location for labeling lines
def text_loc(self):
#If the spectrum is normalized just point linelist at normal y values
if np.median(self.spec0) < 10:
self.elloc = 1.1+np.zeros(self.line.size)
self.ewloc = 1.2+np.zeros(self.line.size)
#Else try half heartly to fit the continuum and scale
else:
forfit, = np.where(np.abs(self.spec0-np.median(self.spec0)) <= np.std(self.spec0)*3.)
input = rv_correct(self.line,self.rv-self.hrv)
specloc = np.interp(input,self.wav0[forfit],self.spec0[forfit])
self.elloc = specloc*.1+specloc
self.ewloc = specloc*.15+specloc
#Plot lines from the Moog line list
def plot_lines(self):
maxw = max(self.wav0)
minw = min(self.wav0)
findlines = rv_correct(self.line,self.rv-self.hrv)
use, = np.where((findlines >= minw) & (findlines <= maxw))
#locations of lines in order
#location on spectrum
self.olines = findlines[use]
#rest wavelengths of lines in order
self.orestlines = self.line[use]
#An array containing the text values to be updated when a manual measurement is applied
self.ewtext = []
for i in use:
self.a.text(rv_correct(self.line[i],self.rv-self.hrv),self.elloc[i],str(self.lineels[i]),rotation='vertical')
self.ewtext.append(self.a.text(rv_correct(self.line[i],self.rv-self.hrv),self.ewloc[i],str(self.lineew[i]),rotation='vertical'))
self.canvas.draw()
#Do wavelength conversion of linelist to vaccum
def vac_mod(self):
#release cursor from entry box and back to the figure
#needs to be done otherwise key strokes will not work
self.f.canvas._tkcanvas.focus_set()
self.aline = self.line.copy()
if ((self.checkvar.get() == 1) & (self.linelistin)):
self.line = air_vac(self.line)
self.spec_plot()
else:
#Uncheck box if linelist is not read in
if (self.linelistin) == False:
self.error = 8
self.onError()
#Uncorrect if mistakenly pressed
if (self.checkvar.get() == 0):
self.line = self.aline
self.spec_plot()
self.C1.deselect()
#Basic click event
def on_click_event(self,click):
#Click envents for continuum selection
if self.seeking:
self.contx.append(click.xdata)
self.conty.append(click.ydata)
self.pcontx.append(click.x)
self.pconty.append(click.y)
self.a.scatter(click.xdata,click.ydata,marker='x',color='red',zorder=8,s=35)
self.canvas.draw()
if self.ewedit :
self.contx.append(click.xdata)
self.conty.append(click.ydata)
self.mangaussfitlist.append(self.a.scatter(click.xdata,click.ydata,marker='x',color='red',zorder=8,s=35))
#Commands to inform the user what is available during line fitting
self.clickcommands.remove()
if len(self.contx) == 1:
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Click Line Center')
if len(self.contx) == 2:
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Left FWHM')
if len(self.contx) == 3:
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Right FWHM')
if len(self.contx) == 4:
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Right Continumm')
self.canvas.draw()
#Once all data points have been ented
if len(self.contx) == 5:
self.calc_gauss()
self.check_ew()
#Remove the X markers from the gaussian fit
for i in self.mangaussfitlist:
i.set_visible(False)
self.canvas.draw()
#Do simpsons rule integration
if self.sint:
self.contx.append(click.xdata)
self.conty.append(click.ydata)
self.intlist.append(self.a.scatter(click.xdata,click.ydata,marker='x',color='red',zorder=8,s=35))
self.clickcommands.remove()
if len(self.contx) == 1:
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Right Side')
#Do the integration
if len(self.contx) == 2:
range, = np.where((self.wav0 >= np.min(self.contx)) & (self.wav0 <= np.max(self.contx)))
self.ewupdate = 1000.*simps(1.-self.spec0[range],x=self.wav0[range],even='avg')
self.canvas.draw()
#check EW
self.check_ew()
try:
self.fitlist[self.editewlineindex][0].remove()
except ValueError:
print 'line already removed'
self.fitlist[self.editewlineindex] = self.a.plot(self.wav0[range],self.spec0[range],color='red',zorder=8)
#Remove the X markers from the gaussian fit
for i in self.intlist:
i.set_visible(False)
self.canvas.draw()
#Function to check if the EW should be saved
def check_ew(self):
if box.askyesno('Save','Should the New EW be added? \n '+str(round(self.ewupdate,2))):
close = np.abs(self.orestlines[self.editewlineindex]-self.line)
update, = np.where(close == close.min())
self.lineew[update] = round(float(self.ewupdate),2)
#Plot the new EW
self.ewtext[self.editewlineindex].remove()
self.ewtext[self.editewlineindex] = self.a.text(rv_correct(self.line[update],self.rv-self.hrv),self.ewloc[update],str(self.lineew[update][0]),color='red',rotation='vertical')
self.canvas.draw()
#After the ew has been calculated remind use to save if they quit
self.saved = False
else:
self.fitlist[self.editewlineindex][0].remove()
#After the all the points have been clicked drop editing and calculate gaussian
self.ewedit = False
# self.spec_plot()
#Select points for continuum normalization
def click_cont(self):
if self.seeking == False:
self.seeking = True
self.contx = []
self.conty = []
#Pixel value so points can be deleted
self.pcontx = []
self.pconty = []
self.clickcommands = self.a.text(np.median(self.wav0),0.0,'Click on the Continuum then Click q to quit and fit \n d deletes bad points')
self.canvas.draw()
#Actually normalize the continuum
def cont_norm(self):
#First a second order legendre polynomail
#Changed to spline based on Gray Analysis of stellar photospheres
# lcoef = np.polynomial.legendre.legfit(self.contx,self.conty,len(self.contx)-1)
if len(self.contx) > 3:
#sort the continuum in x
sorted = np.argsort(np.array(self.contx))
#Spline interpolation of continuum points
fspline = interp1d(np.array(self.contx)[sorted],np.array(self.conty)[sorted],kind='cubic',bounds_error=False)
#Calc the continuum using spline
# self.cont = np.polynomial.legendre.legval(self.wav0,lcoef)
self.cont = fspline(self.wav0)
contplot = self.a.plot(self.wav0,self.cont,color='red',zorder=16)
self.canvas.draw()
#If the continuum normalization is good apply it
if box.askyesno("Continuum",'Is the Continuum Normalization Good?'):
self.spec.data[self.order-1] = self.spec0/self.cont
self.spec_set()
self.spec_plot()
else:
#Ask to keep points in array if continuum normalization is improper
if box.askyesno('Continuum','Keep Trying?'):
self.seeking = True
#remove pervious continuum
contplot[0].remove()
else:
self.spec_plot()
else:
#Error if not enough points for spline fit
self.error = 15
self.seeking = True
self.onError()
#Defind key strokes
def on_key_event(self,event):
#Exits continuum normalization and fits
if event.key == 'q':
self.seeking = False
self.cont_norm()
#Deletes continuum normalization point
if event.key == 'd':
if ((self.seeking) & (self.ewedit == False)) :
subx = np.array(self.pcontx)-event.x
suby = np.array(self.pconty)-event.y
close = np.sqrt(subx**2.+suby**2.)
delcol, = np.where(close == np.min(close))
delcol = delcol[0]
#Delete index with closest pixel value to where d was pressed
pointx = self.contx[delcol]
pointy = self.conty[delcol]
del self.conty[delcol]
del self.contx[delcol]
del self.pconty[delcol]
del self.pcontx[delcol]
self.a.scatter(pointx,pointy,marker='x',color='blue',zorder=15,s=35)
self.canvas.draw()
#key event for editing EW Measurement
if event.key == 'e':
#If the EW for a given measure have been measured
# if (((self.oewmeas[self.order-1]) | (self.ewmeas)) & (self.seeking == False)):
if (self.seeking == False):
subx = np.array(self.olines)-event.xdata
#Find the nearest point in wavelength space
self.editewlineindex, = np.where(np.abs(subx) == np.min(np.abs(subx)))
#If two points are the same distance away error
if self.editewlineindex.size > 1:
self.error = 12
else:
self.edit_ew()
else:
self.error = 11
self.onError()
#Another way to trigger continuum normalization
if event.key == 'c':
self.click_cont()
#Keystroke to reset limits
if event.key == 'r':
self.reset_limits()
#Another way to add and subtract orders to be similar to IRAF
if event.key == 'j':
self.decreaseorder()
#Another way to add and subtract orders to be similar to IRAF
if event.key == 'k':
self.increaseorder()
#automattically tries to fit gaussian to lines in line list
if event.key == 'a':
self.auto_gauss_fit()
#Edit EW measurement
def edit_ew(self):
#set EW range to focus on line
line = self.olines[self.editewlineindex]
self.a.set_xlim([line-5.,line+5.])
findmin, = np.where((self.wav0 >= line-5.) & (self.wav0 <= line+5.))
ymin = np.min(self.spec0[findmin])
self.a.set_ylim([ymin,1.2])
self.ymarker = np.abs(np.diff(self.a.get_ylim()))*.1+np.min(self.a.get_ylim())
self.canvas.draw()
choice = ew_popup(root)
root.wait_window(choice.top)
edit = choice.editparm
#manual gauss fit
if edit == 'g':
#points for the gaussian fit
self.mangaussfitlist = []
self.man_gauss_fit()
#replace line EW with -9999.9
if edit == 'd':
minfind = np.abs(self.orestlines[self.editewlineindex]-self.line)
remove, = np.where(minfind == minfind.min())
self.lineew[remove] = -9999.9
#Plot the new EW
self.ewtext[self.editewlineindex].remove()
self.ewtext[self.editewlineindex] = self.a.text(rv_correct(self.line[remove],self.rv-self.hrv),self.ewloc[remove],str(self.lineew[remove][0]),color='red',rotation='vertical')
#Remove poor fits
try:
self.fitlist[self.editewlineindex][0].remove()
except ValueError:
print 'line already removed'
self.canvas.draw()
#Return to normal part of spectrum
self.reset_limits()
#Do straight integration
if edit == 's':
self.intlist = []
self.man_int()
#Do not edit if c go straight to spec_plot
if edit == 'c':
self.reset_limits()
#Do a Voigt fit Not yet implimented
if edit == 'v':
self.error = 9
self.onError()
#Return to normal part of spectrum
self.reset_limits()
#Calculate a Gaussian from manual fit
def calc_gauss(self):
#First element is the continuum
cont = (self.conty[0]+self.conty[-1])/2.
#Second element is line center
depth = cont-self.conty[1]
#Width of feature (estimate of 2simga
width = np.abs(self.contx[2]-self.contx[3])
g_model = models.Gaussian1D(amplitude=depth,mean=self.contx[1],stddev=width/2.)
fit_g = fitting.LevMarLSQFitter()
#Use input parameters to find LSQ fit to 3 sigma of line
use, = np.where((self.wav0 >= g_model.mean-3*(width/2.)) & (self.wav0 <= g_model.mean+3.*(width/2.)))
gauss_out = fit_g(g_model,self.wav0[use],1.-self.spec0[use])
#Read EW in mA
ew = 1000.*gauss_out.amplitude.value*1.06447*2.*np.abs(gauss_out.stddev.value)
#Plot found EW
sig3 = np.linspace(self.contx[1]-4.*gauss_out.stddev.value,self.contx[1]+4.*gauss_out.stddev.value,100)
#Put gaussian plots in a list so they can be deleted if unsatisfactory
#First remove old plot
#need the zero since for whatever reason plots are different than text
try:
self.fitlist[self.editewlineindex][0].remove()
except ValueError:
print 'No previous measure'
#Now plot new gaussian in place
self.fitlist[self.editewlineindex] = self.a.plot(np.array(sig3),1.-gauss_out(sig3),color='red',zorder=3)
self.a.text(self.contx[1],.5,str(round(ew,1)),color='red',rotation='vertical')
self.canvas.draw()
# the EW to pass to the plotting routine
self.ewupdate = round(ew,1)
#Simpson's Rule integration
def man_int(self):
self.sint = True
self.contx = []
self.conty = []
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Click Left intregation point')
self.canvas.draw()
#Command to manually fit Gaussian
def man_gauss_fit(self):
self.ewedit = True
self.contx = []
self.conty = []
self.clickcommands = self.a.text(self.olines[self.editewlineindex],self.ymarker,'Click Left Continuum')
self.canvas.draw()
#Command to fit gaussians to lines in given order
def auto_gauss_fit(self):
if self.linelistin:
#set EW measured flag for order to be true
self.oewmeas[self.order-1] = True
#List for gaussian plots
self.fitlist = []
#Use the min and maximum wavlength values in order to search for lines
maxw = max(self.wav0)
minw = min(self.wav0)
findlines = rv_correct(self.line,self.rv-self.hrv)
use, = np.where((findlines >= minw) & (findlines <= maxw))
#array for newly calculated ew
newew = []
for i in use:
# self.a.set_xlim([findlines[i]-5.,findlines[i]+5.])
#look for minimum value with some tolerance value of line center (initally setting to .1)
lookbot, = np.where(np.abs(self.wav0-findlines[i]) <= self.findtol)
minspec = self.spec0[lookbot].min()
lookbot, = np.where(self.spec0 == minspec)
#Only use line if minimum is found
if lookbot.size > 0:
minwav = self.wav0[np.where(self.spec0 == minspec)][0]
#Find the line depth and half max
depthspec = np.float(1.-minspec)
halfspec = 1.-(depthspec*.5)
#find the blue line side information
#If more than one minimum pick the first
bluefind = lookbot[0]
listwav = []
listspec = []
distance = 0.
#look for the half max unless it is larger than 2 Angstroms in width
while ((self.spec0[bluefind] <= halfspec) & (distance <= self.maxlinewidth) & (bluefind >= 0)):
bluefind = bluefind-1
listwav.append(np.float(self.wav0[bluefind]))
listspec.append(1.-self.spec0[bluefind])
distance = minwav-self.wav0[bluefind]
#reverse the blue wavelengths so they are in the correct order
listwav.reverse()
listspec.reverse()
#append the center of the line to the list
listwav.append(np.float(minwav))
listspec.append(depthspec)
#find the red line side information
redfind = lookbot[0]
distance = 0.
#look for the half max unless it is larger than 2 Angstroms in width
while ((self.spec0[redfind] <= halfspec) & (distance <= self.maxlinewidth) & (redfind < self.spec0.size-2)):
redfind = redfind+1
listwav.append(np.float(self.wav0[redfind]))
listspec.append(1.-self.spec0[redfind])
distance = self.wav0[redfind]-minwav
#Only include lines with greater than 3 pixels in the line
#and the sigma is greater than 1 pixel seperation in wavelength
redhm = np.mean(listwav[-2:])
bluehm = np.mean(listwav[:2])
sigmaest = (redhm-bluehm)/2.
if ((len(listspec) > 2) & (sigmaest >= np.diff(self.wav0).min())):
#Fit a gaussian model to EW
g_model = models.Gaussian1D(amplitude=depthspec,mean=minwav,stddev=sigmaest)
fit_g = fitting.LevMarLSQFitter()
#Use input parameters to find LSQ fit to 3 sigma of line
useg, = np.where((self.wav0 >= g_model.mean-3*(sigmaest)) & (self.wav0 <= g_model.mean+3.*(sigmaest)))
gauss_out = fit_g(g_model,self.wav0[useg],1.-self.spec0[useg])
# gauss_out = fit_g(g_model,np.array(listwav),np.array(listspec))
#Read EW in mA
ew = 1000.*gauss_out.amplitude.value*1.06447*2.*np.abs(gauss_out.stddev.value)
if ew < 200.:
#Append new EW to list
newew.append(round(ew,1))
#Plot found EW
sig3 = np.linspace(minwav-4.*gauss_out.stddev.value,minwav+4.*gauss_out.stddev.value,100)
self.fitlist.append(self.a.plot(np.array(sig3),1.-gauss_out(sig3),color='red',zorder=3))
#Up
self.a.text(findlines[i],.6,str(round(ew,1)),color='red',rotation='vertical')
else:
newew.append(round(-9999.9,1))
self.fitlist.append(self.a.plot(np.arange(2),np.arange(2),color='red',zorder=3))
#need the zero since for whatever reason plots are different than text
self.fitlist[-1][0].remove()
self.a.text(findlines[i],.5,str(-9999.9),color='red',rotation='vertical')
else:
newew.append(-9999.9)
#Create the plot so everything remains in the same order but immediately drop the plot from the window
self.fitlist.append(self.a.plot(np.arange(2),np.arange(2),color='red',zorder=3))
#need the zero since for whatever reason plots are different than text
self.fitlist[-1][0].remove()
self.a.text(findlines[i],.2,str(-9999.9),color='red',rotation='vertical')
# print listspec
# print listwav
# print 'No line found'
#draw found EW
else:
newew.append(-9999.9)
#Create the plot so everything remains in the same order but immediately drop the plot from the window
self.fitlist.append(self.a.plot(np.arange(2),np.arange(2),color='red',zorder=3))
#need the zero since for whatever reason plots are different than text
self.fitlist[-1][0].remove()
self.a.text(findlines[i],.4,str(-9999.9),color='red',rotation='vertical')
self.canvas.draw()
#Ask if Line EW should be updated
if box.askyesno('Update','Should EW be Updated?'):
#Update lines in order
for j,i in enumerate(use):
self.lineew[i] = newew[j]
#Plot the new EW
self.ewtext[j].remove()
self.ewtext[j] = self.a.text(findlines[i],self.ewloc[i],str(self.lineew[i]),color='red',rotation='vertical')
self.canvas.draw()
#Remind the used the changes have not been saved if they try to exit
self.saved = False
# self.spec_plot()
else:
self.error = 8
self.onError()
self.canvas.draw()
#Command to increase the order to plot new spectrum
def increaseorder(self):
self.order = self.order+1
if self.order > self.ordermax:
self.order = 1
self.sorder.set(str(int(self.order)))
self.a.clear()
self.spec_set()
self.spec_plot()
#Command to decrease order to plot new spectrum
def decreaseorder(self):
self.order = self.order-1
if self.order < 1:
self.order = self.ordermax
self.sorder.set(str(int(self.order)))
self.a.clear()
self.spec_set()
self.spec_plot()
#Create window in center of screen
def centerWindow(self):
w = 2000
h = 1200
sw = self.parent.winfo_screenwidth()
sh = self.parent.winfo_screenheight()
x = (sw-w)/2
y = (sh-h)/2
self.parent.geometry('%dx%d+%d+%d' % (w,h,x,y))
#Intialize the GUI
def initUI(self):
self.parent.title("Echelle Spectrum Analysis")
#create frame for plotting
frame = Tk.Frame(self,relief=Tk.RAISED,borderwidth=1)
frame.pack(fill=Tk.BOTH,expand=1)
self.pack(fill=Tk.BOTH,expand=1)
#set up okay and quit buttons
quitButton = Tk.Button(self,text="Quit",command=self.onExit)
quitButton.pack(side=Tk.RIGHT,padx=5,pady=5)
saveButton = Tk.Button(self,text="Save Fits",command=self.save_fits)
saveButton.pack(side=Tk.RIGHT)
#set up velocity information to be added to line list
rvText = Tk.StringVar()
rvText.set("Radial Velocity (km/s)")
rvDir = Tk.Label(self,textvariable=rvText,height=4)
rvDir.pack(side=Tk.LEFT)
#Add so Velocity can be updated
rv = Tk.StringVar()
rv.set(str(round(self.rv,2)))
self.rvval = Tk.Entry(self,textvariable=rv,width=10)
self.rvval.bind("<Return>",self.rv_callback)
self.rvval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up heliocentric velocity information to be added to line list
hrvText = Tk.StringVar()
hrvText.set("Heliocentric Correction (km/s)")
hrvDir = Tk.Label(self,textvariable=hrvText,height=4)
hrvDir.pack(side=Tk.LEFT)
#Add so Velocity can be updated
hrv = Tk.StringVar()
hrv.set(str(round(self.hrv,2)))
self.hrvval = Tk.Entry(self,textvariable=hrv,width=10)
self.hrvval.bind("<Return>",self.rv_callback)
self.hrvval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up effective temperature
teffText = Tk.StringVar()
teffText.set("Teff (K)")
teffDir = Tk.Label(self,textvariable=teffText,height=4)
teffDir.pack(side=Tk.LEFT)
#Add so effective temperature can be updated
teff = Tk.StringVar()
teff.set(str(round(self.teff,2)))
self.teffval = Tk.Entry(self,textvariable=teff,width=10)
self.teffval.bind("<Return>",self.stellar_param)
self.teffval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up [Fe/H]
fehText = Tk.StringVar()
fehText.set("[Fe/H]")
fehDir = Tk.Label(self,textvariable=fehText,height=4)
fehDir.pack(side=Tk.LEFT)
#Add so [Fe/H] can be updated
feh = Tk.StringVar()
feh.set(str(round(self.feh,2)))
self.fehval = Tk.Entry(self,textvariable=feh,width=10)
self.fehval.bind("<Return>",self.stellar_param)
self.fehval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up log(g)
loggText = Tk.StringVar()
loggText.set("log(g)")
loggDir = Tk.Label(self,textvariable=loggText,height=4)
loggDir.pack(side=Tk.LEFT)
#Add so log(g) can be updated
logg = Tk.StringVar()
logg.set(str(round(self.logg,2)))
self.loggval = Tk.Entry(self,textvariable=logg,width=10)
self.loggval.bind("<Return>",self.stellar_param)
self.loggval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up microturbulent velocity
vturbText = Tk.StringVar()
vturbText.set("Vturb (km/m)")
vturbDir = Tk.Label(self,textvariable=vturbText,height=4)
vturbDir.pack(side=Tk.LEFT)
#Add so microturbulent velocity can be updated
vturb = Tk.StringVar()
vturb.set(str(round(self.vturb,2)))
self.vturbval = Tk.Entry(self,textvariable=vturb,width=10)
self.vturbval.bind("<Return>",self.stellar_param)
self.vturbval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up order number
orderText = Tk.StringVar()
orderText.set("Order")
orderDir = Tk.Label(self,textvariable=orderText,height=4)
orderDir.pack(side=Tk.LEFT)
#Add so order number can be updated
self.sorder = Tk.StringVar()
self.sorder.set(str(int(self.order)))
self.orderval = Tk.Entry(self,textvariable=self.sorder,width=10)
self.orderval.bind("<Return>",self.on_order_box)
self.orderval.pack(side=Tk.LEFT,padx=5,pady=5)
#set up popup menu
self.menu = Tk.Menu(self.parent,tearoff=0)
self.menu.add_command(label="Beep",command=self.bell())
self.menu.add_command(label="Exit",command=self.onExit)
self.parent.bind("<Button-3>",self.showMenu)
self.pack()
#set up Submenu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Tk.Menu(menubar)
subMenu = Tk.Menu(fileMenu)
subMenu.add_command(label="New File",command=self.open_file)
fileMenu.add_cascade(label="Import",menu=subMenu,underline=0)
#create another item in menu
fileMenu.add_separator()
fileMenu.add_command(label='Exit',underline=0,command=self.onExit)
menubar.add_cascade(label="File",underline=0,menu=fileMenu)
self.open_file()
def open_file(self):
#ask to open fits file
self.filename = self.get_file()
#open the fits file
self.fitfile = self.check_spec()
#number of dimension of fits file
self.dmax = len(self.fitfile)
#use the fits dimension with the spectrum in it (default is 0)
m = 0
while m == 0:
try:
self.ordermax = self.fitfile[self.fitsd].shape[0]
m = 1
except IndexError:
#ask for fits dimension with ordered spectrum inside
self.error = 2
self.onError()
self.fitsd = self.get_fitsd()
#set up boolean array with whether order has been measured or not
self.oewmeas = np.ones(self.ordermax,dtype=bool)
#Set the default pyfits information to be extracted
self.spec = self.fitfile[self.fitsd]
#starts plotting the fits file
#First ask for order
#Changed to text box
# self.order = self.on_order()
#set standard header
self.standard_header()
#Set up the infromation need to plot the spectrum
self.spec_set()
#Draw said spectrum
self.spec_plot()
#RV Callback corrects line list for radial velocities
def rv_callback(self,event):
#release cursor from entry box and back to the figure
#needs to be done otherwise key strokes will not work
self.f.canvas._tkcanvas.focus_set()
if self.linelistin:
self.rv = float(self.rvval.get())
self.hrv = float(self.hrvval.get())
self.spec_plot()
self.text_loc()
self.plot_lines()
else:
self.error = 8
self.onError()
def stellar_param(self,event):
#release cursor from entry box and back to the figure
#needs to be done otherwise key strokes will not work
self.f.canvas._tkcanvas.focus_set()
try:
self.teff = float(self.teffval.get())
self.feh = float(self.fehval.get())
self.logg = float(self.loggval.get())
self.vturb = float(self.vturbval.get())
#error if not floats
except ValueError:
self.error = 10
self.onError()
#save fits file
def save_fits(self):
self.fileout = Tkf.asksaveasfilename(filetypes=(('FITS File','*.fits'),('FITS File','*.FITS'),('FITS File','*.fit'),('FITS File','*.FIT')))
#cannot overwrite current file or core will dump
if self.fileout == self.fitfile.filename():
self.error = 14
self.onError()
else:
try:
self.spec.writeto(self.fileout,output_verify='ignore')
except IOError:
if box.askyesno("Over write file?","Do you want to over write "+self.fileout+"?"):
self.spec.writeto(self.fileout,clobber=True)
#Function to get the dimension of
def get_fitsd(self):
m = 0
while m == 0:
try:
inputd = dimension_popup(root)
root.wait_window(inputd.top)
dim = int(inputd.order)
if ((dim > 0) & (dim <= self.dmax)):
m = 1
else:
#Error order is out of range
self.error = 5
self.onError()
except ValueError:
#Error order is not an integer
self.error = 4
self.onError()
return dim
#Function for retrieving order from popup
def on_order(self):
m = 0
while m == 0:
try:
inputO = order_popup(root)
root.wait_window(inputO.top)
order = int(inputO.order)
if ((order > 0) & (order <= self.ordermax)):
m = 1
else:
#Error order is out of range
self.error = 3
self.onError()
except ValueError:
#Error order is not an integer
self.error = 4
self.onError()
return order
#Function for retrieving order from text box
def on_order_box(self,event):
#release cursor from entry box and back to the figure
#needs to be done otherwise key strokes will not work
self.f.canvas._tkcanvas.focus_set()
m = 0
while m == 0:
try:
order = self.orderval.get()
order = int(order)
if ((order > 0) & (order <= self.ordermax)):
m = 1
self.order = order
self.spec_set()
self.spec_plot()
else:
#Error order is out of range
self.error = 3
self.onError()
except ValueError:
#Error order is not an integer
self.error = 4
self.onError()
#Shows a menu with right click
def showMenu(self,e):
self.menu.post(e.x_root,e.y_root)
#Exits the program
def onExit(self):
if self.saved == False:
if box.askyesno('Warning','You have unsaved EW Data!\n Are you sure you want to Exit?'):
self.quit()
else:
self.line_save()
else:
self.quit()
#Tells Why Order information is incorrect
def onError(self):
if self.error == 1:
box.showerror("Error","File Not Found")
if self.error == 2:
box.showerror("Error","Fits Dimension Does not contain proper ordered Spectrum")
if self.error == 5:
box.showerror("Error","Fits Dimension is Out of range (0-"+str(self.dmax)+")")
if self.error == 3:
box.showerror("Error","Order Value is out of Range (1-"+str(self.ordermax)+')')
if self.error == 4:
box.showerror("Error","Value Must be an Integer")
if self.error == 6:
box.showerror("Error","File is not in Fits Format")
if self.error == 7:
box.showerror("Error","File is not formated for Moog")
if self.error == 8:
box.showerror("Error","No line list has been read")
if self.error == 9:
box.showerror("Error","Voigt Function is Currently in Developement")
if self.error == 10:
box.showerror("Error","Value Must be Float")
if self.error == 11:
box.showerror("Error","EW Have not been Measured (press a)")
if self.error == 12:
box.showerror("Error","Could not determine line desired for editing \n Please try again")
if self.error == 14:
box.showerror("Error","Cannot overwrite currently opened fits file \n Please choose a different file name")
if self.error == 15:
box.showerror("Error","Not enough points in continuum \n Please try again")
#routine for opening file
#check to make sure file is found
def get_file(self):
filename = Tkf.askopenfilename()
while os.path.isfile(filename) == False:
self.error = 1
self.onError()
filename = Tkf.askopenfilename()
return filename
#make sure file is a fits file
def check_spec(self):
ffile = 0
while ffile == 0:
try:
fitfile = pyfits.open(self.filename)
ffile = 1
except IOError:
self.error = 6
self.onError()
self.filename = self.get_file()
return fitfile
#Uses IRAF WAT Headers
def standard_header(self):
text = ''
for i in self.spec.header["WAT2*"].itervalues():
add = i
#Correct for any WAT Values less than 68 Characters
while len(add) < 68:
add = add+' '
text = text+add
#Create an array containing one order of information per array
# text = text.replace('spe c','spec').replace('sp ec','spec').replace('s pec','spec')
breakt = text.replace('multipspec ','').split('spec')[2:]
#loop over all orders creating a standard header which will be able to be read
for i,j in enumerate(breakt):
split = j.split(' = ')
astr = split[0]
while len(astr) < 2:
astr = '0'+astr
wavd = split[1].split(' ')
try:
lam0 = np.float(wavd[3])
dlam = np.float(wavd[4])
self.spec.header['CRVL1_'+astr] = lam0
self.spec.header['CDLT1_'+astr] = dlam
except ValueError:
print 'Trying any way'
#set variables spectrum of a given order
def spec_set(self):
ostring = str(self.order)
while len(ostring) < 2:
ostring = '0'+ostring
self.dlam = self.spec.header['CDLT1_'+ostring]
self.lam0 = self.spec.header['CRVL1_'+ostring]
self.wav0 = np.arange(self.spec.data[self.order-1].size).astype(float)*self.dlam+self.lam0
self.spec0 = self.spec.data[self.order-1]
#actually plot the spectrum
def spec_plot(self):
self.a.clear()
self.a.plot(self.wav0,self.spec0,color='black',zorder=1)
self.a.set_xlabel(r'Wavelength ($\AA$)')
self.a.set_ylabel(r'Counts')
self.a.set_xlim([min(self.wav0),max(self.wav0)])
self.canvas.draw()
if self.linelistin:
self.text_loc()
self.plot_lines()
#Starts the main loop of the program
def main():
#Main loop
global root
root = Tk.Tk()
app = gui_c(root)
root.mainloop()
if __name__=="__main__":
#create root frame
main()