diff --git a/.gitignore b/.gitignore
index 6ec8b17..4014722 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,18 @@
resources/runbefore
PyHammerResults*
temp_input_*
-*.csv
\ No newline at end of file
+*.csv
+.DS_Store
+guess_type.txt
+measure_temp_Halpha.py
+PyHammer.app/Contents/Info.plist
+PyHammer.app/Contents/MacOS/applet
+PyHammer.app/Contents/PkgInfo
+PyHammer.app/Contents/Resources/applet.icns
+PyHammer.app/Contents/Resources/applet.rsrc
+PyHammer.app/Contents/Resources/description.rtfd/TXT.rtf
+PyHammer.app/Contents/Resources/Scripts/main.scpt
+PyHammer.app/Icon
+resources/PyHammer.scpt
+resources/templates/allspec.txt
+PyHammer.app/Icon
diff --git a/README.md b/README.md
index 58e5e11..b68c895 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,37 @@
# PyHammer
[![GitHub release](https://img.shields.io/github/release/BU-hammerTeam/PyHammer.svg)](https://github.com/BU-hammerTeam/PyHammer/releases/latest)
-[![GitHub commits](https://img.shields.io/github/commits-since/BU-hammerTeam/PyHammer/v1.2.0.svg)](https://github.com/BU-hammerTeam/PyHammer/commits/master)
+[![GitHub commits](https://img.shields.io/github/commits-since/BU-hammerTeam/PyHammer/v2.0.0.svg)](https://github.com/BU-hammerTeam/PyHammer/commits/master)
[![GitHub issues](https://img.shields.io/github/issues/BU-hammerTeam/PyHammer.svg)](https://github.com/BU-hammerTeam/PyHammer/issues)
[![license](https://img.shields.io/github/license/BU-hammerTeam/PyHammer.svg)](https://github.com/BU-hammerTeam/PyHammer/blob/master/license.txt)
[![Python Supported](https://img.shields.io/badge/Python%20Supported-3-brightgreen.svg)](conda)
-[![Maintenance](https://img.shields.io/maintenance/yes/2018.svg)]()
+[![Maintenance](https://img.shields.io/maintenance/yes/2020.svg)]()
+[![Powered by Astropy](https://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat)](http://www.astropy.org)
### A Python Spectral Typing Suite
PyHammer is a tool developed to allow rapid and automatic spectral classification of stars according to the Morgan-Keenan classification system. Working in the range of 3,650 - 10,200 Angstroms, the automatic spectral typing algorithm compares important spectral lines to template spectra and determines the best matching spectral type, ranging from O to L type stars. This tool has the additional features that it can determine a star's metallicity ([Fe/H]) and radial velocity shifts. Once the automatic classification algorithm has run, PyHammer provides the user an interface for determining spectral types visually by comparing their spectra to provided templates.
+Version 2.0.0 of PyHammer adds the ability to spectral type double-lined spectroscopic binaries (SB2). This updates adds new SB2 templates which were constructed in natural units (i.e. ergs /s /Å) using GaiaDR2 distances. This is done using a library of luminosity normalized spectra. This library was created from a combination of the [MaStar](https://www.sdss.org/surveys/mastar/) survey from [SDSS-IV](https://www.sdss.org) and the [Pickles+1998](https://ui.adsabs.harvard.edu/abs/1998PASP..110..863P/abstract) library. The Pickles library was used for OBAF stars while MaStar and SDSS was used for the GKM, C, WD stars.
+
Modeled after [The Hammer: An IDL Spectral Typing Suite][thehammer] published in [Covey et al. 2007][covey+07] available on [GitHub][hammerGitHub].
See the [PyHammer Wiki](https://github.com/BU-hammerTeam/PyHammer/wiki) for more information on how to install and use this program.
+Information on how the luminosity spectra are added to create SB2 templates can be found in the [Roulston+2020][Roulston_arXiv] paper.
+
### Publications
-PyHammer is detailed in our accepted Astrophysical Journal Supplements [Kesseli et al. (2017)][apjs].
+PyHammer 1.0.0 is detailed in our accepted Astrophysical Journal Supplements [Kesseli et al. (2017)][Kesseli_apjs].
+
+PyHammer 2.0.0 is detailed in our upcoming Astrophysical Journal Supplements [Roulston et al. (2020)][Roulston_arXiv]
+
+![GUI](./resources/PyHammer2_GUI.png?raw=true)
[thehammer]: http://myweb.facstaff.wwu.edu/~coveyk/thehammer.html
[covey+07]: http://adsabs.harvard.edu/abs/2007AJ....134.2398C
[hammerGitHub]: https://github.com/jradavenport/TheHammer
[pyhammerwiki]: https://github.com/BU-hammerTeam/PyHammer/wiki
-[apjs]: http://iopscience.iop.org/article/10.3847/1538-4365/aa656d/pdf
+[Kesseli_apjs]: http://iopscience.iop.org/article/10.3847/1538-4365/aa656d/pdf
+[Roulston_arXiv]: https://arxiv.org/abs/2006.01199
+[SB2_GitHub]: https://github.com/broulston/SB2
diff --git a/eyecheck.py b/eyecheck.py
old mode 100644
new mode 100755
index e5753fa..5ddbd68
--- a/eyecheck.py
+++ b/eyecheck.py
@@ -20,7 +20,7 @@ def __init__(self, specObj, options):
if self.specIndex == -1: # If no initial spectrum is chosen
qApp.quit() # Quit the QApplication
return # Return back to the main pyhammer routine
- self.loadUserSpectrum() # Otherwise, oad the appropriate spectrum to be displayed
+ self.loadUserSpectrum() # Otherwise, load the appropriate spectrum to be displayed
self.updatePlot() # Update the plot showing the template and spectrum
self.show() # Show the final GUI window to the user
@@ -37,10 +37,12 @@ class that can be used in various places.
"""
# Define some basic spectra related information
- self.specType = ['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L']
+ self.specType = ['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L', 'C', 'WD']
self.subType = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
+ #self.subTypeWD = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
self.metalType = ['-2.0', '-1.5', '-1.0', '-0.5', '+0.0', '+0.5', '+1.0']
self.templateDir = os.path.join(os.path.split(__file__)[0], 'resources', 'templates')
+ self.SB2templateDir = os.path.join(os.path.split(__file__)[0], 'resources', 'templates_SB2')
# Define an index to point to the current spectra in the list
# of spectra in the output file. We will start by assuming we're
@@ -235,9 +237,21 @@ def createGui(self):
self.grid.addWidget(self.spectrumList, 1, 0, 1, 3)
# The collection of sliders and their accompanying labels
- self.specTypeLabel, self.specTypeSlider = self.createSlider('Stellar\nType', self.specType, self.specTypeChanged)
- self.subTypeLabel, self.subTypeSlider = self.createSlider('Stellar\nSubtype', self.subType, self.specSubtypeChanged)
- self.metalLabel, self.metalSlider = self.createSlider('Metallicity\n[Fe/H]', self.metalType, self.metallicityChanged)
+ self.specTypeLabel, self.specTypeSlider = self.createSlider('Primary\nStellar\nType', self.specType, self.specTypeChanged)
+ self.subTypeLabel, self.subTypeSlider = self.createSlider('Primary\nStellar\nSubtype', self.subType, self.specSubtypeChanged)
+ self.metalLabel, self.metalSlider = self.createSlider('Primary\nMetallicity\n[Fe/H]', self.metalType, self.metallicityChanged)
+
+ # The collection of SB2 sliders and their accompanying labels
+ self.specTypeLabelSB2, self.specTypeSliderSB2 = self.createSB2Slider('Secondary\nStellar\nType', self.specType, self.specTypeChanged, 0)
+ self.subTypeLabelSB2, self.subTypeSliderSB2 = self.createSB2Slider('Secondary\nStellar\nSubtype', self.subType, self.specSubtypeChanged, 1)
+
+ self.SB2button = QCheckBox("SB2")
+ self.SB2button.setChecked(False)
+ for ii in range(len(self.specTypeLabelSB2)):
+ self.specTypeLabelSB2[ii].setDisabled(True)
+ self.subTypeLabelSB2[ii].setDisabled(True)
+ self.SB2button.stateChanged.connect(lambda:self.btnstate(self.SB2button))
+ self.grid.addWidget(self.SB2button, 3, 2)
# The collection of buttons
self.createButtons('Change Spectral Type', ['Earlier', 'Later'],
@@ -291,6 +305,50 @@ def createGui(self):
self.setWindowTitle('PyHammer Eyecheck')
self.setWindowIcon(self.icon)
+
+ def btnstate(self,b):
+ bad_primary = [0, 1, 7, 9]
+ bad_secondary = [0, 1, 2, 7]
+ if b.text() == "SB2":
+ if b.isChecked() == True:
+ for ii in range(len(self.metalLabel)):
+ self.metalLabel[ii].setDisabled(True)
+
+ for ii in range(len(self.specTypeLabelSB2)):
+ self.specTypeLabelSB2[ii].setDisabled(False)
+ self.subTypeLabelSB2[ii].setDisabled(False)
+
+ for ii in bad_primary:
+ self.specTypeLabel[ii].setDisabled(True)
+
+ for ii in bad_secondary:
+ self.specTypeLabelSB2[ii].setDisabled(True)
+
+ if self.specTypeSlider.sliderPosition() in [0,1]:
+ self.updateSlider(self.specTypeSlider, 2)
+ elif self.specTypeSlider.sliderPosition() in [7, 9]:
+ self.updateSlider(self.specTypeSlider, 8)
+
+ if self.specTypeSliderSB2.sliderPosition() in [0,1,2]:
+ self.updateSlider(self.specTypeSliderSB2, 3)
+ elif self.specTypeSliderSB2.sliderPosition() in [7]:
+ self.updateSlider(self.specTypeSliderSB2, 6)
+
+ self.checkSB2combos()
+ else:
+ for ii in range(len(self.metalLabel)):
+ self.metalLabel[ii].setDisabled(False)
+
+ for ii in range(len(self.specTypeLabelSB2)):
+ self.specTypeLabelSB2[ii].setDisabled(True)
+ self.subTypeLabelSB2[ii].setDisabled(True)
+
+ for ii in range(len(self.specTypeLabel)):
+ self.specTypeLabel[ii].setDisabled(False)
+ self.subTypeLabel[ii].setDisabled(False)
+ self.updatePlot()
+
+
def createMenuBar(self):
"""
Description:
@@ -429,6 +487,66 @@ class which were written on top of the respective QWidgets and
# Return the labels and slider so we can access them later
return tickLabels, slider
+ def createSB2Slider(self, title, labels, callback, column):
+ """
+ Description:
+ A helper method of the create GUI method. This method creates
+ a frame and puts a label at the top, a vertical slider, and
+ a collection of labels next to the slider. Note that both the
+ slider and labels use customized objects from the gui_utils
+ class which were written on top of the respective QWidgets and
+ provide additional functionality. See those respective classes
+ for details.
+
+ Input:
+ title: The label to put at the top of the frame as the title
+ for the section.
+ labels: The labels to use for the slider. The slider itself
+ will be given a set of discrete options to match the number
+ of labels provided.
+ callback: The callback to use when the slider is set to a new
+ value.
+
+ Return:
+ Returns the slider and collection of label objects. These are
+ returned so the GUI can interact with the labels and slider
+ later on.
+ """
+
+ # Define or update the column of the top-level grid to
+ # put this slider component into
+ # if not hasattr(self, 'column'):
+ # self.column = 0
+ # else:
+ # self.column += 1
+
+ # Create the frame and put it in the top layer grid
+ frame = QFrame(frameShape = QFrame.StyledPanel, frameShadow = QFrame.Sunken, lineWidth = 0)
+ sliderGrid = QGridLayout()
+ frame.setLayout(sliderGrid)
+ self.grid.addWidget(frame, 3, column)
+
+ # Create the label at the top of the frame
+ label = QLabel(title, alignment = Qt.AlignCenter)
+ sliderGrid.addWidget(label, 0, 0, 1, 2)
+
+ # Add the slider
+ slider = Slider(Qt.Vertical, minimum = 0, maximum = len(labels)-1, tickInterval = 1, pageStep = 1, invertedAppearance = True)
+ slider.valueChanged.connect(callback)
+ slider.setTickPosition(QSlider.TicksLeft)
+ slider.setTracking(False)
+ sliderGrid.addWidget(slider, 1, 1, len(labels), 1)
+
+ # Add the text labels to the left of the slider
+ tickLabels = []
+ for i,text in enumerate(labels):
+ label = SliderLabel(text, slider, i)
+ tickLabels.append(label)
+ sliderGrid.addWidget(label, i+1, 0)
+
+ # Return the labels and slider so we can access them later
+ return tickLabels, slider
+
def createButtons(self, title, buttonTexts, tooltips, callbacks):
"""
Description:
@@ -449,7 +567,7 @@ def createButtons(self, title, buttonTexts, tooltips, callbacks):
# Define or update the row of the top-level grid to
# put this button frame into
if not hasattr(self, 'row'):
- self.row = 3
+ self.row = 4
else:
self.row += 1
@@ -478,15 +596,18 @@ def createDistanceMetric(self):
# Create the frame
frame = QFrame(frameShape = QFrame.StyledPanel, frameShadow = QFrame.Sunken, lineWidth = 0)
- frame.setToolTip('This metric indicates how well the current\n'
+ frame.setToolTip('These metrics indicate how well the current\n'
'template matches the spectrum. The lower\n'
- 'the value, the better the match.')
+ 'the value, the better the match.\n\n'
+ 'The first metric is the residual between the input spectrum and the selected template.\n'
+ 'The second metric is the residual between the input spectrum and the selected template, normalized by error.\n'
+ 'The third metric is the distance between the measured line of the input spectrum and the selected template.\n')
distGrid = QVBoxLayout(margin = 2, spacing = 2)
frame.setLayout(distGrid)
self.grid.addWidget(frame, self.row + 1, 0, 1, 3)
# Create the distance metric label
- label = QLabel('Template Match Metric', alignment = Qt.AlignCenter)
+ label = QLabel('Metrics: Resid | NormResid | LineDist', alignment = Qt.AlignCenter)
distGrid.addWidget(label)
# Create the distance metric value label
@@ -563,6 +684,7 @@ def updatePlot(self):
# Determine and format the template name for the title, from the filename
templateName = os.path.basename(os.path.splitext(templateFile)[0])
if '_' in templateName:
+ templateName = templateName.replace('Dwarf','') #hide Dwarf from plot title
ii = templateName.find('_')+1 # Index of first underscore, before metallicity
templateName = templateName[:ii] + '[Fe/H] = ' + templateName[ii:]
templateName = templateName.replace('_',',\;')
@@ -618,11 +740,11 @@ def updatePlot(self):
# *** Set Plot Spacing ***
- plt.subplots_adjust(left = 0.075, right = 0.975, top = 0.9, bottom = 0.15)
+ plt.subplots_adjust(left = 0.143, right = 0.927, top = 0.816, bottom = 0.194)
# *** Set Plot Limits ***
- ax.set_xlim([3000,11000]) # Set x axis limits to constant value
+ ax.set_xlim([3500, 10500]) # Set x axis limits to constant value
self.toolbar.update() # Clears out view stack
self.full_xlim = plt.gca().get_xlim() # Pull out default, current x-axis limit
self.full_ylim = plt.gca().get_ylim() # Pull out default, current y-axis limit
@@ -637,10 +759,19 @@ def updatePlot(self):
if templateFile is not None:
m = min(len(self.specObj.wavelength), len(hdulist[1].data['loglam']))
dist = round(np.sqrt(np.nansum([(t-s)**2 for t,s in zip(hdulist[1].data['flux'][:m],self.specObj.flux[:m])])),2)
- dist = '{:.2f}'.format(dist)
+ distSTR = '{:.3f}'.format(dist)
+ distNorm = round(np.sqrt(np.nansum([((t-s)/err)**2 for t,s,err in zip(hdulist[1].data['flux'][:m],self.specObj.flux[:m],self.specObj.var[:m]**0.5)])),2)
+ distNormSTR = '{:.3f}'.format(distNorm)
+ self.specDist = dist
+ self.specDistNorm = distNorm
+ self.lineCHiSq = self.specObj.distance
+ self.distMetric.setText(distSTR+" | "+distNormSTR+" | "+'{:.5e}'.format(self.specObj.distance))
else:
- dist = 'None'
- self.distMetric.setText(dist)
+ distSTR = 'None'
+ distNormSTR = 'None'
+ self.distMetric.setText(distSTR+" | "+distNormSTR+" | "+"None")
+
+
# *** Draw the Plot ***
@@ -815,8 +946,11 @@ def nextCallback(self):
choice and moves forward to the next user's spectrum.
"""
# Store the choice for the current spectra
- self.outData[self.specIndex,5] = self.specType[self.specTypeSlider.sliderPosition()] + str(self.subTypeSlider.sliderPosition())
- self.outData[self.specIndex,6] = self.metalType[self.metalSlider.sliderPosition()]
+ if self.SB2button.isChecked() == True:
+ self.outData[self.specIndex,5] = self.specType[self.specTypeSlider.sliderPosition()] + str(self.subTypeSlider.sliderPosition()) + "+" + self.specType[self.specTypeSliderSB2.sliderPosition()] + str(self.subTypeSliderSB2.sliderPosition())
+ else:
+ self.outData[self.specIndex,5] = self.specType[self.specTypeSlider.sliderPosition()] + str(self.subTypeSlider.sliderPosition())
+ self.outData[self.specIndex,6] = self.metalType[self.metalSlider.sliderPosition()]
# Move to the next spectra
self.moveToNextSpectrum()
@@ -853,14 +987,151 @@ def checkSliderStates(self):
# If it is, turn off the option to pick K8 and K9
# since those don't exist. Otherwise, just turn those
# sub spectral type labels on.
- if self.specTypeSlider.sliderPosition() == 5:
- self.subTypeLabel[-1].setDisabled(True)
- self.subTypeLabel[-2].setDisabled(True)
- if self.subTypeSlider.sliderPosition() in [8,9]:
- self.updateSlider(self.subTypeSlider, 7)
+ # self.specTypeSlider.blockSignals(True)
+ # self.specTypeSliderSB2.blockSignals(True)
+
+ self.subTypeSlider.blockSignals(True)
+ self.subTypeSliderSB2.blockSignals(True)
+
+ bad_primary = [0, 1, 7, 9]
+ bad_secondary = [0, 1, 2, 7]
+
+ if self.SB2button.isChecked() == True:
+ # for ii in range(len(self.specTypeLabelSB2)):
+ # self.specTypeLabelSB2[ii].setDisabled(False)
+ # self.subTypeLabelSB2[ii].setDisabled(False)
+
+ # for ii in bad_primary:
+ # self.specTypeLabel[ii].setDisabled(True)
+
+ # for ii in bad_secondary:
+ # self.specTypeLabelSB2[ii].setDisabled(True)
+
+
+ if self.specTypeSlider.sliderPosition() in [0,1]:
+ self.updateSlider(self.specTypeSlider, 2)
+ elif self.specTypeSlider.sliderPosition() in [7 ,9]:
+ self.updateSlider(self.specTypeSlider, 8)
+
+ if self.specTypeSliderSB2.sliderPosition() in [0,1,2]:
+ self.updateSlider(self.specTypeSliderSB2, 3)
+ elif self.specTypeSliderSB2.sliderPosition() in [7]:
+ self.updateSlider(self.specTypeSliderSB2, 6)
+
+ self.checkSB2combos()
else:
- self.subTypeLabel[-1].setEnabled(True)
- self.subTypeLabel[-2].setEnabled(True)
+ # for ii in range(len(self.specTypeLabelSB2)):
+ # self.specTypeLabelSB2[ii].setDisabled(True)
+ # self.subTypeLabelSB2[ii].setDisabled(True)
+
+ # for ii in range(len(self.specTypeLabel)):
+ # self.specTypeLabel[ii].setDisabled(False)
+ # self.subTypeLabel[ii].setDisabled(False)
+
+
+ if self.specTypeSlider.sliderPosition() == 5:
+ self.subTypeLabel[-1].setDisabled(True)
+ self.subTypeLabel[-2].setDisabled(True)
+ if self.subTypeSlider.sliderPosition() in [8,9]:
+ self.updateSlider(self.subTypeSlider, 7)
+ else:
+ self.subTypeLabel[-1].setEnabled(True)
+ self.subTypeLabel[-2].setEnabled(True)
+
+ self.specTypeSlider.blockSignals(False)
+ self.specTypeSliderSB2.blockSignals(False)
+
+ self.subTypeSlider.blockSignals(False)
+ self.subTypeSliderSB2.blockSignals(False)
+
+ def limitSliders(self, good_PrimarySubtypes, good_Secondary_Types):
+ this_good_PrimarySubtypes = good_PrimarySubtypes[self.specTypeSlider.sliderPosition()]
+ this_good_Secondary_Types = good_Secondary_Types[self.specTypeSlider.sliderPosition()]
+
+ notfound_good_SecondarySubtypes = True
+ primaryType_index = self.specTypeSlider.sliderPosition()
+ primarySubType_index = self.subTypeSlider.sliderPosition()
+ secondaryType_index = self.specTypeSliderSB2.sliderPosition()
+ #counter = 0
+ while notfound_good_SecondarySubtypes:
+ #print(counter)
+ good_SecondarySubtypes = self.specObj._splitSB2spectypes[np.where((self.specObj._splitSB2spectypes[:,0] == self.specObj.letterSpt[primaryType_index]) &
+ (self.specObj._splitSB2spectypes[:,2] == self.specObj.letterSpt[secondaryType_index]) &
+ (self.specObj._splitSB2spectypes[:,1] == str(primarySubType_index)))[0], 3].astype(int).tolist()
+ if not good_SecondarySubtypes:
+ #print(good_SecondarySubtypes)
+ #rint("not if")
+ #primaryType_index =
+ #primarySubType_index =
+ secondaryType_index = secondaryType_index - 1
+ if secondaryType_index > -9:
+ self.updateSlider(self.subTypeSlider, np.arange(10)[primarySubType_index])
+ self.updateSlider(self.specTypeSliderSB2, np.arange(10)[secondaryType_index])
+ else:
+ secondaryType_index = self.specTypeSliderSB2.sliderPosition()
+ primarySubType_index = primarySubType_index - 1
+ if good_SecondarySubtypes:
+ #print(good_SecondarySubtypes)
+ #print("if")
+ notfound_good_SecondarySubtypes = False
+ #counter += 1
+
+ #print(good_SecondarySubtypes)
+ #print(self.specTypeSlider.sliderPosition())
+ #print(self.subTypeSlider.sliderPosition())
+ #print(self.specTypeSliderSB2.sliderPosition())
+
+ for ii in range(len(self.subTypeLabel)):
+ self.subTypeLabel[ii].setDisabled(True)
+ for ii in range(len(self.specTypeLabelSB2)):
+ self.specTypeLabelSB2[ii].setDisabled(True)
+ for ii in range(len(self.subTypeLabelSB2)):
+ self.subTypeLabelSB2[ii].setDisabled(True)
+
+ for ii in this_good_PrimarySubtypes:
+ self.subTypeLabel[ii].setDisabled(False)
+ for ii in this_good_Secondary_Types:
+ self.specTypeLabelSB2[ii].setDisabled(False)
+
+ if self.subTypeSlider.sliderPosition() not in this_good_PrimarySubtypes:
+ self.updateSlider(self.subTypeSlider, this_good_PrimarySubtypes[np.argmin(np.abs(np.array(this_good_PrimarySubtypes) - self.subTypeSlider.sliderPosition()))])
+ if self.specTypeSliderSB2.sliderPosition() not in this_good_Secondary_Types:
+ self.updateSlider(self.specTypeSliderSB2, this_good_Secondary_Types[np.argmin(np.abs(np.array(this_good_Secondary_Types) - self.specTypeSliderSB2.sliderPosition()))])
+
+ for ii in good_SecondarySubtypes:
+ self.subTypeLabelSB2[ii].setDisabled(False)
+ if self.subTypeSliderSB2.sliderPosition() not in good_SecondarySubtypes:
+ self.updateSlider(self.subTypeSliderSB2, good_SecondarySubtypes[np.argmin(np.abs(np.array(good_SecondarySubtypes) - self.subTypeSliderSB2.sliderPosition()))])
+
+
+
+
+ def checkSB2combos(self):
+ good_PrimarySubtypes = [[], #O
+ [], #B
+ [2, 3, 5, 7], #A
+ [0, 2, 5, 6, 8], #F
+ [0, 1, 3, 4, 5, 6, 7, 8, 9], #G
+ [0, 1, 2, 3, 4, 5, 7], #K
+ [0, 1, 2, 3, 4, 5, 6, 7], #M
+ [], #L
+ [0, 1, 2], #C
+ [] #WD
+ ]
+
+ good_Secondary_Types = [[], #O
+ [], #B
+ [3], #A
+ [4, 5], #F
+ [5, 8, 9], #G
+ [6, 8, 9], #K
+ [8, 9], #M
+ [], #L
+ [9], #C
+ [] #WD
+ ]
+
+ self.limitSliders(good_PrimarySubtypes, good_Secondary_Types)
def moveToNextSpectrum(self):
"""
@@ -894,6 +1165,11 @@ def moveToPreviousSpectrum(self):
self.loadUserSpectrum()
self.updatePlot()
+ def splitSpecType(self, s):
+ head = s.rstrip('0123456789')
+ tail = s[len(head):]
+ return head, tail
+
def loadUserSpectrum(self):
"""
Description:
@@ -906,15 +1182,40 @@ def loadUserSpectrum(self):
ftype = self.outData[self.specIndex,1]
self.specObj.readFile(self.options['spectraPath']+fname, ftype) # Ignore returned values
self.specObj.normalizeFlux()
+ self.specObj.guessSpecType()
# Set the spectrum entry field to the new spectrum name
self.spectrumList.setCurrentIndex(self.specIndex)
+
# Set the sliders to the new spectrum's auto-classified choices
- self.updateSlider( self.specTypeSlider, self.specType.index(self.outData[self.specIndex,3][0]) )
- self.updateSlider( self.subTypeSlider, self.subType.index(self.outData[self.specIndex,3][1]) )
- self.updateSlider( self.metalSlider, self.metalType.index(self.outData[self.specIndex,4]) )
-
+ auto_classified_choice = self.outData[self.specIndex,3]
+
+ if '+' in auto_classified_choice: # IF SB2
+ user_type1, user_type2 = auto_classified_choice.replace("+"," ").replace("."," ").split()[:2]
+ user_mainType1, user_subtype1 = self.splitSpecType(user_type1)
+ user_mainType2, user_subtype2 = self.splitSpecType(user_type2)
+
+ self.updateSlider(self.specTypeSlider, self.specType.index(user_mainType1))
+ self.updateSlider(self.subTypeSlider, self.subType.index(user_subtype1))
+
+ self.updateSlider(self.specTypeSliderSB2, self.specType.index(user_mainType2))
+ self.updateSlider(self.subTypeSliderSB2, self.subType.index(user_subtype2))
+
+ self.SB2button.setChecked(True)
+ self.checkSliderStates()
+
+ else:# IF Single star
+ specTypePostSplit, specSubTypePostSplit = self.splitSpecType(auto_classified_choice)
+
+ self.updateSlider(self.specTypeSlider, self.specType.index(specTypePostSplit))
+ self.updateSlider(self.subTypeSlider, self.subType.index(specSubTypePostSplit))
+ self.updateSlider(self.metalSlider, self.metalType.index(self.outData[self.specIndex,4]))
+
+ self.SB2button.setChecked(False)
+
+
+
# Reset the indicator for whether the plot is zoomed. It should only stay zoomed
# between loading templates, not between switching spectra.
self.full_xlim = None
@@ -925,7 +1226,7 @@ def loadUserSpectrum(self):
if not self.lockSmoothState.isChecked():
self.smoothSpectrum.setChecked(False)
- def getTemplateFile(self, specState = None, subState = None, metalState = None):
+ def getTemplateFile(self, specState1 = None, subState1 = None, metalState = None, specState2 = None, subState2 = None):
"""
Description:
This will determine the filename for the template which matches the
@@ -939,33 +1240,46 @@ def getTemplateFile(self, specState = None, subState = None, metalState = None):
"""
# If values weren't passed in for certain states, assume we should
# use what is chosen on the GUI sliders
- if specState is None: specState = self.specTypeSlider.sliderPosition()
- if subState is None: subState = self.subTypeSlider.sliderPosition()
- if metalState is None: metalState = self.metalSlider.sliderPosition()
+ if self.SB2button.isChecked() == True:
+ if specState1 is None: specState1 = self.specTypeSlider.sliderPosition()
+ if subState1 is None: subState1 = self.subTypeSlider.sliderPosition()
- # Try using the full name, i.e., SS_+M.M_Dwarf.fits
- filename = self.specType[specState] + str(subState) + '_' + self.metalType[metalState] + '_Dwarf'
+ if specState2 is None: specState2 = self.specTypeSliderSB2.sliderPosition()
+ if subState2 is None: subState2 = self.subTypeSliderSB2.sliderPosition()
- fullPath = os.path.join(self.templateDir, filename + '.fits')
+ filename = self.specType[specState1] + str(subState1) + "+" + self.specType[specState2] + str(subState2)
- if os.path.isfile(fullPath):
+ fullPath = os.path.join(self.SB2templateDir, filename + '.fits')
return fullPath
+ else:
+ if specState1 is None: specState1 = self.specTypeSlider.sliderPosition()
+ if subState1 is None: subState1 = self.subTypeSlider.sliderPosition()
+ if metalState is None: metalState = self.metalSlider.sliderPosition()
- # Try using only the spectra and metallicity in the name, i.e., SS_+M.M.fits
- filename = filename[:7]
+ # Try using the full name, i.e., SS_+M.M_Dwarf.fits
+ filename = self.specType[specState1] + str(subState1) + '_' + self.metalType[metalState] + '_Dwarf'
- fullPath = os.path.join(self.templateDir, filename + '.fits')
+ fullPath = os.path.join(self.templateDir, filename + '.fits')
- if os.path.isfile(fullPath):
- return fullPath
-
- # Try to use just the spectral type, i.e., SS.fits
- filename = filename[:2]
+ if os.path.isfile(fullPath):
+ return fullPath
- fullPath = os.path.join(self.templateDir, filename + '.fits')
-
- if os.path.isfile(fullPath):
- return fullPath
+ # Try using only the spectra and metallicity in the name, i.e., SS_+M.M.fits
+ filename = filename[:7]
+
+ fullPath = os.path.join(self.templateDir, filename + '.fits')
+
+ if os.path.isfile(fullPath):
+ return fullPath
+
+ # Try to use just the spectral type, i.e., SS.fits
+ #filename = filename[:2]
+ filename = self.specType[specState1] + str(subState1)
+
+ fullPath = os.path.join(self.templateDir, filename + '.fits')
+
+ if os.path.isfile(fullPath):
+ return fullPath
# Return None if file could not be found
return None
diff --git a/gui_utils.py b/gui_utils.py
old mode 100644
new mode 100755
diff --git a/measure_allTemps_Indices.py b/measure_allTemps_Indices.py
new file mode 100644
index 0000000..ae912ac
--- /dev/null
+++ b/measure_allTemps_Indices.py
@@ -0,0 +1,88 @@
+# %load_ext autoreload
+# %autoreload 2
+
+import numpy as np
+
+from pyhamimports import *
+from spectrum import Spectrum
+import glob
+from tqdm import tqdm
+from subprocess import check_output
+
+datestr = check_output(["/bin/date","+%F"])
+datestr = datestr.decode().replace('\n', '')
+
+singleTemp_dir = "resources/templates/"
+SB2Temp_dir = "resources/templates_SB2/"
+
+singleTemp_list = np.array([os.path.basename(x)
+ for x in glob.glob(singleTemp_dir + "*.fits")])
+singleTemp_list.sort()
+
+SB2Temp_list = np.array([os.path.basename(x)
+ for x in glob.glob(SB2Temp_dir + "*.fits")])
+SB2Temp_list.sort()
+
+# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 = O, B, A, F, G, K, M, L, C, WD
+single_letter_specTypes = np.array(['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L', 'C', 'W'])
+specTypes = np.array(['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L', 'C', 'WD'])
+
+new_tempLines_0 = np.empty(singleTemp_list.size, dtype=int)
+new_tempLines_1 = np.empty(singleTemp_list.size, dtype=int)
+new_tempLines_2 = np.empty(singleTemp_list.size, dtype=np.float64)
+new_tempLines_3 = np.ones(singleTemp_list.size, dtype=int) * 5
+new_tempLines_4 = []
+
+for ii in range(singleTemp_list.size):
+ new_tempLines_0[ii] = np.where(
+ single_letter_specTypes == singleTemp_list[ii][0])[0][0]
+ if new_tempLines_0[ii] == 9:
+ new_tempLines_1[ii] = singleTemp_list[ii][2]
+ else:
+ new_tempLines_1[ii] = singleTemp_list[ii][1]
+ if len(singleTemp_list[ii].replace("_", " ").split()) == 1:
+ new_tempLines_2[ii] = 0.
+ else:
+ new_tempLines_2[ii] = np.float64(
+ singleTemp_list[ii].replace("_", " ").split()[1])
+
+spec = Spectrum()
+ftype = None
+print("Measuring lines for single star templates:")
+for ii in tqdm(range(singleTemp_list.size)):
+ message, ftype = spec.readFile(singleTemp_dir + singleTemp_list[ii], ftype)
+ spec._lines = spec.measureLines()
+ lines = np.array(list(spec._lines.values()))[
+ np.argsort(list(spec._lines.keys()))]
+ new_tempLines_4.append(lines)
+
+SB2_index_start = new_tempLines_0.max() + 1 # 10
+new_tempLines_0 = np.append(new_tempLines_0, np.arange(
+ SB2_index_start, SB2_index_start + SB2Temp_list.size, step=1))
+new_tempLines_1 = np.append(new_tempLines_1, np.zeros(SB2Temp_list.size))
+new_tempLines_2 = np.append(new_tempLines_2, np.zeros(SB2Temp_list.size))
+new_tempLines_3 = np.append(new_tempLines_3, np.ones(SB2Temp_list.size) * 5)
+# new_tempLines_4 = new_tempLines_4
+
+spec = Spectrum()
+ftype = None
+print("Measuring lines for SB2 templates:")
+for ii, filename in enumerate(tqdm(SB2Temp_list)):
+ # temp_list = []
+ message, ftype = spec.readFile(SB2Temp_dir + filename, ftype)
+ measuredLines = spec.measureLines()
+ spec._lines = measuredLines
+ lines = np.array(list(spec._lines.values()))[
+ np.argsort(list(spec._lines.keys()))]
+ linesLabels = np.array(list(spec._lines.keys()))[
+ np.argsort(list(spec._lines.keys()))]
+ # temp_list.append(lines)
+ new_tempLines_4.append(lines)
+
+new_tempLines = [new_tempLines_0, new_tempLines_1,
+ new_tempLines_2, new_tempLines_3, new_tempLines_4]
+
+pklPath = os.path.join(spec.thisDir, 'resources',
+ f'tempLines_{datestr}.pickle')
+with open(pklPath, 'wb') as pklFile:
+ pickle.dump(new_tempLines, pklFile)
diff --git a/pyhamimports.py b/pyhamimports.py
old mode 100644
new mode 100755
index b25b1d7..de5cc33
--- a/pyhamimports.py
+++ b/pyhamimports.py
@@ -8,7 +8,7 @@
# Look at version specific information
import sys
-ver = sys.version_info # Get Python version
+ver = sys.version_info # Get Python version
if ver.major != 3:
sys.exit('Python 3 is required to run PyHammer.')
# We need a function to determine which modules
@@ -21,6 +21,7 @@
# Some basic python libraries
import os
+import re
import getopt
import numpy as np
from numpy import ma
@@ -35,6 +36,9 @@
import pickle
import csv
from collections import OrderedDict
+from scipy.optimize import curve_fit
+from tqdm import tqdm
+import warnings
# Check which PyQt version the user may have
# installed and import the appropriate content
diff --git a/pyhammer.py b/pyhammer.py
old mode 100644
new mode 100755
index 1dc77cf..69318af
--- a/pyhammer.py
+++ b/pyhammer.py
@@ -19,7 +19,7 @@ def main(options):
get all the necessary options from the user and pass
them to this function. The general process of this
function should be to:
-
+
- Define a Spectrum object to be used in reading files.
- Load each spectrum sequentially.
- Guess the spectral type.
@@ -45,7 +45,6 @@ def main(options):
# If the user has decided to not skip to the eyecheck, let's
# do some processing
if not options['eyecheck']:
-
# Open the input file
try:
infile = open(options['infile'], 'r')
@@ -59,130 +58,151 @@ def main(options):
if options['lineOutfile']: lineOutfile = open('spectralIndices.csv', 'w')
outfile.write('#Filename,File Type,Radial Velocity (km/s),Guessed Spectral Type,Guessed [Fe/H],User Spectral Type,User [Fe/H]\n')
rejectfile.write('#Filename,File Type,Spectra S/N\n')
- if options['lineOutfile']: lineOutfile.write('#Filename,CaK,CaK_var,Cadel,Cadel_var,CaI4217,CaI4217_var,Gband,Gband_var,Hgam,Hgam_var,FeI4383,FeI4383_var,FeI4404,FeI4404_var,Hbeta,Hbeta_var,MgI,MgI_var,NaD,NaD_var,CaI6162,CaI6162_var,Halpha,Halpha_var,CaH2,CaH2_var,CaH3,CaH3_var,TiO5,TiO5_var,VO7434,VO7434_var,VO7445,VO7445_var,VO-B,VO-B_var,VO7912,VO7912_var,Rb-B,Rb-B_var,NaI,NaI_var,TiO8,TiO8_var,TiO8440,TiO8440_var,Cs-A,Cs-A_var,CaII8498,CaII8498_var,CrH-A,CrH-A_var,CaII8662,CaII8662_var,FeI8689,FeI8689_var,FeH,FeH_var\n')
+ if options['lineOutfile']: lineOutfile.write('#Filename,CaK,CaK_var,Cadel,Cadel_var,CaI4217,CaI4217_var,Gband,Gband_var,Hgam,Hgam_var,FeI4383,FeI4383_var,FeI4404,FeI4404_var,Hbeta,Hbeta_var,MgI,MgI_var,NaD,NaD_var,CaI6162,CaI6162_var,Halpha,Halpha_var,CaH2,CaH2_var,CaH3,CaH3_var,TiO5,TiO5_var,VO7434,VO7434_var,VO7445,VO7445_var,VO-B,VO-B_var,VO7912,VO7912_var,Rb-B,Rb-B_var,NaI,NaI_var,TiO8,TiO8_var,TiO8440,TiO8440_var,Cs-A,Cs-A_var,CaII8498,CaII8498_var,CrH-A,CrH-A_var,CaII8662,CaII8662_var,FeI8689,FeI8689_var,FeH,FeH_var,C2-4382,C2-4382_var,C2-4737,C2-4737_var,C2-5165,C2-5165_var,C2-5636,C2-5636_var,CN-6959,CN-6959_var,CN-7088,CN-7088_var,CN-7259,CN-7259_var,CN-7820,CN-7820_var,CN-8067,CN-8067_var,CN-8270,CN-8270_var,WD-Halpha,WD-Halpha_var,WD-Hbeta,WD-Hbeta_var,WD-Hgamma,WD-Hgamma_var\n')
# Define the string to contain all failure messages. These will be compiled
# and printed once at the end, if anything is put into it.
rejectMessage = ''
- for i, line in enumerate(infile):
- # Remove extra whitespace and other unwanted characters and split
- line = line.strip()
- if ',' in line: line = line.replace(',',' ')
- if ' ' in line:
- fname, ftype = ' '.join(line.split()).rsplit(' ',1)
- else:
- fname = line
- ftype = None
-
- # Print statement of progress for user
- print(i+1, ') Processing ', os.path.basename(fname), sep = '')
-
- # Now read in the current file (this process reads in the file, converts air to
- # vac when necessary and interpolates onto the template grid)
- message, ftype = spec.readFile(options['spectraPath']+fname, ftype)
-
- # If the attempt at reading the file did not succeed, then we
- # should just continue
- if message is not None:
- rejectfile.write(fname + ',' + ('N/A' if ftype is None else ftype) + ',N/A\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: ' + message.replace('\n','') + '\n\n'
- continue
+ infile_init = open(options['infile'], 'r')
+ for i, line in enumerate(infile_init):
+ num_spec = i + 1
+ infile_init.close()
+ #progress = ProgressBar(num_spec, fmt=ProgressBar.FULL)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", category=RuntimeWarning)
+ for i, line in enumerate(tqdm(infile, total=num_spec)):
+ # Remove extra whitespace and other unwanted characters and split
+ line = line.strip()
+ if ',' in line: line = line.replace(',',' ')
+ if ' ' in line:
+ fname, ftype = ' '.join(line.split()).rsplit(' ',1)
+ else:
+ fname = line
+ ftype = None
+
+ # Print statement of progress for user
+ #print(i+1, ') Processing ', os.path.basename(fname), sep = '')
+ # progress.current += 1
+ # progress()
+ # Now read in the current file (this process reads in the file, converts air to
+ # vac when necessary and interpolates onto the template grid)
+ message, ftype = spec.readFile(options['spectraPath']+fname, ftype)
+
+ # If the attempt at reading the file did not succeed, then we
+ # should just continue
+ if message is not None:
+ rejectfile.write(fname + ',' + ('N/A' if ftype is None else ftype) + ',N/A\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: ' + message.replace('\n','') + '\n\n'
+ continue
- # Now that we have the necessary data in the spec object, let's
- # begin processing.
+ # Now that we have the necessary data in the spec object, let's
+ # begin processing.
- # --- 1 ---
- # Calculate the signal to noise of the spectrum to potentially reject
- snVal = spec.calcSN()
- if options['sncut'] is not None:
- if snVal < options['sncut']:
+ # --- 1 ---
+ # Calculate the signal to noise of the spectrum to potentially reject
+ snVal = spec.calcSN()
+ if options['sncut'] is not None:
+ if snVal < options['sncut']:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: S/N = ' + str(snVal) + ' < ' + str(options['sncut']) + '\n\n'
+ continue
+
+ # --- 2 ---
+ # Normalize the input spectrum to the same place where the templates are normalized (8000A)
+ try:
+ spec.normalizeFlux()
+ except:
rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: S/N = ' + str(snVal) + ' < ' + str(options['sncut']) + '\n\n'
+ rejectMessage += 'FILE: ' + fname + '\nREASON: Could not normalize\n\n'
continue
-
- # --- 2 ---
- # Normalize the input spectrum to the same place where the templates are normalized (8000A)
- try:
- spec.normalizeFlux()
- except:
- rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: Could not normalize\n\n'
- continue
- # --- 3 ---
- # Call guessSpecType function for the initial guess
- # this function measures the lines then makes a guess of all parameters
- try:
- spec.guessSpecType()
- except Exception as e:
- rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: Could not guess spectral type. Error Message: {}\n\n'.format(e)
- continue
-
- # If the user wants the calculated spectral indices, write them to a file
- if options['lineOutfile']:
- for key, value in spec.lines.items():
- if key == 'CaK':
- lineOutfile.write(fname + ',' + str(value[0]) + ',' + str(value[1])+',')
- elif key == 'FeH':
- lineOutfile.write(str(value[0]) + ',' + str(value[1])+'\n')
- elif key in ['region1', 'region2', 'region3', 'region4', 'region5']:
- continue
- else:
- lineOutfile.write(str(value[0]) + ',' + str(value[1])+',')
-
- # --- 4 ---
- # Call findRadialVelocity function using the initial guess
- try:
- shift = spec.findRadialVelocity()
- except Exception as e:
- rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: Could not find radial velocity. Error Message: {}\n\n'.format(e)
- continue
+ # --- 3 ---
+ # Call guessSpecType function for the initial guess
+ # this function measures the lines then makes a guess of all parameters
+ try:
+ spec.guessSpecType()
+ except Exception as e:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: Could not guess spectral type. Error Message: {}\n\n'.format(e)
+ continue
- # --- 5 ---
- # Call shiftToRest that shifts the spectrum to rest wavelengths,
- # then interp back onto the grid
- try:
- spec.shiftToRest(shift)
- spec.interpOntoGrid()
- except Exception as e:
- rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: Could not shift to rest. Error Message: {}\n\n'.format(e)
- continue
+ # --- 4 ---
+ # Call findRadialVelocity function using the initial guess
+ try:
+ shift = spec.findRadialVelocity()
+ except Exception as e:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: Could not find radial velocity. Error Message: {}\n\n'.format(e)
+ continue
- # --- 6 ---
- # Repeat guessSpecType function to get a better guess of the spectral
- # type and metallicity
- try:
- spec.guessSpecType()
- except Exception as e:
- rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
- rejectMessage += 'FILE: ' + fname + '\nREASON: Could not guess spectral type. Error Message: {}\n\n'.format(e)
- continue
+ # --- 5 ---
+ # Call shiftToRest that shifts the spectrum to rest wavelengths,
+ # then interp back onto the grid
+ try:
+ spec.shiftToRest(shift)
+ spec.interpOntoGrid()
+ except Exception as e:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: Could not shift to rest. Error Message: {}\n\n'.format(e)
+ continue
- # End of the automatic guessing. We should have:
- # 1. Spectrum object with observed wavelength, flux, var,
- # 2. rest wavelength,
- # 3. spectral type (guessed),
- # 4. radial velocity and uncertainty,
- # 5. metallicity estimate,
- # 6. and line indice measurements
+ # --- 6 ---
+ # Repeat guessSpecType function to get a better guess of the spectral
+ # type and metallicity
+ try:
+ spec.guessSpecType()
+ except Exception as e:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: Could not guess spectral type. Error Message: {}\n\n'.format(e)
+ continue
- # --- 7 ---
-
- # Translate the numbered spectral types into letters
- letterSpt = ['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L'][spec.guess['specType']]
+ # If the user wants the calculated spectral indices, write them to a file
+ if options['lineOutfile']:
+ for key, value in spec.lines.items():
+ if key == 'CaK':
+ lineOutfile.write(fname + ',' + str(value[0]) + ',' + str(value[1])+',')
+ elif key == 'WD-Hgamma':
+ lineOutfile.write(str(value[0]) + ',' + str(value[1])+'\n')
+ elif key in ['region1', 'region2', 'region3', 'region4', 'region5']:
+ continue
+ else:
+ lineOutfile.write(str(value[0]) + ',' + str(value[1])+',')
+
+ # End of the automatic guessing. We should have:
+ # 1. Spectrum object with observed wavelength, flux, var,
+ # 2. rest wavelength,
+ # 3. spectral type (guessed),
+ # 4. radial velocity and uncertainty,
+ # 5. metallicity estimate,
+ # 6. and line indice measurements
+
+ # --- 7 ---
+ if spec.guess['specType'] == -1:
+ rejectfile.write(fname + ',' + ftype + ',' + str(snVal) + '\n')
+ rejectMessage += 'FILE: ' + fname + '\nREASON: No good spectral type. Error Message: Spectrum might be all nans or 0s.\n\n'
+ continue
+
+ # Translate the numbered spectral types into letters
+ if spec._isSB2:
+ # Write the file
+ outfile.write(options['spectraPath']+fname + ',' + # The spectra path and filename
+ ftype + ',' + # The filetype
+ str(shift) + ',' + # The RV shift
+ spec._SB2_filenameList[spec.guess['specType'] - 10][:-5] + ',' + # The auto-guessed spectral type
+ '{:+2.1f}'.format(spec.guess['metal']) + # The auto-guessed metallicity
+ ',nan,nan\n') # The to-be-determined user classifications
+ else:
+ letterSpt = ['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L', 'C', 'WD'][spec.guess['specType']]
+ # Write the file
+ outfile.write(options['spectraPath']+fname + ',' + # The spectra path and filename
+ ftype + ',' + # The filetype
+ str(shift) + ',' + # The RV shift
+ letterSpt + str(spec.guess['subType']) + ',' + # The auto-guessed spectral type
+ '{:+2.1f}'.format(spec.guess['metal']) + # The auto-guessed metallicity
+ ',nan,nan\n') # The to-be-determined user classifications
- # Write the file
- outfile.write(options['spectraPath']+fname + ',' + # The spectra path and filename
- ftype + ',' + # The filetype
- str(shift) + ',' + # The RV shift
- letterSpt + str(spec.guess['subType']) + ',' + # The auto-guessed spectral type
- '{:+2.1f}'.format(spec.guess['metal']) + # The auto-guessed metallicity
- ',nan,nan\n') # The to-be-determined user classifications
-
+ #progress.done()
# We're done so let's close all the files
infile.close()
outfile.close()
@@ -580,7 +600,7 @@ def configureGui(self):
self.textArea.setFont(font)
self.selectSpectraButton.clicked.connect(self.selectSpectraButtonClicked)
self.selectSpectraButton.setToolTip('Select a set of spectrum files\nto add to the input file')
- self.dataTypeList.addItems(['SDSSdr12', 'SDSSdr7', 'fits', 'txt', 'csv'])
+ self.dataTypeList.addItems(['SDSSdr12', 'SDSSdr7', 'fits', 'txt', 'csv','tempfits'])
self.applyType.clicked.connect(self.applyTypeClicked)
self.applyType.setToolTip('Optional: Append the selected data\ntype to every file in the list to give a\nhint to PyHammer about the file type')
self.createFrame.hide()
@@ -954,6 +974,43 @@ def pyhammerSettingsCmd(options):
# Now that all the options have been set, let's get started
main(options)
+class ProgressBar(object):
+ DEFAULT = 'Progress: %(bar)s %(percent)3d%%'
+ FULL = '%(bar)s %(current)d/%(total)d (%(percent)3d%%) %(remaining)d to go'
+
+ def __init__(self, total, width=40, fmt=DEFAULT, symbol='=',
+ output=sys.stderr):
+ assert len(symbol) == 1
+
+ self.total = total
+ self.width = width
+ self.symbol = symbol
+ self.output = output
+ self.fmt = re.sub(r'(?P%\(.+?\))d',
+ r'\g%dd' % len(str(total)), fmt)
+
+ self.current = 0
+
+ def __call__(self):
+ percent = self.current / float(self.total)
+ size = int(self.width * percent)
+ remaining = self.total - self.current
+ bar = '[' + self.symbol * size + ' ' * (self.width - size) + ']'
+
+ args = {
+ 'total': self.total,
+ 'bar': bar,
+ 'current': self.current,
+ 'percent': percent * 100,
+ 'remaining': remaining
+ }
+ print('\r' + self.fmt % args, file=self.output, end='')
+
+ def done(self):
+ self.current = self.total
+ self()
+ print('', file=self.output)
+
###
# PyHammer Entry Point
#
@@ -1082,7 +1139,7 @@ def pyhammerSettingsCmd(options):
# Append a slash to the end if there isn't one
if (options['spectraPath'][-1] not in ['\\', '/']):
options['spectraPath'] += '\\'
-
+
# User indicated they want to skip to the eyecheck
if (opt == '-e' or opt == '--eyecheck'):
if (options['sncut'] is not None):
@@ -1104,7 +1161,7 @@ def pyhammerSettingsCmd(options):
sys.exit('Cannot supply -c and -g at the same time. Use -h for more info.')
else:
options['useGUI'] = False
-
+
# GUI interface is requested.
if (opt == '-g' or opt == '--gui'):
if (options['useGUI'] is not None):
@@ -1123,10 +1180,10 @@ def pyhammerSettingsCmd(options):
PyHammerSettingsGui(options)
app.exec_()
elif options['useGUI'] == False:
+ app = QApplication([])
pyhammerSettingsCmd(options)
-
+ app.exec_()
else:
-
sys.exit("Do not import pyhammer. Run this script with the run command "
"in the python environment, or by invoking with the python command "
"on the command line. Use the -h flag for more information.")
diff --git a/resources/PyHammer2_GUI.png b/resources/PyHammer2_GUI.png
new file mode 100644
index 0000000..bca0ef7
Binary files /dev/null and b/resources/PyHammer2_GUI.png differ
diff --git a/resources/list_of_SB2_temps.txt b/resources/list_of_SB2_temps.txt
new file mode 100644
index 0000000..6b10ef3
--- /dev/null
+++ b/resources/list_of_SB2_temps.txt
@@ -0,0 +1,227 @@
+A2+F0.fits
+A2+F2.fits
+A3+F0.fits
+A3+F2.fits
+A3+F5.fits
+A5+F0.fits
+A5+F2.fits
+A5+F5.fits
+A5+F6.fits
+A7+F0.fits
+A7+F2.fits
+A7+F5.fits
+A7+F6.fits
+A7+F8.fits
+C0+WD0.fits
+C0+WD1.fits
+C0+WD2.fits
+C1+WD0.fits
+C1+WD1.fits
+C1+WD2.fits
+C1+WD3.fits
+C1+WD4.fits
+C2+WD0.fits
+C2+WD1.fits
+C2+WD2.fits
+C2+WD3.fits
+C2+WD4.fits
+C2+WD5.fits
+C2+WD6.fits
+C2+WD7.fits
+F0+G7.fits
+F2+G1.fits
+F2+G4.fits
+F2+G6.fits
+F2+G7.fits
+F5+G0.fits
+F5+G1.fits
+F5+G3.fits
+F5+G4.fits
+F5+G5.fits
+F5+G6.fits
+F5+G7.fits
+F6+G0.fits
+F6+G1.fits
+F6+G3.fits
+F6+G4.fits
+F6+G5.fits
+F6+G6.fits
+F6+G7.fits
+F6+G8.fits
+F6+G9.fits
+F6+K1.fits
+F8+G0.fits
+F8+G1.fits
+F8+G3.fits
+F8+G4.fits
+F8+G5.fits
+F8+G6.fits
+F8+G7.fits
+F8+G8.fits
+F8+G9.fits
+F8+K1.fits
+G0+C0.fits
+G0+K0.fits
+G0+K1.fits
+G0+K2.fits
+G0+K3.fits
+G0+K4.fits
+G0+K5.fits
+G0+K7.fits
+G1+C0.fits
+G1+K0.fits
+G1+K1.fits
+G1+K2.fits
+G1+K3.fits
+G1+K4.fits
+G1+K5.fits
+G3+C0.fits
+G3+K0.fits
+G3+K1.fits
+G3+K2.fits
+G3+K3.fits
+G3+K4.fits
+G3+K5.fits
+G3+K7.fits
+G4+C0.fits
+G4+K0.fits
+G4+K1.fits
+G4+K2.fits
+G4+K3.fits
+G4+K4.fits
+G4+K5.fits
+G5+C0.fits
+G5+K0.fits
+G5+K1.fits
+G5+K2.fits
+G5+K3.fits
+G5+K4.fits
+G5+K5.fits
+G6+C0.fits
+G6+K0.fits
+G6+K1.fits
+G6+K2.fits
+G6+K3.fits
+G6+K4.fits
+G6+K5.fits
+G7+K0.fits
+G7+K1.fits
+G7+K2.fits
+G7+K3.fits
+G7+K4.fits
+G7+K5.fits
+G8+C0.fits
+G8+K0.fits
+G8+K1.fits
+G8+K2.fits
+G8+K3.fits
+G8+K4.fits
+G8+K5.fits
+G8+K7.fits
+G8+WD0.fits
+G9+C0.fits
+G9+K0.fits
+G9+K1.fits
+G9+K2.fits
+G9+K3.fits
+G9+K4.fits
+G9+K5.fits
+G9+K7.fits
+G9+WD0.fits
+K0+C0.fits
+K0+M0.fits
+K0+WD0.fits
+K0+WD1.fits
+K1+C0.fits
+K2+C0.fits
+K2+C1.fits
+K2+M0.fits
+K2+M1.fits
+K2+WD0.fits
+K2+WD1.fits
+K2+WD2.fits
+K3+C0.fits
+K3+C1.fits
+K3+M0.fits
+K3+M1.fits
+K3+M2.fits
+K3+WD0.fits
+K3+WD1.fits
+K3+WD2.fits
+K4+C0.fits
+K4+C1.fits
+K4+M0.fits
+K4+M1.fits
+K4+M2.fits
+K4+WD0.fits
+K4+WD1.fits
+K4+WD2.fits
+K5+C0.fits
+K5+C1.fits
+K5+M0.fits
+K5+M1.fits
+K5+M2.fits
+K5+WD0.fits
+K5+WD1.fits
+K5+WD2.fits
+K5+WD3.fits
+K7+C1.fits
+K7+C2.fits
+K7+M0.fits
+K7+M1.fits
+K7+M2.fits
+K7+WD0.fits
+K7+WD1.fits
+K7+WD2.fits
+K7+WD3.fits
+M0+C1.fits
+M0+C2.fits
+M0+WD0.fits
+M0+WD1.fits
+M0+WD2.fits
+M0+WD3.fits
+M0+WD4.fits
+M1+C1.fits
+M1+C2.fits
+M1+WD0.fits
+M1+WD1.fits
+M1+WD2.fits
+M1+WD3.fits
+M1+WD4.fits
+M1+WD5.fits
+M2+C2.fits
+M2+WD0.fits
+M2+WD1.fits
+M2+WD2.fits
+M2+WD3.fits
+M2+WD4.fits
+M2+WD5.fits
+M2+WD6.fits
+M3+WD2.fits
+M3+WD3.fits
+M3+WD4.fits
+M3+WD5.fits
+M3+WD6.fits
+M3+WD7.fits
+M3+WD8.fits
+M3+WD9.fits
+M4+WD3.fits
+M4+WD4.fits
+M4+WD5.fits
+M4+WD6.fits
+M4+WD7.fits
+M4+WD8.fits
+M4+WD9.fits
+M5+WD3.fits
+M5+WD4.fits
+M5+WD5.fits
+M5+WD6.fits
+M5+WD7.fits
+M5+WD8.fits
+M5+WD9.fits
+M6+WD4.fits
+M6+WD6.fits
+M6+WD8.fits
+M6+WD9.fits
+M7+WD7.fits
+M7+WD8.fits
diff --git a/resources/list_of_single_temps.txt b/resources/list_of_single_temps.txt
new file mode 100644
index 0000000..9715ba9
--- /dev/null
+++ b/resources/list_of_single_temps.txt
@@ -0,0 +1,241 @@
+A0.fits
+A1.fits
+A2.fits
+A3_+0.0_Dwarf.fits
+A3_+0.5_Dwarf.fits
+A3_-0.5_Dwarf.fits
+A3_-1.0_Dwarf.fits
+A4_-1.0_Dwarf.fits
+A6_+0.0_Dwarf.fits
+A6_+0.5_Dwarf.fits
+A6_-0.5_Dwarf.fits
+A6_-1.0_Dwarf.fits
+A6_-1.5_Dwarf.fits
+A6_-2.0_Dwarf.fits
+A7_+0.0_Dwarf.fits
+A7_+0.5_Dwarf.fits
+A7_-0.5_Dwarf.fits
+A7_-1.0_Dwarf.fits
+A7_-1.5_Dwarf.fits
+A7_-2.0_Dwarf.fits
+A8_+0.5_Dwarf.fits
+A8_-0.5_Dwarf.fits
+A8_-1.0_Dwarf.fits
+A8_-1.5_Dwarf.fits
+A9_+0.0_Dwarf.fits
+A9_+0.5_Dwarf.fits
+A9_-0.5_Dwarf.fits
+A9_-1.0_Dwarf.fits
+A9_-1.5_Dwarf.fits
+A9_-2.0_Dwarf.fits
+B0.fits
+B1.fits
+B2.fits
+B3.fits
+B4.fits
+B5.fits
+B6.fits
+B8.fits
+B9.fits
+C0.fits
+C1.fits
+C2.fits
+F0_+0.0_Dwarf.fits
+F0_+0.5_Dwarf.fits
+F0_-1.0_Dwarf.fits
+F0_-1.5_Dwarf.fits
+F0_-2.0_Dwarf.fits
+F1_+0.0_Dwarf.fits
+F1_+0.5_Dwarf.fits
+F1_-0.5_Dwarf.fits
+F1_-1.0_Dwarf.fits
+F1_-1.5_Dwarf.fits
+F1_-2.0_Dwarf.fits
+F2_+0.0_Dwarf.fits
+F2_+0.5_Dwarf.fits
+F2_-0.5_Dwarf.fits
+F2_-1.0_Dwarf.fits
+F2_-1.5_Dwarf.fits
+F2_-2.0_Dwarf.fits
+F3_+0.0_Dwarf.fits
+F3_-0.5_Dwarf.fits
+F3_-1.0_Dwarf.fits
+F3_-1.5_Dwarf.fits
+F3_-2.0_Dwarf.fits
+F4_+0.0_Dwarf.fits
+F4_-0.5_Dwarf.fits
+F4_-1.0_Dwarf.fits
+F4_-1.5_Dwarf.fits
+F4_-2.0_Dwarf.fits
+F5_+0.0_Dwarf.fits
+F5_-0.5_Dwarf.fits
+F5_-1.0_Dwarf.fits
+F5_-1.5_Dwarf.fits
+F5_-2.0_Dwarf.fits
+F6_+0.0_Dwarf.fits
+F6_-0.5_Dwarf.fits
+F6_-1.0_Dwarf.fits
+F6_-1.5_Dwarf.fits
+F6_-2.0_Dwarf.fits
+F7_+0.0_Dwarf.fits
+F7_-0.5_Dwarf.fits
+F7_-1.0_Dwarf.fits
+F7_-1.5_Dwarf.fits
+F7_-2.0_Dwarf.fits
+F8_+0.0_Dwarf.fits
+F8_-0.5_Dwarf.fits
+F8_-1.0_Dwarf.fits
+F8_-1.5_Dwarf.fits
+F8_-2.0_Dwarf.fits
+F9_+0.0_Dwarf.fits
+F9_-0.5_Dwarf.fits
+F9_-1.0_Dwarf.fits
+F9_-1.5_Dwarf.fits
+F9_-2.0_Dwarf.fits
+G0_+0.0_Dwarf.fits
+G0_-0.5_Dwarf.fits
+G0_-1.0_Dwarf.fits
+G0_-1.5_Dwarf.fits
+G0_-2.0_Dwarf.fits
+G1_+0.0_Dwarf.fits
+G1_-0.5_Dwarf.fits
+G1_-1.0_Dwarf.fits
+G1_-1.5_Dwarf.fits
+G1_-2.0_Dwarf.fits
+G2_+0.0_Dwarf.fits
+G2_-0.5_Dwarf.fits
+G2_-1.0_Dwarf.fits
+G2_-1.5_Dwarf.fits
+G2_-2.0_Dwarf.fits
+G3_+0.0_Dwarf.fits
+G3_-0.5_Dwarf.fits
+G3_-1.0_Dwarf.fits
+G3_-1.5_Dwarf.fits
+G3_-2.0_Dwarf.fits
+G4_+0.0_Dwarf.fits
+G4_-0.5_Dwarf.fits
+G4_-1.0_Dwarf.fits
+G4_-1.5_Dwarf.fits
+G4_-2.0_Dwarf.fits
+G5_+0.0_Dwarf.fits
+G5_+0.5_Dwarf.fits
+G5_-0.5_Dwarf.fits
+G5_-1.0_Dwarf.fits
+G5_-1.5_Dwarf.fits
+G5_-2.0_Dwarf.fits
+G6_+0.0_Dwarf.fits
+G6_+0.5_Dwarf.fits
+G6_-0.5_Dwarf.fits
+G6_-1.0_Dwarf.fits
+G6_-1.5_Dwarf.fits
+G6_-2.0_Dwarf.fits
+G7_+0.0_Dwarf.fits
+G7_+0.5_Dwarf.fits
+G7_-0.5_Dwarf.fits
+G7_-1.0_Dwarf.fits
+G7_-1.5_Dwarf.fits
+G7_-2.0_Dwarf.fits
+G8_+0.0_Dwarf.fits
+G8_+0.5_Dwarf.fits
+G8_-0.5_Dwarf.fits
+G8_-1.0_Dwarf.fits
+G8_-1.5_Dwarf.fits
+G8_-2.0_Dwarf.fits
+G9_+0.0_Dwarf.fits
+G9_+0.5_Dwarf.fits
+G9_-0.5_Dwarf.fits
+G9_-1.0_Dwarf.fits
+G9_-1.5_Dwarf.fits
+G9_-2.0_Dwarf.fits
+K0_+0.0_Dwarf.fits
+K0_-0.5_Dwarf.fits
+K0_-1.0_Dwarf.fits
+K0_-1.5_Dwarf.fits
+K0_-2.0_Dwarf.fits
+K1_+0.0_Dwarf.fits
+K1_+0.5_Dwarf.fits
+K1_-0.5_Dwarf.fits
+K1_-1.0_Dwarf.fits
+K1_-1.5_Dwarf.fits
+K1_-2.0_Dwarf.fits
+K2_+0.0_Dwarf.fits
+K2_+0.5_Dwarf.fits
+K2_-0.5_Dwarf.fits
+K2_-1.0_Dwarf.fits
+K2_-1.5_Dwarf.fits
+K2_-2.0_Dwarf.fits
+K3_+0.0_Dwarf.fits
+K3_+0.5_Dwarf.fits
+K3_-0.5_Dwarf.fits
+K3_-1.0_Dwarf.fits
+K3_-1.5_Dwarf.fits
+K3_-2.0_Dwarf.fits
+K4_+0.0_Dwarf.fits
+K4_+0.5_Dwarf.fits
+K4_-0.5_Dwarf.fits
+K4_-1.0_Dwarf.fits
+K4_-1.5_Dwarf.fits
+K4_-2.0_Dwarf.fits
+K5_+0.0_Dwarf.fits
+K5_+0.5_Dwarf.fits
+K5_+1.0_Dwarf.fits
+K5_-0.5_Dwarf.fits
+K5_-1.0_Dwarf.fits
+K5_-2.0_Dwarf.fits
+K7_+0.0_Dwarf.fits
+K7_+0.5_Dwarf.fits
+K7_+1.0_Dwarf.fits
+K7_-0.5_Dwarf.fits
+K7_-1.0_Dwarf.fits
+L0.fits
+L1.fits
+L2.fits
+L3.fits
+L6.fits
+M0_+0.0_Dwarf.fits
+M0_+0.5_Dwarf.fits
+M0_+1.0_Dwarf.fits
+M0_-0.5_Dwarf.fits
+M1_+0.0_Dwarf.fits
+M1_+0.5_Dwarf.fits
+M1_+1.0_Dwarf.fits
+M1_-0.5_Dwarf.fits
+M1_-1.0_Dwarf.fits
+M2_+0.0_Dwarf.fits
+M2_+0.5_Dwarf.fits
+M2_+1.0_Dwarf.fits
+M2_-0.5_Dwarf.fits
+M2_-1.0_Dwarf.fits
+M3_+0.0_Dwarf.fits
+M3_+0.5_Dwarf.fits
+M3_-0.5_Dwarf.fits
+M3_-1.0_Dwarf.fits
+M4_+0.0_Dwarf.fits
+M4_+0.5_Dwarf.fits
+M5_+0.0_Dwarf.fits
+M5_+0.5_Dwarf.fits
+M5_-0.5_Dwarf.fits
+M6_+0.0_Dwarf.fits
+M6_+0.5_Dwarf.fits
+M6_-0.5_Dwarf.fits
+M7_+0.0_Dwarf.fits
+M7_+0.5_Dwarf.fits
+M7_-0.5_Dwarf.fits
+M8_+0.0_Dwarf.fits
+M8_+0.5_Dwarf.fits
+M8_-0.5_Dwarf.fits
+M9.fits
+O5.fits
+O7.fits
+O8.fits
+O9.fits
+WD0.fits
+WD1.fits
+WD2.fits
+WD3.fits
+WD4.fits
+WD5.fits
+WD6.fits
+WD7.fits
+WD8.fits
+WD9.fits
diff --git a/resources/sun.icns b/resources/sun.icns
new file mode 100644
index 0000000..feace9e
Binary files /dev/null and b/resources/sun.icns differ
diff --git a/resources/tempLines.pickle b/resources/tempLines.pickle
index c4aad70..bcd41e1 100644
Binary files a/resources/tempLines.pickle and b/resources/tempLines.pickle differ
diff --git a/resources/tempLines_2020-04-30.pickle b/resources/tempLines_2020-04-30.pickle
new file mode 100644
index 0000000..b9859d7
Binary files /dev/null and b/resources/tempLines_2020-04-30.pickle differ
diff --git a/resources/tempLines_2020-05-08.pickle b/resources/tempLines_2020-05-08.pickle
new file mode 100644
index 0000000..5ec5242
Binary files /dev/null and b/resources/tempLines_2020-05-08.pickle differ
diff --git a/resources/templates/C0.fits b/resources/templates/C0.fits
new file mode 100644
index 0000000..92d6db9
Binary files /dev/null and b/resources/templates/C0.fits differ
diff --git a/resources/templates/C1.fits b/resources/templates/C1.fits
new file mode 100644
index 0000000..9f916e5
Binary files /dev/null and b/resources/templates/C1.fits differ
diff --git a/resources/templates/C2.fits b/resources/templates/C2.fits
new file mode 100644
index 0000000..b2fdbfc
Binary files /dev/null and b/resources/templates/C2.fits differ
diff --git a/resources/templates/WD0.fits b/resources/templates/WD0.fits
new file mode 100644
index 0000000..f05bd10
Binary files /dev/null and b/resources/templates/WD0.fits differ
diff --git a/resources/templates/WD1.fits b/resources/templates/WD1.fits
new file mode 100644
index 0000000..4861b80
Binary files /dev/null and b/resources/templates/WD1.fits differ
diff --git a/resources/templates/WD2.fits b/resources/templates/WD2.fits
new file mode 100644
index 0000000..84deb47
Binary files /dev/null and b/resources/templates/WD2.fits differ
diff --git a/resources/templates/WD3.fits b/resources/templates/WD3.fits
new file mode 100644
index 0000000..01bdc13
Binary files /dev/null and b/resources/templates/WD3.fits differ
diff --git a/resources/templates/WD4.fits b/resources/templates/WD4.fits
new file mode 100644
index 0000000..665c193
Binary files /dev/null and b/resources/templates/WD4.fits differ
diff --git a/resources/templates/WD5.fits b/resources/templates/WD5.fits
new file mode 100644
index 0000000..4a1b95c
Binary files /dev/null and b/resources/templates/WD5.fits differ
diff --git a/resources/templates/WD6.fits b/resources/templates/WD6.fits
new file mode 100644
index 0000000..36861a9
Binary files /dev/null and b/resources/templates/WD6.fits differ
diff --git a/resources/templates/WD7.fits b/resources/templates/WD7.fits
new file mode 100644
index 0000000..81bc2e5
Binary files /dev/null and b/resources/templates/WD7.fits differ
diff --git a/resources/templates/WD8.fits b/resources/templates/WD8.fits
new file mode 100644
index 0000000..ad9246f
Binary files /dev/null and b/resources/templates/WD8.fits differ
diff --git a/resources/templates/WD9.fits b/resources/templates/WD9.fits
new file mode 100644
index 0000000..25cc40d
Binary files /dev/null and b/resources/templates/WD9.fits differ
diff --git a/resources/templates_SB2/A2+F0.fits b/resources/templates_SB2/A2+F0.fits
new file mode 100644
index 0000000..423ac8c
Binary files /dev/null and b/resources/templates_SB2/A2+F0.fits differ
diff --git a/resources/templates_SB2/A2+F2.fits b/resources/templates_SB2/A2+F2.fits
new file mode 100644
index 0000000..1125ae0
Binary files /dev/null and b/resources/templates_SB2/A2+F2.fits differ
diff --git a/resources/templates_SB2/A3+F0.fits b/resources/templates_SB2/A3+F0.fits
new file mode 100644
index 0000000..1ee4c05
Binary files /dev/null and b/resources/templates_SB2/A3+F0.fits differ
diff --git a/resources/templates_SB2/A3+F2.fits b/resources/templates_SB2/A3+F2.fits
new file mode 100644
index 0000000..f83e6f1
Binary files /dev/null and b/resources/templates_SB2/A3+F2.fits differ
diff --git a/resources/templates_SB2/A3+F5.fits b/resources/templates_SB2/A3+F5.fits
new file mode 100644
index 0000000..819ae27
Binary files /dev/null and b/resources/templates_SB2/A3+F5.fits differ
diff --git a/resources/templates_SB2/A5+F0.fits b/resources/templates_SB2/A5+F0.fits
new file mode 100644
index 0000000..846aa82
Binary files /dev/null and b/resources/templates_SB2/A5+F0.fits differ
diff --git a/resources/templates_SB2/A5+F2.fits b/resources/templates_SB2/A5+F2.fits
new file mode 100644
index 0000000..d075f9a
Binary files /dev/null and b/resources/templates_SB2/A5+F2.fits differ
diff --git a/resources/templates_SB2/A5+F5.fits b/resources/templates_SB2/A5+F5.fits
new file mode 100644
index 0000000..b63fa45
Binary files /dev/null and b/resources/templates_SB2/A5+F5.fits differ
diff --git a/resources/templates_SB2/A5+F6.fits b/resources/templates_SB2/A5+F6.fits
new file mode 100644
index 0000000..83c68ff
Binary files /dev/null and b/resources/templates_SB2/A5+F6.fits differ
diff --git a/resources/templates_SB2/A7+F0.fits b/resources/templates_SB2/A7+F0.fits
new file mode 100644
index 0000000..e4021cd
Binary files /dev/null and b/resources/templates_SB2/A7+F0.fits differ
diff --git a/resources/templates_SB2/A7+F2.fits b/resources/templates_SB2/A7+F2.fits
new file mode 100644
index 0000000..8de9b4b
Binary files /dev/null and b/resources/templates_SB2/A7+F2.fits differ
diff --git a/resources/templates_SB2/A7+F5.fits b/resources/templates_SB2/A7+F5.fits
new file mode 100644
index 0000000..5b0ee32
Binary files /dev/null and b/resources/templates_SB2/A7+F5.fits differ
diff --git a/resources/templates_SB2/A7+F6.fits b/resources/templates_SB2/A7+F6.fits
new file mode 100644
index 0000000..7101c89
Binary files /dev/null and b/resources/templates_SB2/A7+F6.fits differ
diff --git a/resources/templates_SB2/A7+F8.fits b/resources/templates_SB2/A7+F8.fits
new file mode 100644
index 0000000..12bb782
Binary files /dev/null and b/resources/templates_SB2/A7+F8.fits differ
diff --git a/resources/templates_SB2/C0+WD0.fits b/resources/templates_SB2/C0+WD0.fits
new file mode 100644
index 0000000..9fac7d5
Binary files /dev/null and b/resources/templates_SB2/C0+WD0.fits differ
diff --git a/resources/templates_SB2/C0+WD1.fits b/resources/templates_SB2/C0+WD1.fits
new file mode 100644
index 0000000..b43988c
Binary files /dev/null and b/resources/templates_SB2/C0+WD1.fits differ
diff --git a/resources/templates_SB2/C0+WD2.fits b/resources/templates_SB2/C0+WD2.fits
new file mode 100644
index 0000000..82407b8
Binary files /dev/null and b/resources/templates_SB2/C0+WD2.fits differ
diff --git a/resources/templates_SB2/C1+WD0.fits b/resources/templates_SB2/C1+WD0.fits
new file mode 100644
index 0000000..312879c
Binary files /dev/null and b/resources/templates_SB2/C1+WD0.fits differ
diff --git a/resources/templates_SB2/C1+WD1.fits b/resources/templates_SB2/C1+WD1.fits
new file mode 100644
index 0000000..848ece8
Binary files /dev/null and b/resources/templates_SB2/C1+WD1.fits differ
diff --git a/resources/templates_SB2/C1+WD2.fits b/resources/templates_SB2/C1+WD2.fits
new file mode 100644
index 0000000..84d98e9
Binary files /dev/null and b/resources/templates_SB2/C1+WD2.fits differ
diff --git a/resources/templates_SB2/C1+WD3.fits b/resources/templates_SB2/C1+WD3.fits
new file mode 100644
index 0000000..9e68a82
Binary files /dev/null and b/resources/templates_SB2/C1+WD3.fits differ
diff --git a/resources/templates_SB2/C1+WD4.fits b/resources/templates_SB2/C1+WD4.fits
new file mode 100644
index 0000000..94b50ee
Binary files /dev/null and b/resources/templates_SB2/C1+WD4.fits differ
diff --git a/resources/templates_SB2/C2+WD0.fits b/resources/templates_SB2/C2+WD0.fits
new file mode 100644
index 0000000..1e62c99
Binary files /dev/null and b/resources/templates_SB2/C2+WD0.fits differ
diff --git a/resources/templates_SB2/C2+WD1.fits b/resources/templates_SB2/C2+WD1.fits
new file mode 100644
index 0000000..c85d5da
Binary files /dev/null and b/resources/templates_SB2/C2+WD1.fits differ
diff --git a/resources/templates_SB2/C2+WD2.fits b/resources/templates_SB2/C2+WD2.fits
new file mode 100644
index 0000000..bfd92c5
Binary files /dev/null and b/resources/templates_SB2/C2+WD2.fits differ
diff --git a/resources/templates_SB2/C2+WD3.fits b/resources/templates_SB2/C2+WD3.fits
new file mode 100644
index 0000000..df36a71
Binary files /dev/null and b/resources/templates_SB2/C2+WD3.fits differ
diff --git a/resources/templates_SB2/C2+WD4.fits b/resources/templates_SB2/C2+WD4.fits
new file mode 100644
index 0000000..7064a89
Binary files /dev/null and b/resources/templates_SB2/C2+WD4.fits differ
diff --git a/resources/templates_SB2/C2+WD5.fits b/resources/templates_SB2/C2+WD5.fits
new file mode 100644
index 0000000..8850866
Binary files /dev/null and b/resources/templates_SB2/C2+WD5.fits differ
diff --git a/resources/templates_SB2/C2+WD6.fits b/resources/templates_SB2/C2+WD6.fits
new file mode 100644
index 0000000..6f0637c
Binary files /dev/null and b/resources/templates_SB2/C2+WD6.fits differ
diff --git a/resources/templates_SB2/C2+WD7.fits b/resources/templates_SB2/C2+WD7.fits
new file mode 100644
index 0000000..c1b1dc1
Binary files /dev/null and b/resources/templates_SB2/C2+WD7.fits differ
diff --git a/resources/templates_SB2/F0+G7.fits b/resources/templates_SB2/F0+G7.fits
new file mode 100644
index 0000000..44b9937
Binary files /dev/null and b/resources/templates_SB2/F0+G7.fits differ
diff --git a/resources/templates_SB2/F2+G1.fits b/resources/templates_SB2/F2+G1.fits
new file mode 100644
index 0000000..2f13831
Binary files /dev/null and b/resources/templates_SB2/F2+G1.fits differ
diff --git a/resources/templates_SB2/F2+G4.fits b/resources/templates_SB2/F2+G4.fits
new file mode 100644
index 0000000..c528e4d
Binary files /dev/null and b/resources/templates_SB2/F2+G4.fits differ
diff --git a/resources/templates_SB2/F2+G6.fits b/resources/templates_SB2/F2+G6.fits
new file mode 100644
index 0000000..9cf34a3
Binary files /dev/null and b/resources/templates_SB2/F2+G6.fits differ
diff --git a/resources/templates_SB2/F2+G7.fits b/resources/templates_SB2/F2+G7.fits
new file mode 100644
index 0000000..f20f601
Binary files /dev/null and b/resources/templates_SB2/F2+G7.fits differ
diff --git a/resources/templates_SB2/F5+G0.fits b/resources/templates_SB2/F5+G0.fits
new file mode 100644
index 0000000..8233f83
Binary files /dev/null and b/resources/templates_SB2/F5+G0.fits differ
diff --git a/resources/templates_SB2/F5+G1.fits b/resources/templates_SB2/F5+G1.fits
new file mode 100644
index 0000000..c2cd2b9
Binary files /dev/null and b/resources/templates_SB2/F5+G1.fits differ
diff --git a/resources/templates_SB2/F5+G3.fits b/resources/templates_SB2/F5+G3.fits
new file mode 100644
index 0000000..3763bcf
Binary files /dev/null and b/resources/templates_SB2/F5+G3.fits differ
diff --git a/resources/templates_SB2/F5+G4.fits b/resources/templates_SB2/F5+G4.fits
new file mode 100644
index 0000000..c12f251
Binary files /dev/null and b/resources/templates_SB2/F5+G4.fits differ
diff --git a/resources/templates_SB2/F5+G5.fits b/resources/templates_SB2/F5+G5.fits
new file mode 100644
index 0000000..06b73f8
Binary files /dev/null and b/resources/templates_SB2/F5+G5.fits differ
diff --git a/resources/templates_SB2/F5+G6.fits b/resources/templates_SB2/F5+G6.fits
new file mode 100644
index 0000000..d69c3ac
Binary files /dev/null and b/resources/templates_SB2/F5+G6.fits differ
diff --git a/resources/templates_SB2/F5+G7.fits b/resources/templates_SB2/F5+G7.fits
new file mode 100644
index 0000000..f15f72f
Binary files /dev/null and b/resources/templates_SB2/F5+G7.fits differ
diff --git a/resources/templates_SB2/F6+G0.fits b/resources/templates_SB2/F6+G0.fits
new file mode 100644
index 0000000..3d37da1
Binary files /dev/null and b/resources/templates_SB2/F6+G0.fits differ
diff --git a/resources/templates_SB2/F6+G1.fits b/resources/templates_SB2/F6+G1.fits
new file mode 100644
index 0000000..7c71f81
Binary files /dev/null and b/resources/templates_SB2/F6+G1.fits differ
diff --git a/resources/templates_SB2/F6+G3.fits b/resources/templates_SB2/F6+G3.fits
new file mode 100644
index 0000000..b85a86d
Binary files /dev/null and b/resources/templates_SB2/F6+G3.fits differ
diff --git a/resources/templates_SB2/F6+G4.fits b/resources/templates_SB2/F6+G4.fits
new file mode 100644
index 0000000..ccf1342
--- /dev/null
+++ b/resources/templates_SB2/F6+G4.fits
@@ -0,0 +1,5810 @@
+SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 32 / length of dimension 1 NAXIS2 = 61596 / length of dimension 2 PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups TFIELDS = 4 / number of table fields TTYPE1 = 'LogLam ' TFORM1 = 'D ' TTYPE2 = 'Flux ' TFORM2 = 'D ' TTYPE3 = 'PropErr ' TFORM3 = 'D ' TTYPE4 = 'Std ' TFORM4 = 'D ' END @Z?ǜ,v?/
+?/
+@M|?Мjw?͉5?͉5@̟?ٝ2_?|p
+?|p
+@XK¡?⟃?iݔ?iݔ@$w?2d?WKl?WKl@JN?x?DP?DP@+%?s>?2(C?2(C@HM?,?R/?R/@Vp?^8?
~?
~@#F?5?v0?v0@Ŷ?!j?K?K@DV?*r x?X?X@Ĉ-?3]I?$%?$%@UC??:&>@!A?E'Y[?K?K@Ad?M^V?=_?=_@Ӻ?U6A?xL?xL@ׇ?^?]P?fY<?fY<@S5?d?SZ
+U?SZ
+U@ =?ls?AwVa?AwVa@?tƌ%(?/Og?/Og@<5?|m:?fX?fX@ꅻX?Eu?
+) 2&?
+) 2&@R:{f?ǫ?nG1?nG1@=??Mzê3?Mzê3@8?͌&#?-7?-7@?2?tlvЧ?tlvЧ@7?}ҹ+??????@P)?*X?%-%-<@5Lo???@oE?Ff-F?v ?v @3?֣9?dtb?dtb@?fz?Q??Q?@O1?\??g??g@?rw?, N?, N@0w?T?kW?kW@@M?sC:?Is?Is@#.c$?>?$? #? #@'M?&-E>?nV?nV@+,?1YU?)j'M?)j'M@.˨?W?Zd|tn?sMo??sMo?@A'y?_|U?`ȴ?`ȴ@E?dC?t?M߷.?M߷.@I~%?i{F?;RIyf?;RIyf@MJ^?nGG?(ħ?(ħ@Q$4?s#u?XS?XS@T(?xy?ZR,?ZR,@X"J?~j?@?@@\|m?6"V>?WŔ7?WŔ7@`I ?6ϵ?ee?ee@df?Nl?a4?a4@g=?ez1??@k?)?hS?hS@o{?ˣ?2?2@sG>?퓟?mr̶?mr̶@wa?oIhqO?[K;nI?[K;nI@zn?=yS?.rv?@A?@A@v?3;n?.$.?.$.@׀?7Q{?/?/@n?;9wZ? FlF? FlF@p~D?@Aښп?Җ?Җ@<?D}?帉n?帉n@ }$?H+ 5?ӟg5?ӟg5@G?MHځm?]iG?]iG@{j?Qc?nCU?nCU@nv?Va#X?Qw?Qw@;yM?ZΜ
+?*8?*8@#?_ݍ?yʁm?yʁm@w?d;#}2?fM?fM@?hn?Td?Td@mv;?m?B1D?B1D@9^~?r^5M?0rVlG?0rVlG@"tU?wn?O4?O4@%+?{sX?,m#?,m#@)r?3L}E?
+?
+@-k?7L'?`?`@18q?t?%?%@5/?E;?èZ:
?]&H6Y?]&H6Y@IOD?@i9Taw?9Taw@r?C?P>?P>@G?E6K??@\Ʒ?GjX)?ξ?ξ@)Eڞ?I?*U?*U@u?Kҵ?g:?g:@D L?N?bL|?bL|@C"?P:]?>Ob?>Ob@[Be?Rn?/T&?/T&@'?TB6Z?p> ?p> @@?SJSO?
+)w?
+)w@ }?Rb*? %a
? %a
@
>T?Q|?6a?6a@Y+?Q<?Kr?Kr@&=7?P\?aeC?aeC@Y?PeQ?w=*!?w=*!@;|?P|3^??@ ?P<'?`$?`$@$X9\?Oϭ
+?0?0@($3?O,J?iv)?iv)@+8 ?O{jfﳶ;?N:hh?Rul?Rul@B2?N+[?hw?hw@F?NGw~?~7?~7@JU1?Nd?9u
?9u
@N!A?NmGQL?7?7@Q/dl?NȠ?Ts?Ts@UC?N$/?coes?coes@Y-?Nc?b?b@]S?Ngo? i1x? i1x@a +?O7%(?'/>?'/>@%}?Pw
+?m?m@P)S?Pi~? ? @#L*?QJ?;?;@o?Qnc?j?j@!?Qs1D?K?K@?RVd?2ۍ]?2ۍ]@Oׅ?RhC?H[8?H[8@\?S?^|m?^|m@2?Sb?tl;Y?tl;Y@@ ?Sfy?\@}?\@}@b?Tvk?N:KA?N:KA@M?Tn*?@]?@]@?Rd}DŽ!?`?`@d?P,?~?r?r@:?M*?`}E?`}E@?K?n ?n @L3?Iw-?gF}N`?gF}N`@V?GK0'.^?Cd?Cd@y?E%~Y?' O?' O@DZl?BڙpH?1?1@~C?@J.?<(>x?<(>x@J?>i1?G: ?G: @?<1?R6?R6@'?9O]J?]|C?]|C@ڰJ?7"T?h]7}?h]7}@|mt?5Xqc?s/Yj?s/Yj@IK?1 ?tE?tE@!?-д?UC*F?UC*F@?*>-,i?
+?
+@?&?
?
@{?"Ab?e7?e7@G>|?#6 ?ú?ú@ aS?pD1?w^?w^@*??[?[@ ? 8lP?T?T@y?UT?!?!@F?P?7E?7E@?5U?{I5?{I5@2[?:P? ĕ? ĕ@U2?3x?->c?->c@xx ? ?8;{?8;{@D?J?C8Qb?C8Qb@ ??N5?N5@"?U?Y4w.?Y4w.@&c?-?d2?d2@*v~&:?5"?o1