bug fix for FixExData, rewrite PlotWindow.py, makes it as a genric plot window

This commit is contained in:
Ryan@Home 2024-11-08 00:20:44 -05:00
parent 81b79f9582
commit 019877e5ea
3 changed files with 187 additions and 150 deletions

View File

@ -35,7 +35,7 @@ class FitPlotWidget(QWidget):
class Fitting(): class Fitting():
def __init__(self): def __init__(self):
self.ExList = [] self.dataName_list = []
self.fitOption = [] self.fitOption = []
self.expData = [] self.expData = []
@ -49,7 +49,7 @@ class Fitting():
print(self.headers) print(self.headers)
def read_expData(self, fileName): def read_expData(self, fileName):
self.ExList = [] self.dataName_list = []
self.fitOption = [] self.fitOption = []
self.expData = [] self.expData = []
@ -74,7 +74,7 @@ class Fitting():
# Extract dataSet Name # Extract dataSet Name
dataName = line.split()[1] dataName = line.split()[1]
self.ExList.append(dataName) self.dataName_list.append(dataName)
# Check for fit option lines # Check for fit option lines
elif line.startswith("fit"): elif line.startswith("fit"):
@ -92,14 +92,14 @@ class Fitting():
self.expData.append(np.array(current_data, dtype=float)) self.expData.append(np.array(current_data, dtype=float))
# Convert to numpy arrays # Convert to numpy arrays
self.ExList = np.array(self.ExList) self.dataName_list = np.array(self.dataName_list)
self.expData = [np.array(data) for data in self.expData] self.expData = [np.array(data) for data in self.expData]
# Output the result # Output the result
print("=========== Number of data set:", len(self.ExList)) print("=========== Number of data set:", len(self.dataName_list))
for i in range(0, len(self.ExList)): for i in range(0, len(self.dataName_list)):
print("-------------------------") print("-------------------------")
print(" ExList:", self.ExList[i]) print(" ExList:", self.dataName_list[i])
print("Fit Options:", self.fitOption[i]) print("Fit Options:", self.fitOption[i])
print(" Data List:\n", self.expData[i]) print(" Data List:\n", self.expData[i])
@ -127,64 +127,64 @@ class Fitting():
fitTheory_upper = [] fitTheory_upper = []
para = [] para = []
perr = [] para_err = []
chi_squared = [] chi_squared = []
for k in range(nFit): for k in range(nFit):
# Get the cross-section IDs for the current fit option and strip extra spaces # Get the cross-section IDs for the current fit option and strip extra spaces
xsecIDStr = self.fitOption[expDataID][k].strip() xsecIDStr = self.fitOption[expDataID][k].strip()
xsecID = [int(part.strip()) for part in xsecIDStr.split('+')] if '+' in xsecIDStr else [int(xsecIDStr)] xsecID = [int(part.strip()) for part in xsecIDStr.split('+')] if '+' in xsecIDStr else [int(xsecIDStr)]
# Ensure all cross-section IDs are valid # Ensure all cross-section IDs are valid
processFlag = True processFlag = True
for id in range(len(xsecID)): for id in range(len(xsecID)):
if xsecID[id] >= nXsec: if xsecID[id] >= nXsec:
print(f"Error: Requested Xsec-{xsecID[id]} exceeds the number of available cross-sections ({nXsec})") print(f"Error: Requested Xsec-{xsecID[id]} exceeds the number of available cross-sections ({nXsec})")
processFlag = False processFlag = False
if processFlag == False : if processFlag == False :
continue continue
# Define the fitting function: a weighted sum of the selected data # Define the fitting function: a weighted sum of the selected data
def fit_func(x, *scale): def fit_func(x, *scale):
y = np.zeros_like(x) y = np.zeros_like(x)
for p, id in enumerate(xsecID): for p, id in enumerate(xsecID):
y += scale[p] * np.interp(x, self.dataX, self.data[id]) y += scale[p] * np.interp(x, self.dataX, self.data[id])
return y return y
lower_bounds = [1e-6] * len(xsecID) # Setting a small positive lower bound
upper_bounds = [np.inf] * len(xsecID) # No upper bound
lower_bounds = [1e-6] * len(xsecID) # Setting a small positive lower bound # Perform curve fitting using the fit_func and experimental data with y-errors as weights
upper_bounds = [np.inf] * len(xsecID) # No upper bound popt, pcov = curve_fit(fit_func, x_exp, y_exp, sigma=y_err, absolute_sigma=True,
p0=np.ones(len(xsecID)), # Initial guess for scale parameters
bounds=(lower_bounds, upper_bounds))
# Perform curve fitting using the fit_func and experimental data with y-errors as weights para.append(popt)
popt, pcov = curve_fit(fit_func, x_exp, y_exp, sigma=y_err, absolute_sigma=True, perr = np.sqrt(np.diag(pcov))# Standard deviation of the parameters
p0=np.ones(len(xsecID)), # Initial guess for scale parameters para_err.append(perr)
bounds=(lower_bounds, upper_bounds))
para.append(popt) # Get the fitted model values
perr.append(np.sqrt(np.diag(pcov))) # Standard deviation of the parameters y_fit = fit_func(x_exp, *popt)
residuals = y_exp - y_fit
chi_squared.append(np.sum((residuals / y_err) ** 2))
# Get the fitted model values print(f"Fitted scale for fit {k}: {', '.join([f'{x:.3f}' for x in popt])} +/- {', '.join([f'{x:.3f}' for x in perr])} | Chi^2 : {chi_squared[-1]:.4f}")
y_fit = fit_func(x_exp, *popt) # print(f"Fitted scale for fit {k}: {popt} +/- {perr} | Chi^2 : {chi_squared[-1]:.4f}")
residuals = y_exp - y_fit
chi_squared.append(np.sum((residuals / y_err) ** 2))
print(f"Fitted scale for fit {k}: {', '.join([f'{x:.3f}' for x in popt])} +/- {', '.join([f'{x:.3f}' for x in perr[-1]])} | Chi^2 : {chi_squared[-1]:.4f}") # Append the theoretical fit for this fit option
# print(f"Fitted scale for fit {k}: {popt} +/- {perr} | Chi^2 : {chi_squared[-1]:.4f}") fitTheory.append(np.zeros_like(self.dataX))
for p, id in enumerate(xsecID):
fitTheory[-1] += popt[p] * np.interp(self.dataX, self.dataX, self.data[id])
# Append the theoretical fit for this fit option # Optionally, you can plot the uncertainty as shaded regions (confidence intervals)
fitTheory.append(np.zeros_like(self.dataX)) # Create the upper and lower bounds of the theoretical model with uncertainties
for p, id in enumerate(xsecID): fitTheory_upper.append(np.zeros_like(self.dataX))
fitTheory[k] += popt[p] * np.interp(self.dataX, self.dataX, self.data[id]) fitTheory_lower.append(np.zeros_like(self.dataX))
# Optionally, you can plot the uncertainty as shaded regions (confidence intervals) for p, id in enumerate(xsecID):
# Create the upper and lower bounds of the theoretical model with uncertainties fitTheory_upper[-1] += (popt[p] + perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fitTheory_upper.append(np.zeros_like(self.dataX)) fitTheory_lower[-1] += (popt[p] - perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fitTheory_lower.append(np.zeros_like(self.dataX))
for p, id in enumerate(xsecID):
fitTheory_upper[k] += (popt[p] + perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fitTheory_lower[k] += (popt[p] - perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fig = plt.figure() fig = plt.figure()
figure_list.append(fig) figure_list.append(fig)
@ -206,13 +206,11 @@ class Fitting():
plt.yscale('log') plt.yscale('log')
# Replace plt.title() with plt.text() to position the title inside the plot # Replace plt.title() with plt.text() to position the title inside the plot
plt.text(0.05, 0.05, f'Fit for Exp Data : {self.ExList[expDataID]}', transform=plt.gca().transAxes, plt.text(0.05, 0.05, f'Fit for Exp Data : {self.dataName_list[expDataID]}', transform=plt.gca().transAxes,
fontsize=12, verticalalignment='bottom', horizontalalignment='left', color='black') fontsize=12, verticalalignment='bottom', horizontalalignment='left', color='black')
for i, _ in enumerate(para): for i, _ in enumerate(para):
plt.text(0.05, 0.1 + 0.05*i, f"Xsec-{self.fitOption[expDataID][i].strip()}: {', '.join([f'{x:.3f}' for x in para[i]])} +/- {', '.join([f'{x:.3f}' for x in perr[i]])}" , transform=plt.gca().transAxes, plt.text(0.05, 0.1 + 0.05*i, f"Xsec-{self.fitOption[expDataID][i].strip()}: {', '.join([f'{x:.3f}' for x in para[i]])} +/- {', '.join([f'{x:.3f}' for x in para_err[i]])}" , transform=plt.gca().transAxes,
fontsize=12, verticalalignment='bottom', horizontalalignment='left', color=default_colors[i]) fontsize=12, verticalalignment='bottom', horizontalalignment='left', color=default_colors[i])
return figure_list return figure_list

View File

@ -1,126 +1,164 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os
import time
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QGridLayout, QWidget, QCheckBox QGridLayout, QWidget, QCheckBox
) )
from PyQt6.QtCore import QUrl
from PyQt6.QtWebEngineWidgets import QWebEngineView import numpy as np
import plotly.graph_objects as go
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from ExtractXsecPy import read_DWBA from ExtractXsecPy import read_DWBA
class PlotWindow(QWidget): class PlotWindow(QWidget):
def __init__(self, XsecFile): def __init__(self, windowTitle):
super().__init__() super().__init__()
self.setWindowTitle("DWBA Plot") self.setWindowTitle(windowTitle)
self.setGeometry(100, 100, 800, 600) self.resize(800, 600)
self.x = [] self.default_colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
self.data = []
self.headers = []
self.read_data(XsecFile)
self.log_scale_checkbox = QCheckBox("Use Log Scale for Y-Axis") self.log_scale_checkbox = QCheckBox("Use Log Scale for Y-Axis")
self.log_scale_checkbox.setChecked(True) self.log_scale_checkbox.setChecked(True)
self.log_scale_checkbox.stateChanged.connect(self.plot_plotly_graph) self.log_scale_checkbox.stateChanged.connect(self.plot_graph)
self.gridline_checkbox = QCheckBox("Show Gridlines") self.gridline_checkbox = QCheckBox("Show Gridlines")
self.gridline_checkbox.stateChanged.connect(self.plot_plotly_graph) self.gridline_checkbox.stateChanged.connect(self.plot_graph)
self.showMarker_checkBox = QCheckBox("Show Markers") self.showMarker_checkBox = QCheckBox("Show Markers")
self.showMarker_checkBox.stateChanged.connect(self.plot_plotly_graph) self.showMarker_checkBox.stateChanged.connect(self.plot_graph)
self.html_file = None self.figure, self.ax = plt.subplots()
self.web_view = QWebEngineView() self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
layout = QGridLayout(self) layout = QGridLayout(self)
layout.addWidget(self.showMarker_checkBox, 0, 0) layout.addWidget(self.toolbar, 0, 0, 1, 3)
layout.addWidget(self.log_scale_checkbox, 0, 1) layout.addWidget(self.showMarker_checkBox, 1, 0)
layout.addWidget(self.gridline_checkbox, 0, 2) layout.addWidget(self.log_scale_checkbox, 1, 1)
layout.addWidget(self.web_view, 1, 0, 5, 3) layout.addWidget(self.gridline_checkbox, 1, 2)
layout.addWidget(self.canvas, 2, 0, 5, 3)
self.plot_plotly_graph() self.xData = []
self.yData_list = []
self.header_list = []
self.yTitle = ""
def read_data(self,file_path): self.x_exp = [] # x positions
self.headers, self.x, self.data = read_DWBA(file_path) self.x_err = [] # x uncertainties (errors)
self.y_exp = [] # y positions
self.y_err = [] # y errors
self.dataName = ""
self.fitOption = []
def plot_plotly_graph(self): self.para = [] # fit parameters
self.perr = [] # fit error
self.chi_square = [] # fit Chi-squared
if self.html_file and os.path.exists(self.html_file): def set_plot_data(self, xData, yData_list, header_list, yTitle):
os.remove(self.html_file) self.xData = xData
self.yData_list = yData_list
# Create a Plotly figure self.header_list = header_list
fig = go.Figure() self.yTitle = yTitle
if self.showMarker_checkBox.isChecked() : def set_expData(self, expData, fitOption, dataName_list, ID):
plotStyle = 'lines+markers' self.x_exp = expData[ID][:, 0]
self.x_err = expData[ID][:, 1]
self.y_exp = expData[ID][:, 2]
self.y_err = expData[ID][:, 3]
self.dataName = dataName_list[ID]
self.fitOption = fitOption[ID]
def read_Xsec(self, file_path):
headers, dataX, data = read_DWBA(file_path)
self.xData = dataX
self.yData_list = data
self.header_list = headers[1:]
def set_fitResult(self, para, perr, chi_sq):
self.para = para
self.perr = perr
self.chi_square = chi_sq
def plot_graph(self):
self.ax.clear()
plotStyle = '-' if not self.showMarker_checkBox.isChecked() else '-o'
for i, y in enumerate(self.yData_list):
self.ax.plot(self.xData, y, plotStyle, label=self.header_list[i])
self.ax.set_xlabel('Angle_CM [deg]')
self.ax.set_ylabel(self.yTitle)
self.ax.legend(loc='upper right', frameon=True)
# Apply log scale for y-axis if selected
if self.log_scale_checkbox.isChecked():
self.ax.set_yscale('log')
else: else:
plotStyle = 'lines' self.ax.set_yscale('linear')
self.ax.autoscale(enable=True, axis='x', tight=True)
self.figure.tight_layout()
def plot_Fit(self):
self.ax.clear()
self.ax.errorbar(self.x_exp, self.y_exp, xerr=self.x_err, yerr=self.y_err,
fmt='x', label='Experimental Data', color='black', markersize = 15, elinewidth=2)
self.ax.set_xlabel('Angle_CM [deg]')
self.ax.set_ylabel(self.yTitle)
self.ax.legend(loc='upper right', frameon=True)
# Apply log scale for y-axis if selected
if self.log_scale_checkbox.isChecked():
self.ax.set_yscale('log')
else:
self.ax.set_yscale('linear')
self.ax.autoscale(enable=True, axis='x', tight=True)
self.figure.tight_layout()
for k in range(len(self.fitOption)):
fitTheory = []
fitTheory_lower = []
fitTheory_upper = []
xsecIDStr = self.fitOption[k].strip()
xsecID = [int(part.strip()) for part in xsecIDStr.split('+')] if '+' in xsecIDStr else [int(xsecIDStr)]
fitTheory.append(np.zeros_like(self.xData))
for p, id in enumerate(xsecID):
fitTheory += self.para[p] * np.interp(self.xData, self.xData, self.yData_list[id])
fitTheory_upper.append(np.zeros_like(self.xData))
fitTheory_lower.append(np.zeros_like(self.xData))
for p, id in enumerate(xsecID):
fitTheory_upper += (self.para[p] + self.perr[p]) * np.interp(self.xData, self.xData, self.yData_list[id])
fitTheory_lower += (self.para[p] - self.perr[p]) * np.interp(self.xData, self.xData, self.yData_list[id])
# Replace plt.title() with plt.text() to position the title inside the plot
self.ax.text(0.05, 0.05, f'Fit for Exp Data : {self.dataName}', transform=plt.gca().transAxes,
fontsize=12, verticalalignment='bottom', horizontalalignment='left', color='black')
for i, fit in enumerate(fitTheory):
self.ax.plot(self.xData, fit, label=f'Chi2:{self.chi_square[i]:.3f} | Xsec:{self.fitOption[i]}')
self.ax.fill_between(self.xData, fitTheory_lower[i], fitTheory_upper[i], alpha=0.2)
for i, _ in enumerate(self.para):
self.ax.text(0.05, 0.1 + 0.05*i, f"Xsec-{self.fitOption[i].strip()}: {', '.join([f'{x:.3f}' for x in self.para[i]])} +/- {', '.join([f'{x:.3f}' for x in self.perr[i]])}" ,
transform=plt.gca().transAxes, fontsize=12,
verticalalignment='bottom', horizontalalignment='left', color=self.default_colors[i])
self.canvas.draw_idle()
# Add traces for each column in data against x
for i, y in enumerate(self.data):
fig.add_trace(go.Scatter(x=self.x, y=y, mode=plotStyle, name=self.headers[i + 1])) # Use headers for names
# Update layout for better presentation
fig.update_layout(
xaxis_title="Angle_CM [Deg]",
yaxis_title="Xsec [mb/sr]",
template="plotly",
plot_bgcolor='rgba(0,0,0,0)', # Set plot background to transparent
paper_bgcolor='rgba(0,0,0,0)', # Set paper background to transparent
legend=dict(
x=1, # X position (1 = far right)
y=1, # Y position (1 = top)
xanchor='right', # Anchor the legend to the right
yanchor='top', # Anchor the legend to the top
bgcolor='rgba(255, 255, 255, 0.5)', # Optional: semi-transparent background for legend
bordercolor='rgba(0, 0, 0, 0.5)', # Optional: border color
borderwidth=1 # Optional: border width
),
yaxis=dict(
# linecolor='black', # Set y-axis line color to black
type ='log' if self.log_scale_checkbox.isChecked() else 'linear', # Toggle y-axis scale
gridcolor='lightgray', # Set gridline color
gridwidth=1, # Set gridline width (in pixels)
showgrid = self.gridline_checkbox.isChecked() # Toggle gridlines for y-axis
),
xaxis=dict(
# linecolor='black', # Set x-axis line color to black
gridcolor='lightgray', # Set gridline color
gridwidth=1, # Set gridline width (in pixels)
showgrid = self.gridline_checkbox.isChecked() # Toggle gridlines for x-axis as well
),
margin=dict(l=40, r=40, t=40, b=40), # Set margins to reduce empty space
# width=800, # Optional: set fixed width for the plot
# height=600 # Optional: set fixed height for the plot
)
fig.add_shape(
# Line with reference to the plot
type="rect",
xref="paper",
yref="paper",
x0=0,
y0=0,
x1=1.0,
y1=1.0,
line=dict(
color="black",
width=1,
)
)
# Save the plot as an HTML file in a temporary location
timestamp = int(time.time() * 1000) # Unique timestamp in milliseconds
html_file = f"/tmp/plotwindow_{timestamp}.html"
fig.write_html(html_file)
self.html_file = html_file # Store for cleanup
self.web_view.setUrl(QUrl.fromLocalFile(html_file))
def __del__(self):
if os.path.exists(self.html_file):
os.remove(self.html_file)

View File

@ -5,4 +5,5 @@ PyQt6-WebEngine
numpy numpy
pandas pandas
matplotlib matplotlib
kaleido kaleido
scipy