diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index 9bd924d..6726034 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -1,8 +1,166 @@ # Import packages -import numpy as np -import time as ttime import os -import glob +import sys +from pathlib import Path + +from api import (get_current_scanid, check_inputs, xrf_loop, loop_sleep) + +from PyQt5 import QtWidgets +from PyQt5 import uic +from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtWidgets import QFileDialog + +# For Hi-DPI monitors +QtWidgets.QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) +QtWidgets.QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) + + +class MainWindow(QtWidgets.QMainWindow): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + path = Path(__file__).parent + uic.loadUi(path / "gui/main_form.ui", self) + + self.label_logo.setProperty("pixmap", path / "gui/5-ID_TopAlign.png") + self.setProperty("windowIcon", path / "gui/5-ID_TopAlign.png") + + self.pushButton_stop.setProperty("enabled", False) + self.setContentsMargins(20, 0, 20, 20) + + self.pushButton_currentid.released.connect(self.update_scanid) + self.pushButton_plus1.released.connect(self.update_scanid_plus1) + self.pushButton_browse.released.connect(self.get_dir) + self.pushButton_start.released.connect(self.start_loop) + self.pushButton_stop.released.connect(self.stop_loop) + + def update_scanid(self): + self.lineEdit_startid.setProperty("text", str(get_current_scanid())) + return + + def update_scanid_plus1(self): + self.lineEdit_startid.setProperty("text", str(get_current_scanid()+1)) + return + + def get_dir(self): + dialog = QFileDialog() + dialog.setFileMode(QFileDialog.DirectoryOnly) + + folder = dialog.getExistingDirectory(self, 'Save Location', str(Path.home())) + if folder != "": + if folder[-1] != "/" and folder[-1] != "\\": + folder += os.sep + self.lineEdit_savelocation.setProperty("text", folder) + return + + def get_scan_parameters(self): + self.start_id = int(self.lineEdit_startid.text()) + self.wd = self.lineEdit_savelocation.text() + self.N = int(self.lineEdit_numscan.text()) + self.dt = int(self.lineEdit_delay.text()) + + def set_scan_parameters(self): + self.lineEdit_savelocation.setProperty("text", self.wd) + self.lineEdit_startid.setProperty("text", str(self.start_id)) + self.lineEdit_numscan.setProperty("text", str(self.N)) + self.lineEdit_delay.setProperty("text", str(self.dt)) + + def lock_widgets(self, value): + self.lineEdit_savelocation.setProperty("enabled", value) + self.lineEdit_startid.setProperty("enabled", value) + self.lineEdit_numscan.setProperty("enabled", value) + self.lineEdit_delay.setProperty("enabled", value) + self.pushButton_browse.setProperty("enabled", value) + self.pushButton_currentid.setProperty("enabled", value) + self.pushButton_plus1.setProperty("enabled", value) + + def start_loop(self): + # Check for a thread running the main loop + try: + if self.th.isRunning is True: + self.th.stop() + except AttributeError: + self.th = Tloop(self) + + # Check the scan parameters + self.get_scan_parameters() + tmp = check_inputs(self.start_id, self.wd, self.N, self.dt) + self.start_id = tmp[0] + self.wd = tmp[1] + self.N = tmp[2] + self.dt = tmp[3] + # print(self.start_id, self.wd, self.N, self.dt) + self.set_scan_parameters() + + # Change to the proper working directory + try: + os.chdir(self.wd) + except FileNotFoundError as ex: + self.label_status.setProperty("text", str(ex)) + print(ex) + return + + # Start the thread + self.pushButton_stop.setProperty("enabled", True) + self.pushButton_start.setProperty("text", "Force Restart") + self.lock_widgets(False) + self.th.start() + return + + def stop_loop(self): + try: + self.th.stop() + self.pushButton_stop.setProperty("enabled", False) + self.pushButton_start.setProperty("text", "Start") + self.lock_widgets(True) + except AttributeError: + pass + return + + def update_progress(self, x): + self.progressBar.setProperty("value", x) + return + + def update_status(self, x): + self.label_status.setProperty("text", x) + return + + +class Tloop(QThread): + signal_update_progressBar = pyqtSignal(float) + signal_update_status = pyqtSignal(str) + DT = 0.01 # sleep time + + def __init__(self, form): + super(QThread, self).__init__() + self.form = form + self.isRunning = False + self.signal_update_progressBar.connect(self.form.update_progress) + self.signal_update_status.connect(self.form.update_status) + + def __del__(self): + self.isRunning = False + self.wait() + + def stop(self): + self.__del__() + + def run(self): + self.isRunning = True + + try: + while self.isRunning: + xrf_loop(self.form.start_id, self.form.N, gui=self) + loop_sleep(self.form.dt, gui=self) + except KeyboardInterrupt: + print("\n\nStopping SRX Autosave loop.") + pass + + +app = QtWidgets.QApplication(sys.argv) +window = MainWindow() +window.show() +app.exec_() # %% Main loop for XRF maps -> HDF5 @@ -33,82 +191,16 @@ def autosave_xrf(start_id, wd="", N=1000, dt=60): >>> autosave_xrf(1234, wd='/home/xf05id1/current_user_data/, N=1000, dt=60) """ - # Check the input parameters - if start_id < 0: - # Need to add 1 to scan ID - # Otherwise it will grab the current value (which would be from the previous user) - start_id = get_current_scanid() + 1 - print(f"Using startind scan ID: {start_id}") - if wd == "": - wd = os.getcwd() - print(f"Using current directory.\n{wd}") + (start_id, wd, N, dt) = check_inputs(start_id, wd, N, dt) os.chdir(wd) - if N < 1: - # Add logic later that if N < 1, then always use current scan ID - print("Warning: N changed to 100.") - N = 100 - if dt < 1: - print("Warning: dt changed to 1 second.") - dt = 1 print("--------------------------------------------------") - # Build the possible list of scan IDs and filenames - num = np.arange(start_id, start_id + N, 1) - # Maybe a function for this - # make_XRF_fn(scan_id) -> return 'scan2D_{scanid}.h5' - # filelist_h5 = ['scan2D_' + str(n) + '.h5' for n in num] - - # Enter the main loop - def mainloop(): - for i in range(N): - # Check if the scan ID exists - # We could make a function to check if current scan ID - # >= this value. Or same thing but return True/False - scanid = int(num[i]) - try: - h = db[scanid] - except Exception: - print(f"{scanid} does not exist!") - break - - # Output to command line that we are on a given scan - print(scanid, end="\t", flush=True) - print(h.start["plan_name"], end="\t", flush=True) # This might change - - # Check if fly scan - # Should be more generic, if XRF scan - if h.start["plan_name"] == "scan_and_fly": - # fname = filelist_h5[i] - # Check if the file noes not exist - # if not os.path.isfile(fname): - if not glob.glob(f"scan2D_{scanid}_*.h5") and not os.path.isfile( - f"scan2D_{scanid}.h5" - ): - # Check if the scan is done - try: - # db[scanid].stop['time'] - make_hdf(scanid, completed_scans_only=True) - except Exception: - pass - else: - print("XRF HDF5 already created.") - else: - print() - - print("\nSleeping for %d seconds...Press Ctrl-C to exit" % (dt), flush=True) - t0 = ttime.monotonic() - del_t = 0.0 - while del_t < dt: - print(" %02d seconds remaining..." % (dt - del_t), end="\r", flush=True) - ttime.sleep(0.5) - del_t = ttime.monotonic() - t0 - print("--------------------------------------------------") - try: while True: - mainloop() + xrf_loop(start_id, N) + loop_sleep(dt) except KeyboardInterrupt: print("\n\nExiting SRX AutoSave.") pass diff --git a/srx_autosave/api.py b/srx_autosave/api.py index 301146e..ccbba7b 100644 --- a/srx_autosave/api.py +++ b/srx_autosave/api.py @@ -1,9 +1,20 @@ # Import packages import numpy as np import time as ttime +import os +import glob from databroker import Broker -# from pyxrf.api import * +try: + from pyxrf.api import * +except ImportError: + print("Error importing pyXRF. Continuing without import.") + +try: + from epics import caget +except ImportError: + print("Error importing caget. Continuing without import.") + """ SRX Autosave APIs @@ -11,12 +22,15 @@ Helper functions for SRX Autosave Andy Kiss ------------------------------------------------------------------------ """ # Register the data broker -db = Broker.named("srx") +try: + db = Broker.named("srx") +except AttributeError: + db = Broker.named("temp") + print("Using temporary databroker.") # ---------------------------------------------------------------------- @@ -75,7 +89,11 @@ def _get_current_scanid_db(): The current scan ID """ - return db[-1].start["scan_id"] + try: + x = db[-1].start["scan_id"] + except IndexError: + x = 1 + return x def _get_current_scanid_pv(): @@ -103,3 +121,102 @@ def _get_current_scanid_pv(): # # db.add_filter(user='Andy') # hdr = db(saf, cycle) # return hdr + + +def check_inputs(start_id, wd, N, dt): + # Check the input parameters + if start_id < 0: + # Need to add 1 to scan ID + # Otherwise it will grab the current value (which would be from the previous user) + start_id = get_current_scanid() + 1 + print(f"Using starting scan ID: {start_id}") + + if wd == "": + wd = os.getcwd() + print(f"Using current directory.\n{wd}") + + if N < 1: + # Add logic later that if N < 1, then always use current scan ID + print("Warning: N changed to 100.") + N = 100 + + if dt < 1: + print("Warning: dt changed to 1 second.") + dt = 1 + + return (start_id, wd, N, dt) + + +def xrf_loop(start_id, N, gui=None): + num = np.arange(start_id, start_id + N, 1) + for i in range(N): + # Check if the scan ID exists + # We could make a function to check if current scan ID + # >= this value. Or same thing but return True/False + scanid = int(num[i]) + + if gui is not None: + gui.signal_update_status.emit(f"Making {scanid}...") + + if gui.isRunning is False: + gui.signal_update_status.emit(f"SRX Autosave stopped.") + gui.signal_update_progressBar.emit(0) + return + + try: + h = db[scanid] + except Exception: + print(f"{scanid} does not exist!") + break + + # Output to command line that we are on a given scan + print(scanid, end="\t", flush=True) + print(h.start["plan_name"], end="\t", flush=True) # This might change + + # Check if fly scan + # Should be more generic, if XRF scan + if h.start["plan_name"] == "scan_and_fly": + # fname = filelist_h5[i] + # Check if the file noes not exist + # if not os.path.isfile(fname): + if not glob.glob(f"scan2D_{scanid}_*.h5") and not os.path.isfile( + f"scan2D_{scanid}.h5" + ): + # Check if the scan is done + try: + # db[scanid].stop['time'] + make_hdf(scanid, completed_scans_only=True) + except Exception as ex: + print(ex) + pass + else: + print("XRF HDF5 already created.") + else: + print() + return + + +def loop_sleep(dt, gui=None): + if gui is not None: + DT = gui.DT + else: + DT = 0.5 + + print("\nSleeping for %d seconds...Press Ctrl-C to exit" % (dt), flush=True) + t0 = ttime.monotonic() + del_t = 0.0 + while del_t < dt: + str_status = "%02d seconds remaining..." % (dt - del_t) + print(" %s" % str_status, end="\r", flush=True) + if gui is not None: + gui.signal_update_progressBar.emit(100 * del_t / dt) + gui.signal_update_status.emit(str_status) + if gui.isRunning is False: + print('SRX Autosave stopped.') + gui.signal_update_status.emit('SRX Autosave stopped.') + gui.signal_update_progressBar.emit(0) + break + ttime.sleep(DT) + del_t = ttime.monotonic() - t0 + print("--------------------------------------------------") + return diff --git a/srx_autosave/gui/5-ID_TopAlign.png b/srx_autosave/gui/5-ID_TopAlign.png new file mode 100644 index 0000000..ae8b7a8 Binary files /dev/null and b/srx_autosave/gui/5-ID_TopAlign.png differ diff --git a/srx_autosave/gui/main_form.ui b/srx_autosave/gui/main_form.ui new file mode 100644 index 0000000..843d146 --- /dev/null +++ b/srx_autosave/gui/main_form.ui @@ -0,0 +1,402 @@ + + + MainWindow + + + + 0 + 0 + 556 + 383 + + + + SRX Autosave + + + + 5-ID_TopAlign.png5-ID_TopAlign.png + + + + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 256 + 256 + + + + + 0 + 0 + + + + + 128 + 128 + + + + + + + 5-ID_TopAlign.png + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + + Arial + 24 + 75 + true + + + + SRX Autosave + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + + Arial + + + + version: + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + Arial + 12 + + + + Save Location: + + + 0 + + + + + + + + Arial + 8 + + + + /home/xf05id1/current_user_data/ + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Arial + 12 + + + + Browse + + + + + + + + Arial + 12 + + + + Starting Scan ID: + + + 0 + + + + + + + + Arial + 8 + + + + -1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Arial + 12 + + + + Get Current Scan ID + + + + + + + + 12 + + + + +1 + + + + + + + + + + Arial + 12 + + + + Number of Scans: + + + 0 + + + + + + + + Arial + 8 + + + + 100 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Arial + 12 + + + + Scan Delay [s]: + + + 0 + + + + + + + + Arial + 8 + + + + 60 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + Arial + 12 + + + + Welcome to SRX Autosave! + + + + + + + + Arial + 12 + + + + 0 + + + false + + + false + + + + + + + + + + Arial + 12 + + + + Start + + + + + + + + Arial + 12 + + + + Stop + + + + + + + + + + +