Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
2348e34
new changes for the 3d gui and the fitting panel for workable version
Apr 10, 2025
7a33a6a
new additions with the main interface and the 4d gui
Apr 10, 2025
d175e85
added set points for the cursors and save button to extract the relev…
Apr 10, 2025
38cc77c
Changed the name of the class to Gui_3d
Apr 10, 2025
edecfcf
update imports
rettigl Apr 11, 2025
263c72b
Took out the cursors for the EDC graph since it will be treated in th…
Apr 11, 2025
82bac6e
changed the method of plotting the xaarays
Apr 11, 2025
c690b4a
added small comments
Apr 11, 2025
e993b0e
small change
Apr 11, 2025
c8d6497
add extraction result button for the Jupyter Notebook and corrected s…
Apr 12, 2025
ab5c98a
Created a method for double clicking on the graph and relate it to a …
Apr 15, 2025
e32c1f5
added the new clicking feature to Gui_3d
Apr 15, 2025
35a867c
added the new clicking feature to show_window_4d
Apr 15, 2025
f66ca06
basically no modification here
Apr 15, 2025
4eef8dd
added the new clicking feature to graphs
Apr 15, 2025
2bc5265
added a template file for Jupyter Notebook
Apr 15, 2025
b936f05
corrected a small bug related to clearing the graph and leaving the c…
Apr 15, 2025
381f9ca
created a fit panel for a single 1D xarray data
Apr 15, 2025
f5db38b
added an example for the fit panel single
Apr 15, 2025
c688ac1
added a test file for fit panel single
Apr 15, 2025
f29c452
cleaned up a bit
Apr 16, 2025
9a5f4cd
cleaned up tutorial
Apr 16, 2025
8dea3c8
cleaning up additional prints and so on
Apr 16, 2025
ece7604
I add a loading function for nexus files on Main.py
Apr 16, 2025
c87fc1b
fixed the sinusoid fit function
Apr 16, 2025
495a09d
I change the loading function for nexus files to use nxarray
Apr 17, 2025
9c8b12b
added the feature to extract results by right clicking
Apr 17, 2025
c9b8079
added the feature to extract results by right clicking and added extr…
Apr 17, 2025
2f8da32
function for the right click feature
Apr 17, 2025
37a577d
add nxarray dependency
Arora0 Apr 17, 2025
e6995de
modified the whole structure and separated the cursors and dots in an…
Apr 18, 2025
60a85a3
the cursor and dot handlers
Apr 18, 2025
15c8af7
modified accordingly the fit panel since the input array got modified
Apr 18, 2025
14306e8
added the error bars which is fed to graphs
Apr 23, 2025
08120d6
added the error bars and checkboxes to show them, added a cursor for …
Apr 23, 2025
72a07a5
removed an usued block
Apr 23, 2025
eee3402
added cursors for the delay in the energy_time plot from all the time…
Apr 23, 2025
f955c1c
modified the logic a bit by changing the checkbox_changed and deleted…
Apr 23, 2025
5818cb7
added colorscale sliders to the show_4d_window
Apr 24, 2025
7227446
the function for adding a colorscale slider with two handlers for min…
Apr 24, 2025
3e31a22
changed the initialization of the cursors and how they are updated
Apr 25, 2025
4c28ff3
cleaned out some prints
Apr 25, 2025
40b509a
cleaned up
Apr 25, 2025
9607b6b
changed the structure for the 4 plots each its own canvas and added t…
Apr 25, 2025
d483815
added a colorbar
Apr 25, 2025
e26036c
made it more general
Apr 25, 2025
22a458b
added a feature to keep the log scale on when updating the graph with…
Apr 25, 2025
ed8027b
added mpes_tools.
Apr 25, 2025
a47bb54
small clean up
Apr 25, 2025
9d7c1e2
deleted unwanted import
Apr 25, 2025
4e35ccf
added superqt into the dependencies
Apr 25, 2025
0430aff
clean up
Apr 25, 2025
5df5e41
small changes
Apr 25, 2025
2a7e0c3
merged Arpes_gui_nxs into Arpes_gui
Apr 25, 2025
917bd76
added the nxarray that I mistakenly didnt merge earlier
Apr 25, 2025
572a4df
fixed a bug with the cursors for the fit
Apr 25, 2025
70d4d9f
fixed a bug with leaving a touched cell in the table empty
Apr 25, 2025
96d888b
fixed changes suggested in the pull request
May 6, 2025
936dee1
changed the call for Gui_3d
May 6, 2025
762f71f
took out old imports
May 6, 2025
8ff652e
took out old imports and the external call
May 6, 2025
7f76735
deleted unnecessary or unfinished files
May 6, 2025
7aadfd6
deleted unnecessary or unfinished files
May 6, 2025
3258102
bug fix
May 6, 2025
a019a5e
Add download path for example file
rettigl May 7, 2025
1629809
update easy access api
rettigl May 7, 2025
18cfc9a
stored the colorscale variables
May 7, 2025
8c6c7f8
Merge remote-tracking branch 'origin/main' into Arpes_gui
rettigl May 7, 2025
657b3ad
format
rettigl May 7, 2025
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
14 changes: 14 additions & 0 deletions .cspell/custom-dictionary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ARPES
cmap
codemirror
ipython
kernelspec
matplotlib
mpes
nbconvert
nbformat
numpy
nxarray
pygments
pyplot
venv
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

poetry.toml
poetry.toml
*.nxs
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies = [
"numpy>=1.26.1,<2.0",
"PyQt5>=5.0.0",
"xarray>=0.20.2",
"nxarray>=0.4.4",
"superqt >=0.3.0",
]

[project.urls]
Expand Down
871 changes: 468 additions & 403 deletions src/mpes_tools/Gui_3d.py

Large diffs are not rendered by default.

Binary file added src/mpes_tools/METIS.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions src/mpes_tools/Main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QAction, QFileDialog, QSlider, QGridLayout,QHBoxLayout, QSizePolicy,QLabel, QGridLayout, QPushButton, QFileDialog
from PyQt5.QtCore import Qt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
import numpy as np
import h5py
from mpes_tools.Gui_3d import Gui_3d
import xarray as xr
from mpes_tools.hdf5 import load_h5
from mpes_tools.show_4d_window import show_4d_window
import os
from PyQt5.QtGui import QPixmap
import nxarray

class ARPES_Analyser(QMainWindow):
def __init__(self):
super().__init__()

self.setWindowTitle("ARPES_Analyser")
self.setGeometry(100, 100, 400, 300)

# Central widget and main layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QGridLayout()
central_widget.setLayout(layout)
N=256

# Get the directory of the current script
base_path = os.path.dirname(os.path.abspath(__file__))

# Build full path to image files in the same folder
image1_path = os.path.join(base_path, "METIS.png")
image2_path = os.path.join(base_path, "Phoibos.png")

# --- First Button + Image ---
vbox1 = QVBoxLayout()
label_img1 = QLabel()
pixmap1 = QPixmap(image1_path) # 🔁 Replace with your image path
label_img1.setPixmap(pixmap1.scaled(N, N, Qt.KeepAspectRatio, Qt.SmoothTransformation))
label_img1.setAlignment(Qt.AlignCenter)

self.btn_open_h5 = QPushButton("Open File H5")
self.btn_open_h5.clicked.connect(self.open_file_dialoge)

vbox1.addWidget(label_img1)
vbox1.addWidget(self.btn_open_h5)

# --- Second Button + Image ---
vbox2 = QVBoxLayout()
label_img2 = QLabel()
pixmap2 = QPixmap(image2_path) # 🔁 Replace with your image path
label_img2.setPixmap(pixmap2.scaled(N, N, Qt.KeepAspectRatio, Qt.SmoothTransformation))
label_img2.setAlignment(Qt.AlignCenter)

self.btn_open_phoibos = QPushButton("Open File Phoibos")
self.btn_open_phoibos.clicked.connect(self.open_file_phoibos)

vbox2.addWidget(label_img2)
vbox2.addWidget(self.btn_open_phoibos)

# Add the vbox layouts to the main grid layout
layout.addLayout(vbox1, 0, 0)
layout.addLayout(vbox2, 0, 1)

self.graph_windows = []
self.ce = None

self.show()



def open_file_phoibos(self):
# ... existing code ...
file_path, _ = QFileDialog.getOpenFileName(self, "Open File", "", "Data Files (*.npz *.nxs)")
if file_path:
if file_path.endswith('.npz'):
loaded_data = np.load(file_path)
V1 = xr.DataArray(loaded_data['data_array'],
dims=['Angle', 'Ekin','delay'],
coords={'Angle': loaded_data['Angle'],
'Ekin': loaded_data['Ekin'],
'delay': loaded_data['delay']})
elif file_path.endswith('.nxs'):
V1=nxarray.load(file_path)
V1=V1.rename({'angular0':'Angle','energy':'Ekin','delay':'delay'})
V1=V1[list(V1.data_vars)[0]]

axis=[V1['Angle'],V1['Ekin']-21.7,V1['delay']]
V1 = V1.assign_coords(Ekin=V1.coords['Ekin'] -21.7)
graph_window= Gui_3d(V1)
graph_window.show()
self.graph_windows.append(graph_window)

def open_file_dialoge(self):
# Open file dialog to select a .h5 file
file_path, _ = QFileDialog.getOpenFileName(self, "Open hdf5", "", "h5 Files (*.h5)")
print(file_path)
if file_path:
data_array = load_h5(file_path)
graph_4d = show_4d_window(data_array)
graph_4d.show()
self.graph_windows.append(graph_4d)
# self.load_data(data_array)




if __name__ == "__main__":
app = QApplication(sys.argv)
window = ARPES_Analyser()
window.show()
sys.exit(app.exec_())
Binary file added src/mpes_tools/Phoibos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/mpes_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""mpes-tools module easy access APIs."""
import importlib.metadata

from mpes_tools.show_4d_window import MainWindow
from mpes_tools.fit_panel import fit_panel
from mpes_tools.Gui_3d import Gui_3d
from mpes_tools.Main import ARPES_Analyser
from mpes_tools.show_4d_window import show_4d_window

__version__ = importlib.metadata.version("mpes-tools")
__all__ = ["MainWindow"]
__all__ = ["show_4d_window", "Gui_3d", "fit_panel", "ARPES_Analyser"]
44 changes: 0 additions & 44 deletions src/mpes_tools/color_scale.py

This file was deleted.

80 changes: 80 additions & 0 deletions src/mpes_tools/colorscale_slider_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel,QHBoxLayout,QGridLayout
from superqt import QRangeSlider
from PyQt5.QtCore import Qt
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys

class colorscale_slider(QWidget):
def __init__(self, layout, imshow_artist,canvas, limits=None):
super().__init__()
self.case=False
self.im = imshow_artist
self.canvas = canvas
self.colorbar = None # Optional: set this externally if you want to update a colorbar
if limits is None:
self.data = imshow_artist.get_array().data
self.vmin, self.vmax = float(np.min(self.data)), float(np.max(self.data))
else:
self.vmin,self.vmax= limits
if self.vmin==self.vmax:
self.vmax += 0.1
if self.vmax<10:
self.cmin, self.cmax = 10, 1e9
self.case=True
else:
self.cmin, self.cmax=self.vmin,self.vmax
Comment on lines +23 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here? I don't understand this.

# Slider Widget
slider_widget = QWidget()
slider_layout = QVBoxLayout(slider_widget)

self.slider = QRangeSlider(Qt.Vertical)
self.slider.setFixedWidth(15)
self.slider.setMinimum(int(1 * self.cmin))
self.slider.setMaximum(int(1.5* self.cmax))
self.slider.setValue([float(self.vmin),float(self.vmax)])
if self.case :
self.slider.setValue([self.new_values(self.vmin), self.new_values(self.vmax)])
Comment on lines +37 to +38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. Please explain, and give meaningful variable names

self.slider.valueChanged.connect(lambda value: self.update_clim(value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this lambda function, can't you use the function directly?


# self.vmin_label = QLabel(f"{self.vmin:.2e}")
# self.vmax_label = QLabel(f"{self.vmax:.2e}")
self.vmin_label = QLabel("")
self.vmax_label = QLabel("")
slider_layout.addWidget(self.vmax_label)
slider_layout.addWidget(self.slider)
slider_layout.addWidget(self.vmin_label)

# New horizontal layout: slider left, canvas right
h_container = QWidget()
h_layout = QHBoxLayout(h_container)
h_layout.addWidget(slider_widget)
h_layout.addWidget(self.canvas)
h_layout.addWidget(self.canvas, stretch=1)
if isinstance(layout, QGridLayout):
layout.addWidget(h_container,0,0)
else:
layout.insertWidget(0, h_container)


def new_values(self, x):
a = (self.cmax - self.cmin) / (self.vmax - self.vmin)
b = self.vmax * self.cmin - self.vmin * self.cmax
return int(a * x + b)

def inverse(self, x):
a = (self.cmax - self.cmin) / (self.vmax - self.vmin)
b = self.vmax * self.cmin - self.vmin * self.cmax
return (x - b) / a

def update_clim(self, value):
vmin, vmax = value
if self.case:
vmin, vmax = self.inverse(value[0]), self.inverse(value[1])
self.im.set_clim(vmin, vmax)
# self.vmin_label.setText(f" {vmin:.2e}")
# self.vmax_label.setText(f"{vmax:.2e}")
if self.colorbar:
self.colorbar.update_normal(self.im)
self.canvas.draw_idle()
29 changes: 29 additions & 0 deletions src/mpes_tools/cursor_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class Cursor_handler:
def __init__(self,fig,ax, artist, changes=None,parent=None):
self.artist=artist
self.active_cursor=None
self.changes=changes
self.parent = parent
self.ax=ax
self.fig=fig
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.fig.canvas.mpl_connect('button_release_event', self.on_release)
def on_pick(self,event): # function to pick up the cursors or the dots
if event.artist == self.artist and self.parent.active_handler is None:
self.active_cursor = self.artist
self.parent.active_handler = self
def on_motion(self,event): # function to move the cursors or the dots
if self.active_cursor is not None and event.inaxes == self.ax:
if self.active_cursor == self.artist:
if self.artist.get_xdata()[0]==self.artist.get_xdata()[1]:
self.artist.set_xdata([event.xdata, event.xdata])
self.changes()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work, if changes=None?
Also, please give meaningful names

else :
self.artist.set_ydata([event.ydata, event.ydata])
self.changes()
def on_release(self,event):
self.active_cursor = None
self.parent.active_handler = None


22 changes: 22 additions & 0 deletions src/mpes_tools/dot_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Dot_handler:
def __init__(self,fig,ax, artist, changes=None):
self.artist=artist
self.active_cursor=None
self.changes=changes
self.ax=ax
self.fig=fig
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.fig.canvas.mpl_connect('button_release_event', self.on_release)
Comment on lines +8 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you connect these handlers in each instance, this means you will have a lot of handlers connected to each event happening on the canvas. I am wondering if this might slow things down, compared to only one global handler per event type.

def on_pick(self,event): # function to pick up the cursors or the dots
if event.artist == self.artist:
self.active_cursor = self.artist
def on_motion(self,event): # function to move the cursors or the dots
if self.active_cursor is not None and event.inaxes == self.ax:
if self.active_cursor == self.artist:
self.artist.center= (event.xdata, event.ydata)
self.changes()
def on_release(self,event):
self.active_cursor = None


Loading