Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ __pycache__/
.vs/
*.sps
*.spanc
*.csv
*.csv
*.DS_Store
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
SPSPy is a Python based package of tools for use with the Super-Enge Split-Pole Spectrograph at FSU. Much of the code here is based on Java programs originally written at Yale University by D.W. Visser, C.M. Deibel, and others. Currently the package contains SPSPlot, a tool aimed at informing users which states should appear at the focal plane of the SESPS, and SPANC, a tool for calibrating the position spectra from the focal plane.

## Depencencies and Requirements
The requirements for running SPSPy are outlined in the requirements.txt file located in the repository. It is recommended to install these to a local virtual environment using `pip install -r requirements.txt`. For conda use the environments.yml file to create a conda environment for SPSPy. Simply run `conda env create -f environment.yml` from the SPSPy directory. conda will make a new virtual environment named spsenv with the dependencies outlined in environments.yml. If you already have an environment named spsenv or would like to change the name simply edit the first line of the enviornments.yml.

The recommended install for SPSPy dependencies is via pip.
To ensure capability, use python 3.14. The requirements for running SPSPy are outlined in the requirements.txt file located in the repository. It is recommended to install these to a local virtual environment using `pip install -r requirements.txt`.

### Creating a virtual environment with pip
To create a virtual environment with pip in the terminal for MacOS or Linux use `python3 -m venv env` to create a local virtual environment named `env` (or whatever name you'd like), or on Windows use `py -m venv env` to do the same. To activate your new environment run `source env/bin/activate` in MacOS or Linux, or `.\env\Scripts\activate`. Now you can run the above `pip` command to install all dependencies to the virtual environment. To leave the virtual environment use the command `deactivate` in your terminal.
Expand Down
51 changes: 0 additions & 51 deletions environment.yml

This file was deleted.

33 changes: 25 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
lxml==4.9.1
matplotlib==3.6.2
numpy==1.23.5
pycatima==1.71
pyqtdarktheme==1.2.1
PySide6==6.4.1
requests==2.28.1
scipy==1.9.3
certifi==2025.11.12
charset-normalizer==3.4.4
contourpy==1.3.3
cycler==0.12.1
fonttools==4.61.0
idna==3.11
kiwisolver==1.4.9
lxml==6.0.2
matplotlib==3.10.7
numpy==2.3.5
packaging==25.0
pillow==12.0.0
pycatima==1.981
pyparsing==3.2.5
PySide6==6.10.1
PySide6_Addons==6.10.1
PySide6_Essentials==6.10.1
python-dateutil==2.9.0.post0
QDarkStyle==3.2.3
QtPy==2.4.3
requests==2.32.5
scipy==1.16.3
shiboken6==6.10.1
six==1.17.0
urllib3==2.5.0
5 changes: 3 additions & 2 deletions spspy/Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import sys
import matplotlib as mpl
from qdarktheme import load_stylesheet
import qdarkstyle

class Launcher(QMainWindow):
def __init__(self, parent=None):
Expand Down Expand Up @@ -43,6 +43,7 @@ def run_launcher() -> None:
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = Launcher()
sys.exit(app.exec_())
5 changes: 3 additions & 2 deletions spspy/SPSPlotUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QAction

from qdarktheme import load_stylesheet
import qdarkstyle
from enum import Enum, auto
import matplotlib as mpl
import sys
Expand Down Expand Up @@ -299,6 +299,7 @@ def run_spsplot_ui():
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = SPSPlotGUI()
sys.exit(app.exec_())
80 changes: 79 additions & 1 deletion spspy/Spanc.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,82 @@ def calculate_calibrations(self) -> None:
for calibration in self.calibrations.values():
rxn = self.reactions[calibration.rxnName]
calibration.rho = rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation))
calibration.rhoErr = np.abs(rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation + calibration.excitationErr)) - calibration.rho)
calibration.rhoErr = np.abs(rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation + calibration.excitationErr)) - calibration.rho)

def get_excitation_curves(self,
x_min: float = -300.0,
x_max: float = 300.0,
n_bins: int = 601):
"""
Compute excitation-energy curves for every reaction by evaluating
Ex = reaction.calculate_excitation( rho(x) ) across a grid of
focal-plane positions.

Returns:
x_vals: array of FP bin centers
ex_curves: { rxn_name: Ex_array_in_MeV }
"""
# Bin centers (e.g. 601 values from -300 to 300)
x_vals = np.linspace(x_min, x_max, n_bins)

# ρ(x) uses your fitted polynomial a0 + a1 x + ... + aN x^N
params = self.fitter.get_parameters() # [a0, a1, ..., aN]
rho_vals = np.polyval(params[::-1], x_vals) # reverse order for numpy poly

ex_curves: dict[str, np.ndarray] = {}

for rxn_name, rxn in self.reactions.items():
ex_vals = np.array(
[rxn.calculate_excitation(rho) for rho in rho_vals],
dtype=float,
)
ex_curves[rxn_name] = ex_vals

return x_vals, ex_curves

def export_excitation_csv(self,
filename: str,
x_min: float = -300.0,
x_max: float = 300.0,
n_bins: int = 601):
"""
Export excitation-energy calibration table as CSV.

CSV format:
FP_x_mm, Ex_rxn1_MeV, Ex_rxn2_MeV, ...

FP_x_mm runs from x_min to x_max with n_bins steps (inclusive).
"""
import csv
import numpy as np

# Generate FP positions (601 values from -300 to 300)
x_vals = np.linspace(x_min, x_max, n_bins)

# Compute rho(x) using fitted polynomial
params = self.fitter.get_parameters() # [a0, a1, ..., aN]
rho_vals = np.polyval(params[::-1], x_vals)

# Compute excitation for each reaction
rxn_names = list(self.reactions.keys())
ex_mev = {rxn: [] for rxn in rxn_names}

for rho in rho_vals:
for rxn_name, rxn in self.reactions.items():
ex = rxn.calculate_excitation(rho)
ex_mev[rxn_name].append(ex)

# Write CSV
with open(filename, "w", newline="") as csvfile:
writer = csv.writer(csvfile)

# Header row
header = ["x_mm"] + [f"Ex_{rxn}_MeV" for rxn in rxn_names]
writer.writerow(header)

# Data rows
for i, x in enumerate(x_vals):
row = [f"{x:.6f}"]
for rxn in rxn_names:
row.append(f"{ex_mev[rxn][i]:.9f}")
writer.writerow(row)
50 changes: 48 additions & 2 deletions spspy/SpancUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QAction

from qdarktheme import load_stylesheet, load_palette
import qdarkstyle
import matplotlib as mpl
import numpy as np
from numpy.typing import NDArray
Expand Down Expand Up @@ -95,6 +95,17 @@ def create_fit_canvas(self) -> None:
fitOptionLayout.addWidget(QLabel("Polynomial Order", self.fitOptionGroup))
fitOptionLayout.addWidget(self.fitOrderBox)
fitOptionLayout.addWidget(self.fitButton)

# NEW: button to plot excitation curves for all reactions
self.exCurveButton = QPushButton("Plot Ex vs x", self.fitOptionGroup)
self.exCurveButton.clicked.connect(self.plot_excitation_curves)
fitOptionLayout.addWidget(self.exCurveButton)

# Button to export excitation calibration CSV
self.exportCSVButton = QPushButton("Export Ex(x) CSV", self.fitOptionGroup)
self.exportCSVButton.clicked.connect(self.handle_export_excitation_csv)
fitOptionLayout.addWidget(self.exportCSVButton)

self.fitOptionGroup.setLayout(fitOptionLayout)

fitLayout.addWidget(QLabel("Fit", self.fitCanvas))
Expand Down Expand Up @@ -374,13 +385,48 @@ def update_fit_text(self, residuals: NDArray[np.float64], studentizedResiduals:
f"## Parameter Uncertanties (ua0 -> uaN): {np.array_str(self.spanc.fitter.get_parameter_errors(), precision=3)} \n \n"
f"## Residuals (x0 -> xN): {np.array_str(residuals, precision=3)} \n \n"
f"## Studentized Residuals (x0 -> xN): {np.array_str(studentizedResiduals, precision=3)} \n \n")

self.fitResultText.setMarkdown(markdownString)

def plot_excitation_curves(self):
# Require a fit so rho(x) is defined
if not self.spanc.isFit:
print("Run the calibration fit first before plotting Ex vs x.")
return

# Compute Ex at 600 bin centers from -300 to 300 for all reactions
x_vals, ex_curves = self.spanc.get_excitation_curves(
x_min=-300.0,
x_max=300.0,
n_bins=600,
)

self.fitCanvas.axes.cla()
for rxn_name, ex_vals in ex_curves.items():
self.fitCanvas.axes.plot(x_vals, ex_vals, label=rxn_name)

self.fitCanvas.axes.set_xlabel(r"$x$ (mm)")
self.fitCanvas.axes.set_ylabel(r"$E_x$ (MeV)")
self.fitCanvas.axes.set_title("Excitation energy vs focal-plane position")
self.fitCanvas.axes.grid(True)
self.fitCanvas.axes.legend()
self.fitCanvas.fig.tight_layout()
self.fitCanvas.draw()

def handle_export_excitation_csv(self):
fileName = QFileDialog.getSaveFileName(
self, "Export Excitation CSV", "./", "CSV Files (*.csv)"
)
if fileName[0]:
self.spanc.export_excitation_csv(fileName[0])
print(f"Exported excitation calibration to {fileName[0]}")

def run_spanc_ui() :
mpl.use("Qt5Agg")
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = SpancGUI()
sys.exit(app.exec_())