From 60c2b5d6c19e67314746bd528ff887247c66bf55 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 27 Aug 2024 14:00:18 -0500 Subject: [PATCH 01/47] 2D detector image can zoom in/out --- src/firefly/area_detector_overlay.ui | 92 ++++++++++++++++++++++------ src/firefly/area_detector_roi.ui | 49 +++++++++++++-- 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index c322d15a..5346576f 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -31,37 +31,37 @@ - - + + - Show: + Shape: - - + + - haven://${AD}.overlays.overlay_${OV}.use - - - - - - - Shape: + haven://${AD}.overlays.overlay_${OV}.shape - - + + - haven://${AD}.overlays.overlay_${OV}.shape + haven://${AD}.overlays.overlay_${OV}.use + + + + + + + Show: @@ -71,6 +71,15 @@ + + true + + + false + + + false + @@ -80,6 +89,15 @@ Qt::Vertical + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -95,15 +113,30 @@ - + + + false + + + [] + haven://${AD}.overlays.overlay_${OV}.position_x - - 0 + + true + + + -1.000000000000000 + + + 1.000000000000000 + + + 101 @@ -137,6 +170,15 @@ Qt::Vertical + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -159,6 +201,15 @@ haven://${AD}.overlays.overlay_${OV}.size_x + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -194,7 +245,8 @@ PyDMSlider QFrame -
pydm.widgets.slider
+
pydm.widgets.slider
+ 1
diff --git a/src/firefly/area_detector_roi.ui b/src/firefly/area_detector_roi.ui index 1f4f834a..d1bfcb6f 100644 --- a/src/firefly/area_detector_roi.ui +++ b/src/firefly/area_detector_roi.ui @@ -149,6 +149,15 @@ Qt::Vertical + + 1.000000000000000 + + + -1.000000000000000 + + + true +
@@ -174,6 +183,15 @@ 0 + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -206,6 +224,15 @@ Qt::Vertical + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -228,6 +255,15 @@ haven://${AD}.roi${R}.size.x + + 1.000000000000000 + + + -1.000000000000000 + + + true + @@ -250,11 +286,6 @@ - - PyDMLabel - QLabel -
pydm.widgets.label
-
PyDMCheckbox QCheckBox @@ -263,7 +294,13 @@ PyDMSlider QFrame -
pydm.widgets.slider
+
pydm.widgets.slider
+ 1 +
+ + PyDMLabel + QLabel +
pydm.widgets.label
From 05939c8deca732dcdcb310919ed2ec46a4511869 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 27 Aug 2024 22:05:56 -0500 Subject: [PATCH 02/47] Update robot GUI --- src/firefly/robot.ui | 364 ++++++++++++++++++++++++------------------- 1 file changed, 201 insertions(+), 163 deletions(-) diff --git a/src/firefly/robot.ui b/src/firefly/robot.ui index b9c5c6a7..52389708 100644 --- a/src/firefly/robot.ui +++ b/src/firefly/robot.ui @@ -6,8 +6,8 @@ 0 0 - 408 - 270 + 410 + 359 @@ -15,173 +15,211 @@ - - - Sample Holder Numbers + + + + + Sample Holder Numbers + + + + + + + + + Current: + + + + + + + + + + 0 + + + false + + + true + + + false + + + true + + + + + + haven://${DEVICE}.current_sample + + + false + + + + + + + Qt::Horizontal + + + + 490 + 52 + + + + + + + + + 0 + 0 + + + + Sample holder + + + + + + + + + + Qt::Horizontal + + + + 490 + 52 + + + + + + + + + + + + Qt::Horizontal - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Current: - - - - - - - - - - 0 - - - false - - - true - - - false - - - true - - - - - - haven://${DEVICE}.current_sample - - - false - - - PyDMLabel::String - - - - - - - Qt::Horizontal - - - - 490 - 52 - - - - - - - - - 0 - 0 - - - - Sample holder - - - - - - - - - - Qt::Horizontal - - - - 490 - 52 - - - - - - - - - - Motors - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Num. Motors - - - - - - - true - - - false - - - false - - - QAbstractSpinBox::CorrectToNearestValue - - - 0 - - - 0 - - - 10 + + + + + + + + 0 + 0 + + + + Num. Motors + + + + + + + true + + + false + + + false + + + QAbstractSpinBox::CorrectToNearestValue + + + 0 + + + 0 + + + 10 + + + + + + + Qt::Horizontal + + + + 490 + 52 + + + + + + + + + + + + true + + + + + 0 + 0 + 386 + 148 + + + + + + 3 + + + 3 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - - - - - Qt::Horizontal - - - - 490 - 52 - - - - - - - - - - - + + + + + From 7b781d624a76e80faf4a7bea5f1c8e4312c27ca1 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Fri, 30 Aug 2024 11:37:52 -0500 Subject: [PATCH 03/47] Add functions to estimate data_noise --- src/firefly/data_noise.py | 251 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 src/firefly/data_noise.py diff --git a/src/firefly/data_noise.py b/src/firefly/data_noise.py new file mode 100644 index 00000000..0e13512a --- /dev/null +++ b/src/firefly/data_noise.py @@ -0,0 +1,251 @@ +# larch imports +from larch.xafs import pre_edge, autobk, sort_xafs, xftf, estimate_noise +from larch.io import read_ascii +from larch.plot.plotly_xafsplots import * + +# General imports +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import os +import copy + +# Set the default font for the entire plot +mpl.rcParams['font.family'] = 'Arial' + + +# Constant +# for epsilon_r method +r_window_size = 30 # this can be changed depending on the data points. +noise_factor=0.2 # not sure now +# for random noise method +average_num = 5 # smoothing data using average of 5 data points +random_noise_window_size = 10 # this can be changed depending on the data points. + + +def find_closest_index(array, target_value): + """ + Finds the index of the element in a NumPy array that is closest to the target_value. + + Parameters: + - array (numpy.ndarray): The NumPy array of numbers to search. + - target_value (float): The value to find the closest match for. + + Returns: + - int: The index of the element closest to the target_value. + """ + closest_index = np.abs(array - target_value).argmin() + return closest_index + +def find_smoothest_range(data, window_size): + """ + Finds the smoothest range in an oscillation curve based on variance. + Parameters: + - data: 1D array or list representing the oscillation curve. + - window_size: Size of the window for calculating variance. + + Returns: + - Start and end indices of the smoothest range. + """ + window_size = int(window_size) # Ensure window_size is an integer + variances = np.zeros(len(data) - window_size + 1) + for i in range(len(variances)): + variances[i] = np.var(data[i:i+window_size]) + smoothest_index = np.argmin(variances) + start_index = smoothest_index + end_index = smoothest_index + window_size - 1 + return start_index, end_index + +def load_data(data_path): + """ + From this step, the data including all the Larch processes, eg. data.r and data.chi_r will automatically created in the data group after xftf process + """ + data = read_ascii(data_path, labels='col1, col2') + data.is_frozen = False + data.datatype = 'xas' + data.xdat = data.data[0, : ] + data.ydat = data.data[1, : ]/1.0 + data.yerr = 1.0 + data.energy = data.xdat + data.mu = data.ydat + sort_xafs(data, overwrite=True, fix_repeats=True) + data.filename = data_path.split(os.sep)[-1] + return data + +def data_nor_bg(data, figure=True): + # Normalization + pre_edge(data, pre1=-100, pre2=-20.00, + nvict=0, nnorm=2, norm1=50.00, norm2=1000) + # Background function + autobk(data, + rbkg= 1.0, + ek0= data.e0, + kmin= 0.000, kmax= 30, kweight= 2.0, + clamp_lo= 0, clamp_hi= 0) + + if figure: + fig1 = plot_mu(data, show_norm=False, show_deriv=False, + show_pre=True, show_post=True, show_e0=True, with_deriv=False, + emin=None, emax=None, label=f'mu_nor_bg',offset=0,title=None, fig= None, show=True) + fig1.set_style(width=800, height=350) + indice = np.where(data.energy>data.ek0)[0][0] + fig1.add_plot([data.energy[indice]], [data.mu[indice]], label='ek0', color='red',linewidth=0, marker=dict(size=5)) + fig1.show() + plot_bkg(data, label=f'bkg_nor_bg') + return data + +def scan_num_epsilon_r(folder, data_list, figure=True): + """ + Finds the scan number while the noise is smaller than noise_factor*(the 1st scan data noise) + Here the noise_factor is 0.2, which is not sure now. + Parameters: + - folder: the folder saving all the data. + - data_list: a data list of data with one scan, two scan merged, three scan merged... + - figure: whether plots are shown in the noise estimate process + + Returns: + - scan number that is good for measurement + """ + + epsilon_r_list=[] + + data_path = folder+data_list[0][0] + data = load_data(data_path) + datacopy=copy.deepcopy(data) + data_nor_bg(datacopy, figure=False) + xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 3.000, window='Hanning', rmax_out=30.000) # k weight should be 3 to magnifize the noise part. + start_index, end_index = find_smoothest_range(datacopy.chir_re, r_window_size) + r_start_noise = datacopy.r[start_index] + r_end_noise = datacopy.r[end_index] + + + for n in range(len(data_list)): + data_path = folder+data_list[n][0] + print(data_path) + data = load_data(data_path) + datacopy=copy.deepcopy(data) + data_nor_bg(datacopy, figure=False) + + xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 3.000, window='Hanning', rmax_out=30.000) + + estimate_noise(datacopy.k, datacopy.chi, group=datacopy, rmin=r_start_noise, rmax=r_end_noise, kweight=3, kmin=3, kmax=12, dk=1, kwindow='Hanning')#, dk2=None, kstep=0.05, nfft=2048, _larch=None) + epsilon_r=datacopy.epsilon_r + print(epsilon_r) + epsilon_r_list.append(epsilon_r) + + noise = list(zip(range(len(data_list)), epsilon_r_list)) + if len(noise)==1: + scan_num = noise[0][0] + else: + for i in range(1, len(noise)): + current_value = noise[i][1] + first_value = noise[0][1] + if current_value < noise_factor * first_value: + scan_num = noise[i - 1][0] + else: + scan_num = noise[-1][0] + + if figure: + plt.figure() + plt.plot(range(len(data_list)), epsilon_r_list) + plt.xlabel('scan number') + plt.ylabel('epsilon_r') + + return scan_num + +def scan_num_random_noise(folder, data_list, figure=True): + """ + The random noise method follows Shelly D. Kelly Chapter14 Analysis of Soils and Minerals Using X-ray Absorption SPectroscopy. + """ + random_noise_list=[] + + # find a region to estimate the random noise. The region should not be affected by a sudden drop of data points at the peak wings. So the function find_smoothest_range is used. + data_path = folder+data_list[0][0] + data = load_data(data_path) + datacopy=copy.deepcopy(data) + data_nor_bg(datacopy, figure=False) + xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 2.000, window='Hanning', rmax_out=30.000) + start_index = find_closest_index(datacopy.k, 9) + end_index = find_closest_index(datacopy.k, 11) + # Filter the data to get only the rows where k is in the range [9, 11] + filtered_data = (datacopy.k[start_index:end_index])**2*datacopy.chi[start_index:end_index] + data_series = pd.Series(filtered_data) + # Apply a moving average to estimate the signal + smoothed_signal = data_series.rolling(window=average_num, center=True).mean() + # Fill any NaN values that result from the moving average + smoothed_signal = smoothed_signal.fillna(method='bfill').fillna(method='ffill') + # Estimate noise as the difference between the original data and the smoothed signal + noise = data_series - smoothed_signal + start_index_noise, end_index_noise=find_smoothest_range(noise, random_noise_window_size) + + for n in range(len(data_list)): + data_path = folder+data_list[n][0] + print(data_path) + data = load_data(data_path) + datacopy=copy.deepcopy(data) + data_nor_bg(datacopy, figure=False) + + xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 2.000, window='Hanning', rmax_out=30.000) + if figure: + plt.figure() + plt.subplot(2, 1, 1) + plt.plot(datacopy.k, (datacopy.k)**2*datacopy.chi, label=data_path) + plt.axvspan(9, 11, color='yellow', alpha=0.3) + plt.xlabel('k (Å^(-1))') + plt.xlim(0,np.max(datacopy.k)) + plt.ylabel('$k^2\chi(k)$ ($Å^{-2}$)') + plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + + plt.subplot(2, 1, 2) + plt.plot(datacopy.k, (datacopy.k)**2*datacopy.chi) + plt.xlabel('k (Å^(-1))') + plt.xlim(9,11) + plt.ylabel('$k^2\chi(k)$ ($Å^{-2}$)') + plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + + start_index = find_closest_index(datacopy.k, 9) + end_index = find_closest_index(datacopy.k, 11) + filtered_data = (datacopy.k[start_index:end_index])**2*datacopy.chi[start_index:end_index] + data_series = pd.Series(filtered_data) + smoothed_signal = data_series.rolling(window=average_num, center=True).mean() + smoothed_signal = smoothed_signal.fillna(method='bfill').fillna(method='ffill') + noise = data_series - smoothed_signal + noise_flat = noise[start_index_noise:end_index_noise] + + if figure: + plt.figure(figsize=(8,8)) + plt.subplot(2, 1, 1) + plt.plot(np.arange(len(smoothed_signal)), smoothed_signal, 'r.-') + plt.title("Smoothed data averaged by 5 data poins" ) + + plt.subplot(2, 1, 2) + plt.plot(np.arange(len(noise)), noise, 'r.-') + plt.axvspan(start_index_noise, end_index_noise, color='c', alpha=0.5) + plt.title("Random noise and the region for the final standard deviation" ) + + noise_std = np.std(noise_flat) + print(noise_std) + random_noise_list.append(noise_std) + + noise = list(zip(range(len(data_list)), random_noise_list)) + if len(noise)==1: + scan_num = noise[0][0] + else: + for i in range(1, len(noise)): + current_value = noise[i][1] + if current_value < 0.1: + scan_num = noise[i][0] + break + else: + scan_num = noise[-1][0] + + if figure: + plt.figure() + plt.plot(range(len(data_list)), random_noise_list) + plt.xlabel('scan number') + plt.ylabel('random noise (%)') + + return scan_num + + From 2b1af70adb2703dc87e31034634aed78cabc027c Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 5 Sep 2024 12:46:56 -0500 Subject: [PATCH 04/47] Set limits for PyDMSlider --- src/firefly/area_detector_overlay.ui | 20 ++++++++++---------- src/firefly/area_detector_roi.ui | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index 5346576f..025b4a05 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -87,13 +87,13 @@ haven://${AD}.overlays.overlay_${OV}.position_y - Qt::Vertical + 2 - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true @@ -130,10 +130,10 @@ true - -1.000000000000000 + 0.000000000000000 - 1.000000000000000 + 3000.000000000000000 101 @@ -168,13 +168,13 @@ haven://${AD}.overlays.overlay_${OV}.size_y - Qt::Vertical + 2 - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true @@ -202,10 +202,10 @@ haven://${AD}.overlays.overlay_${OV}.size_x - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true diff --git a/src/firefly/area_detector_roi.ui b/src/firefly/area_detector_roi.ui index d1bfcb6f..de05f496 100644 --- a/src/firefly/area_detector_roi.ui +++ b/src/firefly/area_detector_roi.ui @@ -147,13 +147,13 @@ haven://${AD}.roi${R}.min_xyz.min_y - Qt::Vertical + 2 - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true @@ -184,10 +184,10 @@ 0 - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true @@ -222,13 +222,13 @@ haven://${AD}.roi${R}.size.y - Qt::Vertical + 2 - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true @@ -256,10 +256,10 @@ haven://${AD}.roi${R}.size.x - 1.000000000000000 + 3000.000000000000000 - -1.000000000000000 + 0.000000000000000 true From b2340fc22b0aedb10ad18e7c2f778639c9e6a565 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 21 Jan 2025 10:44:06 -0600 Subject: [PATCH 05/47] Update exposure time and gain and overlay line width. --- src/firefly/area_detector_overlay.ui | 98 +++++-- src/firefly/area_detector_viewer.ui | 372 +++++++++++---------------- src/firefly/plans/line_scan.ui | 2 +- 3 files changed, 226 insertions(+), 246 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index 025b4a05..165f7e06 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -7,7 +7,7 @@ 0 0 263 - 181 + 215 @@ -31,20 +31,10 @@ - - + + - Shape: - - - - - - - - - - haven://${AD}.overlays.overlay_${OV}.shape + Show: @@ -58,10 +48,20 @@ - - + + - Show: + Shape: + + + + + + + + + + haven://${AD}.overlays.overlay_${OV}.shape @@ -229,9 +229,67 @@ + + + + + + + + Width + + + Qt::AlignCenter + + + + + + + + + + + + 1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + haven://${AD}.overlayes.overlay_${OV}.WidthX + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PyDMLabel + QLabel +
pydm.widgets.label
+
+ + PyDMSlider + QFrame +
pydm.widgets.slider
+ 1 +
PyDMCheckbox QCheckBox @@ -242,12 +300,6 @@ QComboBox
pydm.widgets.enum_combo_box
- - PyDMSlider - QFrame -
pydm.widgets.slider
- 1 -
diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index 4dc59644..5ead0e0c 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -103,17 +103,114 @@ - - + + + + + + + + + haven://${AD}.drv.acquire_time + + + + + + + s + + + Qt::AlignCenter + + + + + + + Continuous + + + haven://${AD}.drv.acquire_time_auto + + + + + + + - Shape: + Gain: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + + + + + + haven://${AD}.drv.gain + + + + + + + + + + Qt::AlignCenter + + + + + + + Continuous + + + haven://${AD}.drv.gain_auto + + + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + + haven://${AD}.drv.DetectorState_RBV + + + + 0 @@ -130,7 +227,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - haven://${AD}.cam.array_size.array_size_y + haven://${AD}.hdf.array_size_y @@ -153,7 +250,7 @@ 1224 - haven://${AD}.cam.array_size.array_size_x + haven://${AD}.hdf.array_size_x @@ -172,20 +269,10 @@
- - - - Exposure: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - + + - Gain: + Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -193,37 +280,9 @@ - - - - - false - - - - - - [] - - - haven://${AD}.cam.gain - - - true - - - 0.000000000000000 - - - 20.000000000000000 - - - 101 - - - + - + @@ -231,7 +290,7 @@ Auto - haven://${AD}.cam.gain_auto + haven://${AD}.drv.acquire_time_auto 1 @@ -240,35 +299,40 @@ - - - - - - false - - - - - - [] - - - haven://${AD}.cam.acquire_time - - - true - - - 0.000000000000000 - - - 0.200000000000000 - - - + + + + State: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Exposure: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + - + @@ -276,7 +340,7 @@ Auto - haven://${AD}.cam.acquire_time_auto + haven://${AD}.drv.gain_auto 1 @@ -307,7 +371,7 @@ false - haven://${AD}.cam.acquire + haven://${AD}.drv.acquire false @@ -350,7 +414,7 @@ [{"name": "enable_acquire_buttons", "property": "Enable", "initial_value": "", "expression": "ch[0] == 1", "channels": [{"channel": "${PREFIX}cam1:Acquire", "trigger": true, "use_enum": false}]}] - haven://${AD}.cam.acquire + haven://${AD}.drv.acquire 0 @@ -473,141 +537,6 @@ - - - - ROIs - - - - - - - - - - - - QTabWidget::South - - - QTabWidget::Rounded - - - 3 - - - false - - - - - - - - - - - - - - - - Full - - - - - - - - - QFrame::NoFrame - - - AD=${AD},R=4 - - - area_detector_roi.ui - - - - - - - - 1 - - - - - - - - - QFrame::NoFrame - - - AD=${AD},R=1 - - - area_detector_roi.ui - - - - - - - - 2 - - - - - - - - - QFrame::NoFrame - - - AD=${AD},R=2 - - - area_detector_roi.ui - - - - - - - - 3 - - - - - - - - - QFrame::NoFrame - - - AD=${AD},R=3 - - - area_detector_roi.ui - - - - - - - - - - @@ -651,18 +580,17 @@ QPushButton
pydm.widgets.pushbutton
- - PyDMSlider - QFrame -
pydm.widgets.slider
- 1 -
ImageView QWidget
pyqtgraph
1
+ + PyDMLineEdit + QLineEdit +
pydm.widgets.line_edit
+
diff --git a/src/firefly/plans/line_scan.ui b/src/firefly/plans/line_scan.ui index f21e7b6b..1279f2e1 100644 --- a/src/firefly/plans/line_scan.ui +++ b/src/firefly/plans/line_scan.ui @@ -29,7 +29,7 @@ 0 0 377 - 215 + 213
From 0a848c79f450c16d51b0028ca74f561bff9ab146 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 21 Jan 2025 16:03:42 -0600 Subject: [PATCH 06/47] Name the ui objects --- src/firefly/area_detector_viewer.ui | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index 9e3c74e8..f431518e 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -106,7 +106,7 @@ - + @@ -126,7 +126,7 @@ - + Continuous @@ -153,7 +153,7 @@ - + @@ -173,7 +173,7 @@ - + Continuous @@ -288,7 +288,7 @@ - + @@ -338,7 +338,7 @@ - + From 7cd53b8cb744194ef680cca3268d561166068590 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 21 Jan 2025 19:30:49 -0600 Subject: [PATCH 07/47] Add slot for checkbox and pushbutton --- src/firefly/area_detector_viewer.py | 40 ++++++++++++++++++++++ src/firefly/area_detector_viewer.ui | 52 ++++++++++++++--------------- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index 1eb3f186..fb5960ca 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -4,6 +4,8 @@ import numpy as np import pydm import pyqtgraph +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QCheckBox, QPushButton from firefly import display from haven import beamline @@ -23,6 +25,25 @@ class AreaDetectorViewerDisplay(display.FireflyDisplay): ) image_is_new: bool = True + def __init__(self, parent=None, args=None, macros=None): + super().__init__(parent=parent, args=args, macros=macros) + + # Access Exposure widgets + self.exposure_checkbox = self.findChild(QCheckBox, 'ExposureCheckBox') + self.exposure_push_button = self.findChild(QPushButton, 'ExposurePushButton') + + # Connect Exposure signals to slots + self.exposure_checkbox.stateChanged.connect(self.handle_exposure_checkbox_change) + self.exposure_push_button.clicked.connect(self.set_exposure_auto) + + # Access Gain widgets + self.gain_checkbox = self.findChild(QCheckBox, 'GainCheckBox') + self.gain_push_button = self.findChild(QPushButton, 'GainPushButton') + + # Connect Gain signals to slots + self.gain_checkbox.stateChanged.connect(self.handle_gain_checkbox_change) + self.gain_push_button.clicked.connect(self.set_gain_auto) + def customize_device(self): device_name = name = self.macros()["AD"] device = beamline.devices[device_name] @@ -74,6 +95,25 @@ def launch_caqtdm(self): log.info(f"Launching caQtDM: {cmd}") self._open_caqtdm_subprocess(cmd) + @pyqtSlot(int) + def handle_exposure_checkbox_change(self, state): + """ + Handle the Exposure checkbox state change. + Disable the LineEdit when the checkbox is checked. + """ + if state == 2: # Checked + self.exposure_push_button.setEnabled(False) + else: # Checkbox is unchecked + self.exposure_push_button.setEnabled(True) + + @pyqtSlot() + def handle_exposure_push_button_click(self): + """ + Handle the Exposure push button click. + Disable the LineEdit when the button is clicked. + """ + self.exposure_checkbox.setChecked(False) + # ----------------------------------------------------------------------------- # :author: Mark Wolfman diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index f431518e..884d6a18 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -125,19 +125,6 @@ - - - - Continuous - - - haven://${AD}.drv.acquire_time_auto - - - 2 - - - @@ -172,19 +159,6 @@ - - - - Continuous - - - haven://${AD}.drv.gain_auto - - - 2 - - - @@ -303,6 +277,19 @@ + + + + Continuous + + + haven://${AD}.drv.acquire_time_auto + + + 2 + + + @@ -353,6 +340,19 @@ + + + + Continuous + + + haven://${AD}.drv.gain_auto + + + 2 + + + From 50548da089367ea710f63dcfcffb0ee3d16bf4e4 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 21 Jan 2025 21:57:30 -0600 Subject: [PATCH 08/47] Add tests for CheckBox and PushButton. --- .../tests/test_area_detector_display.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/firefly/tests/test_area_detector_display.py b/src/firefly/tests/test_area_detector_display.py index 82fd5c0d..a7a05ae6 100644 --- a/src/firefly/tests/test_area_detector_display.py +++ b/src/firefly/tests/test_area_detector_display.py @@ -4,6 +4,7 @@ import pydm import pyqtgraph import pytest +from pyQt5.QtCore import Qt from firefly.area_detector_viewer import AreaDetectorViewerDisplay @@ -48,6 +49,52 @@ def test_caqtdm_window(display, sim_camera): ) +def test_exposure_checkbox_push_button(display, qtbot): + """ + Test the interaction between the Exposure checkbox and push button. + """ + exposure_checkbox = display.exposure_checkbox + exposure_push_button = display.exposure_push_button + + # Initially both should be enabled + assert exposure_checkbox.isEnabled() + assert exposure_push_button.isEnabled() + + # Simulate checking the checkbox + exposure_checkbox.setChecked(True) + assert not exposure_push_button.isEnabled(), "Exposure PushButton should be disabled when Checkbox is checked." + + # Simulate unchecking the checkbox + exposure_checkbox.setChecked(False) + assert exposure_push_button.isEnabled(), "Exposure PushButton should be enabled when Checkbox is unchecked." + + # Simulate clicking the push button + qtbot.mouseClick(exposure_push_button, Qt.LeftButton) + assert not exposure_checkbox.isChecked(), "Exposure Checkbox should be unchecked when PushButton is clicked." + +def test_gain_checkbox_push_button(display, qtbot): + """ + Test the interaction between the Gain checkbox and push button. + """ + gain_checkbox = display.gain_checkbox + gain_push_button = display.gain_push_button + + # Initially both should be enabled + assert gain_checkbox.isEnabled() + assert gain_push_button.isEnabled() + + # Simulate checking the checkbox + gain_checkbox.setChecked(True) + assert not gain_push_button.isEnabled(), "Gain PushButton should be disabled when Checkbox is checked." + + # Simulate unchecking the checkbox + gain_checkbox.setChecked(False) + assert gain_push_button.isEnabled(), "Gain PushButton should be enabled when Checkbox is unchecked." + + # Simulate clicking the push button + qtbot.mouseClick(gain_push_button, Qt.LeftButton) + assert not gain_checkbox.isChecked(), "Gain Checkbox should be unchecked when PushButton is clicked." + # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov From 8e915f7f31e136fcf5b8c775bd7093c9b14503ca Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 21 Jan 2025 23:43:10 -0600 Subject: [PATCH 09/47] Update the checkbox and push button test --- src/firefly/area_detector_viewer.py | 49 +++++++++++++------ .../tests/test_area_detector_display.py | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index fb5960ca..ff948eff 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -34,7 +34,7 @@ def __init__(self, parent=None, args=None, macros=None): # Connect Exposure signals to slots self.exposure_checkbox.stateChanged.connect(self.handle_exposure_checkbox_change) - self.exposure_push_button.clicked.connect(self.set_exposure_auto) + self.exposure_push_button.clicked.connect(self.handle_exposure_push_button_click) # Access Gain widgets self.gain_checkbox = self.findChild(QCheckBox, 'GainCheckBox') @@ -42,7 +42,7 @@ def __init__(self, parent=None, args=None, macros=None): # Connect Gain signals to slots self.gain_checkbox.stateChanged.connect(self.handle_gain_checkbox_change) - self.gain_push_button.clicked.connect(self.set_gain_auto) + self.gain_push_button.clicked.connect(self.handle_gain_push_button_click) def customize_device(self): device_name = name = self.macros()["AD"] @@ -97,22 +97,41 @@ def launch_caqtdm(self): @pyqtSlot(int) def handle_exposure_checkbox_change(self, state): - """ - Handle the Exposure checkbox state change. - Disable the LineEdit when the checkbox is checked. - """ - if state == 2: # Checked - self.exposure_push_button.setEnabled(False) - else: # Checkbox is unchecked - self.exposure_push_button.setEnabled(True) + """ + Handle the exposure checkbox state change. + Disable the push button when the checkbox is checked. + """ + if state == 2: # Checked + self.exposure_push_button.setEnabled(False) + else: # Checkbox is unchecked + self.exposure_push_button.setEnabled(True) @pyqtSlot() def handle_exposure_push_button_click(self): - """ - Handle the Exposure push button click. - Disable the LineEdit when the button is clicked. - """ - self.exposure_checkbox.setChecked(False) + """ + Handle the exposure push button click. + Disable the checkbox when the button is clicked. + """ + self.exposure_checkbox.setChecked(False) + + @pyqtSlot(int) + def handle_gain_checkbox_change(self, state): + """ + Handle the gain checkbox state change. + Disable the push button when the checkbox is checked. + """ + if state == 2: # Checked + self.exposure_push_button.setEnabled(False) + else: # Checkbox is unchecked + self.gain_push_button.setEnabled(True) + + @pyqtSlot() + def handle_gain_push_button_click(self): + """ + Handle the gain push button click. + Disable the checkbox when the button is clicked. + """ + self.gain_checkbox.setChecked(False) # ----------------------------------------------------------------------------- diff --git a/src/firefly/tests/test_area_detector_display.py b/src/firefly/tests/test_area_detector_display.py index a7a05ae6..da19f1f3 100644 --- a/src/firefly/tests/test_area_detector_display.py +++ b/src/firefly/tests/test_area_detector_display.py @@ -4,7 +4,7 @@ import pydm import pyqtgraph import pytest -from pyQt5.QtCore import Qt +from PyQt5.QtCore import Qt from firefly.area_detector_viewer import AreaDetectorViewerDisplay From a6fca529ec9c40a19721c238a93f4bef54133337 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 22 Jan 2025 00:32:37 -0600 Subject: [PATCH 10/47] Pass pytest. --- src/firefly/area_detector_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index ff948eff..fba4b131 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -121,7 +121,7 @@ def handle_gain_checkbox_change(self, state): Disable the push button when the checkbox is checked. """ if state == 2: # Checked - self.exposure_push_button.setEnabled(False) + self.gain_push_button.setEnabled(False) else: # Checkbox is unchecked self.gain_push_button.setEnabled(True) From f384ae1a9858448e9f997d7f7e49208249e5effd Mon Sep 17 00:00:00 2001 From: yannachen Date: Thu, 23 Jan 2025 13:25:41 -0600 Subject: [PATCH 11/47] Add shape width in overlays --- environment-designer.yml | 2 ++ src/firefly/area_detector_overlay.ui | 30 ++++++++-------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/environment-designer.yml b/environment-designer.yml index def133f4..c89314d2 100644 --- a/environment-designer.yml +++ b/environment-designer.yml @@ -81,3 +81,5 @@ dependencies: # --- Extra Qt UI packages - qtawesome - qasync + + - dask >= 2022 diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index 165f7e06..2582e4bf 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -108,7 +108,7 @@ Center - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter
@@ -145,7 +145,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -189,7 +189,7 @@ Size - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -217,7 +217,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -239,32 +239,23 @@ Width - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter - - - - - - 1 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - haven://${AD}.overlayes.overlay_${OV}.WidthX + haven://${AD}.overlays.overlay_${OV}.widthX - Qt::Horizontal + Qt::Orientation::Horizontal @@ -279,11 +270,6 @@ - - PyDMLabel - QLabel -
pydm.widgets.label
-
PyDMSlider QFrame From 271f3deae6fc39fcdea13f7d23db4449d14eb2f9 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 23 Jan 2025 20:33:55 -0600 Subject: [PATCH 12/47] Update overlay ui. --- src/firefly/area_detector_overlay.ui | 120 ++++----------------------- 1 file changed, 15 insertions(+), 105 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index 2582e4bf..c322d15a 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -7,7 +7,7 @@ 0 0 263 - 215 + 181
@@ -71,15 +71,6 @@ - - true - - - false - - - false - @@ -87,16 +78,7 @@ haven://${AD}.overlays.overlay_${OV}.position_y - 2 - - - 3000.000000000000000 - - - 0.000000000000000 - - - true + Qt::Vertical
@@ -108,35 +90,20 @@ Center - Qt::AlignmentFlag::AlignCenter + Qt::AlignCenter - - - false - + - - [] - haven://${AD}.overlays.overlay_${OV}.position_x - - true - - - 0.000000000000000 - - - 3000.000000000000000 - - - 101 + + 0 @@ -145,7 +112,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -168,16 +135,7 @@ haven://${AD}.overlays.overlay_${OV}.size_y - 2 - - - 3000.000000000000000 - - - 0.000000000000000 - - - true + Qt::Vertical @@ -189,7 +147,7 @@ Size - Qt::AlignmentFlag::AlignCenter + Qt::AlignCenter @@ -201,15 +159,6 @@ haven://${AD}.overlays.overlay_${OV}.size_x - - 3000.000000000000000 - - - 0.000000000000000 - - - true - @@ -217,45 +166,7 @@ - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - Width - - - Qt::AlignmentFlag::AlignCenter - - - - - - - - - haven://${AD}.overlays.overlay_${OV}.widthX - - - - - - - Qt::Orientation::Horizontal + Qt::Horizontal @@ -270,12 +181,6 @@ - - PyDMSlider - QFrame -
pydm.widgets.slider
- 1 -
PyDMCheckbox QCheckBox @@ -286,6 +191,11 @@ QComboBox
pydm.widgets.enum_combo_box
+ + PyDMSlider + QFrame +
pydm.widgets.slider
+
From dff4db77d025ec53d0a40f64c4e0770d6e0bb084 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 23 Jan 2025 20:57:01 -0600 Subject: [PATCH 13/47] Update robot ui --- src/firefly/mesa | 1 + src/firefly/tests/=0.9.0a1 | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 160000 src/firefly/mesa create mode 100644 src/firefly/tests/=0.9.0a1 diff --git a/src/firefly/mesa b/src/firefly/mesa new file mode 160000 index 00000000..aa37e2e5 --- /dev/null +++ b/src/firefly/mesa @@ -0,0 +1 @@ +Subproject commit aa37e2e5cb8a2282791d60a36becb5f32de62d11 diff --git a/src/firefly/tests/=0.9.0a1 b/src/firefly/tests/=0.9.0a1 new file mode 100644 index 00000000..8b361d66 --- /dev/null +++ b/src/firefly/tests/=0.9.0a1 @@ -0,0 +1,43 @@ +Requirement already satisfied: ophyd-async in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (0.5.0) +Requirement already satisfied: networkx>=2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (3.2.1) +Requirement already satisfied: numpy<2.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.26.4) +Requirement already satisfied: packaging in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (24.1) +Requirement already satisfied: pint in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (0.24.3) +Requirement already satisfied: bluesky>=1.13.0a3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.13.0a4) +Requirement already satisfied: event-model in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.21.0) +Requirement already satisfied: p4p in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (4.1.12) +Requirement already satisfied: pyyaml in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (6.0.2) +Requirement already satisfied: colorlog in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (6.8.2) +Requirement already satisfied: pydantic>=2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (2.8.2) +Requirement already satisfied: pydantic-numpy in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (5.0.2) +Requirement already satisfied: cycler in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.12.1) +Requirement already satisfied: historydict in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (1.2.6) +Requirement already satisfied: msgpack in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (1.0.8) +Requirement already satisfied: msgpack-numpy in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.4.8) +Requirement already satisfied: toolz in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.12.1) +Requirement already satisfied: tqdm>=4.44 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (4.66.5) +Requirement already satisfied: zict<3.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (2.2.0) +Requirement already satisfied: typing-extensions>=4.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (4.12.2) +Requirement already satisfied: importlib-resources in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from event-model->ophyd-async) (6.4.0) +Requirement already satisfied: jsonschema>=3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from event-model->ophyd-async) (4.23.0) +Requirement already satisfied: annotated-types>=0.4.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic>=2.0->ophyd-async) (0.7.0) +Requirement already satisfied: pydantic-core==2.20.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic>=2.0->ophyd-async) (2.20.1) +Requirement already satisfied: epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (7.0.7.99.0.2) +Requirement already satisfied: pvxslibs<1.4.0a1,>=1.3.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (1.3.1) +Requirement already satisfied: nose2>=0.8.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (0.15.1) +Requirement already satisfied: ply in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (3.11) +Requirement already satisfied: appdirs>=1.4.4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (1.4.4) +Requirement already satisfied: flexcache>=0.3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (0.3) +Requirement already satisfied: flexparser>=0.3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (0.3.1) +Requirement already satisfied: compress-pickle[lz4] in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (2.1.0) +Requirement already satisfied: ruamel-yaml<0.19.0,>=0.18.5 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (0.18.6) +Requirement already satisfied: semver<4.0.0,>=3.0.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (3.0.2) +Requirement already satisfied: setuptools in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2->p4p->ophyd-async) (72.1.0) +Requirement already satisfied: setuptools-dso>=2.9a1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2->p4p->ophyd-async) (2.11a2) +Requirement already satisfied: attrs>=22.2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (24.2.0) +Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (2023.12.1) +Requirement already satisfied: referencing>=0.28.4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (0.35.1) +Requirement already satisfied: rpds-py>=0.7.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (0.20.0) +Requirement already satisfied: ruamel.yaml.clib>=0.2.7 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ruamel-yaml<0.19.0,>=0.18.5->pydantic-numpy->ophyd-async) (0.2.8) +Requirement already satisfied: heapdict in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from zict<3.0.0->bluesky>=1.13.0a3->ophyd-async) (1.0.1) +Requirement already satisfied: lz4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from compress-pickle[lz4]->pydantic-numpy->ophyd-async) (4.3.3) From 7ab5caee83737c9d3eeb034c2ca60fa81816db7b Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 23 Jan 2025 21:05:38 -0600 Subject: [PATCH 14/47] Delete unknow folder --- src/firefly/mesa | 1 - 1 file changed, 1 deletion(-) delete mode 160000 src/firefly/mesa diff --git a/src/firefly/mesa b/src/firefly/mesa deleted file mode 160000 index aa37e2e5..00000000 --- a/src/firefly/mesa +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aa37e2e5cb8a2282791d60a36becb5f32de62d11 From a134fe684c3fe9eb54abe50c572209787592d094 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 23 Jan 2025 21:26:13 -0600 Subject: [PATCH 15/47] Remove change in yml file --- =5 | 2 ++ =5.15 | 0 environment-designer.yml | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 =5 create mode 100644 =5.15 diff --git a/=5 b/=5 new file mode 100644 index 00000000..88e053f9 --- /dev/null +++ b/=5 @@ -0,0 +1,2 @@ +Requirement already satisfied: aps-dm-api in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (9.3.1) +Requirement already satisfied: decorator in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from aps-dm-api) (5.1.1) diff --git a/=5.15 b/=5.15 new file mode 100644 index 00000000..e69de29b diff --git a/environment-designer.yml b/environment-designer.yml index c89314d2..def133f4 100644 --- a/environment-designer.yml +++ b/environment-designer.yml @@ -81,5 +81,3 @@ dependencies: # --- Extra Qt UI packages - qtawesome - qasync - - - dask >= 2022 From f3c6d8fab18dc8f04c129e09d2a36d6e2cb2ab55 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 23 Jan 2025 21:34:21 -0600 Subject: [PATCH 16/47] Use black and isort --- src/firefly/area_detector_viewer.py | 22 +- src/firefly/data_noise.py | 263 +++++++++++------- .../tests/test_area_detector_display.py | 26 +- 3 files changed, 202 insertions(+), 109 deletions(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index fba4b131..51ae5fda 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -29,16 +29,20 @@ def __init__(self, parent=None, args=None, macros=None): super().__init__(parent=parent, args=args, macros=macros) # Access Exposure widgets - self.exposure_checkbox = self.findChild(QCheckBox, 'ExposureCheckBox') - self.exposure_push_button = self.findChild(QPushButton, 'ExposurePushButton') + self.exposure_checkbox = self.findChild(QCheckBox, "ExposureCheckBox") + self.exposure_push_button = self.findChild(QPushButton, "ExposurePushButton") # Connect Exposure signals to slots - self.exposure_checkbox.stateChanged.connect(self.handle_exposure_checkbox_change) - self.exposure_push_button.clicked.connect(self.handle_exposure_push_button_click) + self.exposure_checkbox.stateChanged.connect( + self.handle_exposure_checkbox_change + ) + self.exposure_push_button.clicked.connect( + self.handle_exposure_push_button_click + ) # Access Gain widgets - self.gain_checkbox = self.findChild(QCheckBox, 'GainCheckBox') - self.gain_push_button = self.findChild(QPushButton, 'GainPushButton') + self.gain_checkbox = self.findChild(QCheckBox, "GainCheckBox") + self.gain_push_button = self.findChild(QPushButton, "GainPushButton") # Connect Gain signals to slots self.gain_checkbox.stateChanged.connect(self.handle_gain_checkbox_change) @@ -102,7 +106,7 @@ def handle_exposure_checkbox_change(self, state): Disable the push button when the checkbox is checked. """ if state == 2: # Checked - self.exposure_push_button.setEnabled(False) + self.exposure_push_button.setEnabled(False) else: # Checkbox is unchecked self.exposure_push_button.setEnabled(True) @@ -112,7 +116,7 @@ def handle_exposure_push_button_click(self): Handle the exposure push button click. Disable the checkbox when the button is clicked. """ - self.exposure_checkbox.setChecked(False) + self.exposure_checkbox.setChecked(False) @pyqtSlot(int) def handle_gain_checkbox_change(self, state): @@ -132,7 +136,7 @@ def handle_gain_push_button_click(self): Disable the checkbox when the button is clicked. """ self.gain_checkbox.setChecked(False) - + # ----------------------------------------------------------------------------- # :author: Mark Wolfman diff --git a/src/firefly/data_noise.py b/src/firefly/data_noise.py index 0e13512a..752c1613 100644 --- a/src/firefly/data_noise.py +++ b/src/firefly/data_noise.py @@ -1,27 +1,27 @@ # larch imports -from larch.xafs import pre_edge, autobk, sort_xafs, xftf, estimate_noise -from larch.io import read_ascii -from larch.plot.plotly_xafsplots import * +import copy +import os # General imports import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd -import os -import copy +from larch.io import read_ascii +from larch.plot.plotly_xafsplots import * +from larch.xafs import autobk, estimate_noise, pre_edge, sort_xafs, xftf # Set the default font for the entire plot -mpl.rcParams['font.family'] = 'Arial' +mpl.rcParams["font.family"] = "Arial" # Constant # for epsilon_r method -r_window_size = 30 # this can be changed depending on the data points. -noise_factor=0.2 # not sure now +r_window_size = 30 # this can be changed depending on the data points. +noise_factor = 0.2 # not sure now # for random noise method -average_num = 5 # smoothing data using average of 5 data points -random_noise_window_size = 10 # this can be changed depending on the data points. +average_num = 5 # smoothing data using average of 5 data points +random_noise_window_size = 10 # this can be changed depending on the data points. def find_closest_index(array, target_value): @@ -38,6 +38,7 @@ def find_closest_index(array, target_value): closest_index = np.abs(array - target_value).argmin() return closest_index + def find_smoothest_range(data, window_size): """ Finds the smoothest range in an oscillation curve based on variance. @@ -51,21 +52,22 @@ def find_smoothest_range(data, window_size): window_size = int(window_size) # Ensure window_size is an integer variances = np.zeros(len(data) - window_size + 1) for i in range(len(variances)): - variances[i] = np.var(data[i:i+window_size]) + variances[i] = np.var(data[i : i + window_size]) smoothest_index = np.argmin(variances) start_index = smoothest_index end_index = smoothest_index + window_size - 1 return start_index, end_index + def load_data(data_path): """ - From this step, the data including all the Larch processes, eg. data.r and data.chi_r will automatically created in the data group after xftf process + From this step, the data including all the Larch processes, eg. data.r and data.chi_r will automatically created in the data group after xftf process """ - data = read_ascii(data_path, labels='col1, col2') + data = read_ascii(data_path, labels="col1, col2") data.is_frozen = False - data.datatype = 'xas' - data.xdat = data.data[0, : ] - data.ydat = data.data[1, : ]/1.0 + data.datatype = "xas" + data.xdat = data.data[0, :] + data.ydat = data.data[1, :] / 1.0 data.yerr = 1.0 data.energy = data.xdat data.mu = data.ydat @@ -73,27 +75,53 @@ def load_data(data_path): data.filename = data_path.split(os.sep)[-1] return data + def data_nor_bg(data, figure=True): - # Normalization - pre_edge(data, pre1=-100, pre2=-20.00, - nvict=0, nnorm=2, norm1=50.00, norm2=1000) - # Background function - autobk(data, - rbkg= 1.0, - ek0= data.e0, - kmin= 0.000, kmax= 30, kweight= 2.0, - clamp_lo= 0, clamp_hi= 0) - - if figure: - fig1 = plot_mu(data, show_norm=False, show_deriv=False, - show_pre=True, show_post=True, show_e0=True, with_deriv=False, - emin=None, emax=None, label=f'mu_nor_bg',offset=0,title=None, fig= None, show=True) - fig1.set_style(width=800, height=350) - indice = np.where(data.energy>data.ek0)[0][0] - fig1.add_plot([data.energy[indice]], [data.mu[indice]], label='ek0', color='red',linewidth=0, marker=dict(size=5)) - fig1.show() - plot_bkg(data, label=f'bkg_nor_bg') - return data + # Normalization + pre_edge(data, pre1=-100, pre2=-20.00, nvict=0, nnorm=2, norm1=50.00, norm2=1000) + # Background function + autobk( + data, + rbkg=1.0, + ek0=data.e0, + kmin=0.000, + kmax=30, + kweight=2.0, + clamp_lo=0, + clamp_hi=0, + ) + + if figure: + fig1 = plot_mu( + data, + show_norm=False, + show_deriv=False, + show_pre=True, + show_post=True, + show_e0=True, + with_deriv=False, + emin=None, + emax=None, + label=f"mu_nor_bg", + offset=0, + title=None, + fig=None, + show=True, + ) + fig1.set_style(width=800, height=350) + indice = np.where(data.energy > data.ek0)[0][0] + fig1.add_plot( + [data.energy[indice]], + [data.mu[indice]], + label="ek0", + color="red", + linewidth=0, + marker=dict(size=5), + ) + fig1.show() + plot_bkg(data, label=f"bkg_nor_bg") + return data + def scan_num_epsilon_r(folder, data_list, figure=True): """ @@ -102,150 +130,197 @@ def scan_num_epsilon_r(folder, data_list, figure=True): Parameters: - folder: the folder saving all the data. - data_list: a data list of data with one scan, two scan merged, three scan merged... - - figure: whether plots are shown in the noise estimate process + - figure: whether plots are shown in the noise estimate process Returns: - scan number that is good for measurement """ - epsilon_r_list=[] + epsilon_r_list = [] - data_path = folder+data_list[0][0] + data_path = folder + data_list[0][0] data = load_data(data_path) - datacopy=copy.deepcopy(data) + datacopy = copy.deepcopy(data) data_nor_bg(datacopy, figure=False) - xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 3.000, window='Hanning', rmax_out=30.000) # k weight should be 3 to magnifize the noise part. + xftf( + datacopy, + kmin=3, + kmax=12, + dk=1.000, + kweight=3.000, + window="Hanning", + rmax_out=30.000, + ) # k weight should be 3 to magnifize the noise part. start_index, end_index = find_smoothest_range(datacopy.chir_re, r_window_size) r_start_noise = datacopy.r[start_index] - r_end_noise = datacopy.r[end_index] - + r_end_noise = datacopy.r[end_index] for n in range(len(data_list)): - data_path = folder+data_list[n][0] + data_path = folder + data_list[n][0] print(data_path) data = load_data(data_path) - datacopy=copy.deepcopy(data) + datacopy = copy.deepcopy(data) data_nor_bg(datacopy, figure=False) - xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 3.000, window='Hanning', rmax_out=30.000) - - estimate_noise(datacopy.k, datacopy.chi, group=datacopy, rmin=r_start_noise, rmax=r_end_noise, kweight=3, kmin=3, kmax=12, dk=1, kwindow='Hanning')#, dk2=None, kstep=0.05, nfft=2048, _larch=None) - epsilon_r=datacopy.epsilon_r + xftf( + datacopy, + kmin=3, + kmax=12, + dk=1.000, + kweight=3.000, + window="Hanning", + rmax_out=30.000, + ) + + estimate_noise( + datacopy.k, + datacopy.chi, + group=datacopy, + rmin=r_start_noise, + rmax=r_end_noise, + kweight=3, + kmin=3, + kmax=12, + dk=1, + kwindow="Hanning", + ) # , dk2=None, kstep=0.05, nfft=2048, _larch=None) + epsilon_r = datacopy.epsilon_r print(epsilon_r) epsilon_r_list.append(epsilon_r) noise = list(zip(range(len(data_list)), epsilon_r_list)) - if len(noise)==1: + if len(noise) == 1: scan_num = noise[0][0] else: for i in range(1, len(noise)): current_value = noise[i][1] first_value = noise[0][1] - if current_value < noise_factor * first_value: + if current_value < noise_factor * first_value: scan_num = noise[i - 1][0] else: scan_num = noise[-1][0] - + if figure: plt.figure() plt.plot(range(len(data_list)), epsilon_r_list) - plt.xlabel('scan number') - plt.ylabel('epsilon_r') + plt.xlabel("scan number") + plt.ylabel("epsilon_r") return scan_num + def scan_num_random_noise(folder, data_list, figure=True): """ The random noise method follows Shelly D. Kelly Chapter14 Analysis of Soils and Minerals Using X-ray Absorption SPectroscopy. """ - random_noise_list=[] - - # find a region to estimate the random noise. The region should not be affected by a sudden drop of data points at the peak wings. So the function find_smoothest_range is used. - data_path = folder+data_list[0][0] + random_noise_list = [] + + # find a region to estimate the random noise. The region should not be affected by a sudden drop of data points at the peak wings. So the function find_smoothest_range is used. + data_path = folder + data_list[0][0] data = load_data(data_path) - datacopy=copy.deepcopy(data) + datacopy = copy.deepcopy(data) data_nor_bg(datacopy, figure=False) - xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 2.000, window='Hanning', rmax_out=30.000) + xftf( + datacopy, + kmin=3, + kmax=12, + dk=1.000, + kweight=2.000, + window="Hanning", + rmax_out=30.000, + ) start_index = find_closest_index(datacopy.k, 9) end_index = find_closest_index(datacopy.k, 11) # Filter the data to get only the rows where k is in the range [9, 11] - filtered_data = (datacopy.k[start_index:end_index])**2*datacopy.chi[start_index:end_index] + filtered_data = (datacopy.k[start_index:end_index]) ** 2 * datacopy.chi[ + start_index:end_index + ] data_series = pd.Series(filtered_data) - # Apply a moving average to estimate the signal + # Apply a moving average to estimate the signal smoothed_signal = data_series.rolling(window=average_num, center=True).mean() # Fill any NaN values that result from the moving average - smoothed_signal = smoothed_signal.fillna(method='bfill').fillna(method='ffill') + smoothed_signal = smoothed_signal.fillna(method="bfill").fillna(method="ffill") # Estimate noise as the difference between the original data and the smoothed signal noise = data_series - smoothed_signal - start_index_noise, end_index_noise=find_smoothest_range(noise, random_noise_window_size) + start_index_noise, end_index_noise = find_smoothest_range( + noise, random_noise_window_size + ) for n in range(len(data_list)): - data_path = folder+data_list[n][0] + data_path = folder + data_list[n][0] print(data_path) data = load_data(data_path) - datacopy=copy.deepcopy(data) + datacopy = copy.deepcopy(data) data_nor_bg(datacopy, figure=False) - xftf(datacopy, kmin=3, kmax=12, dk= 1.000, kweight= 2.000, window='Hanning', rmax_out=30.000) + xftf( + datacopy, + kmin=3, + kmax=12, + dk=1.000, + kweight=2.000, + window="Hanning", + rmax_out=30.000, + ) if figure: plt.figure() plt.subplot(2, 1, 1) - plt.plot(datacopy.k, (datacopy.k)**2*datacopy.chi, label=data_path) - plt.axvspan(9, 11, color='yellow', alpha=0.3) - plt.xlabel('k (Å^(-1))') - plt.xlim(0,np.max(datacopy.k)) - plt.ylabel('$k^2\chi(k)$ ($Å^{-2}$)') - plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + plt.plot(datacopy.k, (datacopy.k) ** 2 * datacopy.chi, label=data_path) + plt.axvspan(9, 11, color="yellow", alpha=0.3) + plt.xlabel("k (Å^(-1))") + plt.xlim(0, np.max(datacopy.k)) + plt.ylabel("$k^2\chi(k)$ ($Å^{-2}$)") + plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) plt.subplot(2, 1, 2) - plt.plot(datacopy.k, (datacopy.k)**2*datacopy.chi) - plt.xlabel('k (Å^(-1))') - plt.xlim(9,11) - plt.ylabel('$k^2\chi(k)$ ($Å^{-2}$)') - plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + plt.plot(datacopy.k, (datacopy.k) ** 2 * datacopy.chi) + plt.xlabel("k (Å^(-1))") + plt.xlim(9, 11) + plt.ylabel("$k^2\chi(k)$ ($Å^{-2}$)") + plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) start_index = find_closest_index(datacopy.k, 9) end_index = find_closest_index(datacopy.k, 11) - filtered_data = (datacopy.k[start_index:end_index])**2*datacopy.chi[start_index:end_index] + filtered_data = (datacopy.k[start_index:end_index]) ** 2 * datacopy.chi[ + start_index:end_index + ] data_series = pd.Series(filtered_data) smoothed_signal = data_series.rolling(window=average_num, center=True).mean() - smoothed_signal = smoothed_signal.fillna(method='bfill').fillna(method='ffill') + smoothed_signal = smoothed_signal.fillna(method="bfill").fillna(method="ffill") noise = data_series - smoothed_signal noise_flat = noise[start_index_noise:end_index_noise] if figure: - plt.figure(figsize=(8,8)) + plt.figure(figsize=(8, 8)) plt.subplot(2, 1, 1) - plt.plot(np.arange(len(smoothed_signal)), smoothed_signal, 'r.-') - plt.title("Smoothed data averaged by 5 data poins" ) + plt.plot(np.arange(len(smoothed_signal)), smoothed_signal, "r.-") + plt.title("Smoothed data averaged by 5 data poins") plt.subplot(2, 1, 2) - plt.plot(np.arange(len(noise)), noise, 'r.-') - plt.axvspan(start_index_noise, end_index_noise, color='c', alpha=0.5) - plt.title("Random noise and the region for the final standard deviation" ) - + plt.plot(np.arange(len(noise)), noise, "r.-") + plt.axvspan(start_index_noise, end_index_noise, color="c", alpha=0.5) + plt.title("Random noise and the region for the final standard deviation") + noise_std = np.std(noise_flat) print(noise_std) random_noise_list.append(noise_std) noise = list(zip(range(len(data_list)), random_noise_list)) - if len(noise)==1: + if len(noise) == 1: scan_num = noise[0][0] else: for i in range(1, len(noise)): current_value = noise[i][1] - if current_value < 0.1: + if current_value < 0.1: scan_num = noise[i][0] break else: scan_num = noise[-1][0] - + if figure: plt.figure() plt.plot(range(len(data_list)), random_noise_list) - plt.xlabel('scan number') - plt.ylabel('random noise (%)') + plt.xlabel("scan number") + plt.ylabel("random noise (%)") return scan_num - - diff --git a/src/firefly/tests/test_area_detector_display.py b/src/firefly/tests/test_area_detector_display.py index da19f1f3..2610f7fd 100644 --- a/src/firefly/tests/test_area_detector_display.py +++ b/src/firefly/tests/test_area_detector_display.py @@ -62,15 +62,22 @@ def test_exposure_checkbox_push_button(display, qtbot): # Simulate checking the checkbox exposure_checkbox.setChecked(True) - assert not exposure_push_button.isEnabled(), "Exposure PushButton should be disabled when Checkbox is checked." + assert ( + not exposure_push_button.isEnabled() + ), "Exposure PushButton should be disabled when Checkbox is checked." # Simulate unchecking the checkbox exposure_checkbox.setChecked(False) - assert exposure_push_button.isEnabled(), "Exposure PushButton should be enabled when Checkbox is unchecked." + assert ( + exposure_push_button.isEnabled() + ), "Exposure PushButton should be enabled when Checkbox is unchecked." # Simulate clicking the push button qtbot.mouseClick(exposure_push_button, Qt.LeftButton) - assert not exposure_checkbox.isChecked(), "Exposure Checkbox should be unchecked when PushButton is clicked." + assert ( + not exposure_checkbox.isChecked() + ), "Exposure Checkbox should be unchecked when PushButton is clicked." + def test_gain_checkbox_push_button(display, qtbot): """ @@ -85,15 +92,22 @@ def test_gain_checkbox_push_button(display, qtbot): # Simulate checking the checkbox gain_checkbox.setChecked(True) - assert not gain_push_button.isEnabled(), "Gain PushButton should be disabled when Checkbox is checked." + assert ( + not gain_push_button.isEnabled() + ), "Gain PushButton should be disabled when Checkbox is checked." # Simulate unchecking the checkbox gain_checkbox.setChecked(False) - assert gain_push_button.isEnabled(), "Gain PushButton should be enabled when Checkbox is unchecked." + assert ( + gain_push_button.isEnabled() + ), "Gain PushButton should be enabled when Checkbox is unchecked." # Simulate clicking the push button qtbot.mouseClick(gain_push_button, Qt.LeftButton) - assert not gain_checkbox.isChecked(), "Gain Checkbox should be unchecked when PushButton is clicked." + assert ( + not gain_checkbox.isChecked() + ), "Gain Checkbox should be unchecked when PushButton is clicked." + # ----------------------------------------------------------------------------- # :author: Mark Wolfman From b247268978375e2c6f9513cbdb8beaa94705238b Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Fri, 24 Jan 2025 10:05:45 -0600 Subject: [PATCH 17/47] Update line_scan --- src/firefly/plans/line_scan.ui | 38 ---------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/firefly/plans/line_scan.ui b/src/firefly/plans/line_scan.ui index 29081353..cbe938a0 100644 --- a/src/firefly/plans/line_scan.ui +++ b/src/firefly/plans/line_scan.ui @@ -29,44 +29,6 @@ Num. Motors -<<<<<<< HEAD - - - - 0 - 0 - 377 - 213 - - - - - - - 3 - - - 3 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - -======= ->>>>>>> dc9463eefceb32e7a16973a8d82b0fecacf7b691
From 8df0fdf5bacb81ca1580f74943662632156d6332 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Fri, 24 Jan 2025 10:54:20 -0600 Subject: [PATCH 18/47] update enviroment file --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 4d8c39a6..9a5f8096 100644 --- a/environment.yml +++ b/environment.yml @@ -60,7 +60,7 @@ dependencies: # - bluesky >=1.13.1 - git+https://github.com/bluesky/bluesky.git@v1.13.1rc1 # Replace with pypi version once released - ophyd >=1.6.3 - - ophyd-async >=0.9.0a1 + - ophyd-async ==0.9.0a1 - apstools == 1.6.20 # Leave at 1.6.20 until this is fixed: https://github.com/BCDA-APS/apstools/issues/1022 - pcdsdevices # For extra signal types - pydm >=1.18.0 From 0b8243422c14204b9f67380a7d29d764be97f7a5 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Fri, 24 Jan 2025 10:59:10 -0600 Subject: [PATCH 19/47] Git remove trash files --- =5 | 2 - =5.15 | 0 src/firefly/data_noise.py | 326 -------------------------------------- 3 files changed, 328 deletions(-) delete mode 100644 =5 delete mode 100644 =5.15 delete mode 100644 src/firefly/data_noise.py diff --git a/=5 b/=5 deleted file mode 100644 index 88e053f9..00000000 --- a/=5 +++ /dev/null @@ -1,2 +0,0 @@ -Requirement already satisfied: aps-dm-api in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (9.3.1) -Requirement already satisfied: decorator in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from aps-dm-api) (5.1.1) diff --git a/=5.15 b/=5.15 deleted file mode 100644 index e69de29b..00000000 diff --git a/src/firefly/data_noise.py b/src/firefly/data_noise.py deleted file mode 100644 index 752c1613..00000000 --- a/src/firefly/data_noise.py +++ /dev/null @@ -1,326 +0,0 @@ -# larch imports -import copy -import os - -# General imports -import matplotlib as mpl -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from larch.io import read_ascii -from larch.plot.plotly_xafsplots import * -from larch.xafs import autobk, estimate_noise, pre_edge, sort_xafs, xftf - -# Set the default font for the entire plot -mpl.rcParams["font.family"] = "Arial" - - -# Constant -# for epsilon_r method -r_window_size = 30 # this can be changed depending on the data points. -noise_factor = 0.2 # not sure now -# for random noise method -average_num = 5 # smoothing data using average of 5 data points -random_noise_window_size = 10 # this can be changed depending on the data points. - - -def find_closest_index(array, target_value): - """ - Finds the index of the element in a NumPy array that is closest to the target_value. - - Parameters: - - array (numpy.ndarray): The NumPy array of numbers to search. - - target_value (float): The value to find the closest match for. - - Returns: - - int: The index of the element closest to the target_value. - """ - closest_index = np.abs(array - target_value).argmin() - return closest_index - - -def find_smoothest_range(data, window_size): - """ - Finds the smoothest range in an oscillation curve based on variance. - Parameters: - - data: 1D array or list representing the oscillation curve. - - window_size: Size of the window for calculating variance. - - Returns: - - Start and end indices of the smoothest range. - """ - window_size = int(window_size) # Ensure window_size is an integer - variances = np.zeros(len(data) - window_size + 1) - for i in range(len(variances)): - variances[i] = np.var(data[i : i + window_size]) - smoothest_index = np.argmin(variances) - start_index = smoothest_index - end_index = smoothest_index + window_size - 1 - return start_index, end_index - - -def load_data(data_path): - """ - From this step, the data including all the Larch processes, eg. data.r and data.chi_r will automatically created in the data group after xftf process - """ - data = read_ascii(data_path, labels="col1, col2") - data.is_frozen = False - data.datatype = "xas" - data.xdat = data.data[0, :] - data.ydat = data.data[1, :] / 1.0 - data.yerr = 1.0 - data.energy = data.xdat - data.mu = data.ydat - sort_xafs(data, overwrite=True, fix_repeats=True) - data.filename = data_path.split(os.sep)[-1] - return data - - -def data_nor_bg(data, figure=True): - # Normalization - pre_edge(data, pre1=-100, pre2=-20.00, nvict=0, nnorm=2, norm1=50.00, norm2=1000) - # Background function - autobk( - data, - rbkg=1.0, - ek0=data.e0, - kmin=0.000, - kmax=30, - kweight=2.0, - clamp_lo=0, - clamp_hi=0, - ) - - if figure: - fig1 = plot_mu( - data, - show_norm=False, - show_deriv=False, - show_pre=True, - show_post=True, - show_e0=True, - with_deriv=False, - emin=None, - emax=None, - label=f"mu_nor_bg", - offset=0, - title=None, - fig=None, - show=True, - ) - fig1.set_style(width=800, height=350) - indice = np.where(data.energy > data.ek0)[0][0] - fig1.add_plot( - [data.energy[indice]], - [data.mu[indice]], - label="ek0", - color="red", - linewidth=0, - marker=dict(size=5), - ) - fig1.show() - plot_bkg(data, label=f"bkg_nor_bg") - return data - - -def scan_num_epsilon_r(folder, data_list, figure=True): - """ - Finds the scan number while the noise is smaller than noise_factor*(the 1st scan data noise) - Here the noise_factor is 0.2, which is not sure now. - Parameters: - - folder: the folder saving all the data. - - data_list: a data list of data with one scan, two scan merged, three scan merged... - - figure: whether plots are shown in the noise estimate process - - Returns: - - scan number that is good for measurement - """ - - epsilon_r_list = [] - - data_path = folder + data_list[0][0] - data = load_data(data_path) - datacopy = copy.deepcopy(data) - data_nor_bg(datacopy, figure=False) - xftf( - datacopy, - kmin=3, - kmax=12, - dk=1.000, - kweight=3.000, - window="Hanning", - rmax_out=30.000, - ) # k weight should be 3 to magnifize the noise part. - start_index, end_index = find_smoothest_range(datacopy.chir_re, r_window_size) - r_start_noise = datacopy.r[start_index] - r_end_noise = datacopy.r[end_index] - - for n in range(len(data_list)): - data_path = folder + data_list[n][0] - print(data_path) - data = load_data(data_path) - datacopy = copy.deepcopy(data) - data_nor_bg(datacopy, figure=False) - - xftf( - datacopy, - kmin=3, - kmax=12, - dk=1.000, - kweight=3.000, - window="Hanning", - rmax_out=30.000, - ) - - estimate_noise( - datacopy.k, - datacopy.chi, - group=datacopy, - rmin=r_start_noise, - rmax=r_end_noise, - kweight=3, - kmin=3, - kmax=12, - dk=1, - kwindow="Hanning", - ) # , dk2=None, kstep=0.05, nfft=2048, _larch=None) - epsilon_r = datacopy.epsilon_r - print(epsilon_r) - epsilon_r_list.append(epsilon_r) - - noise = list(zip(range(len(data_list)), epsilon_r_list)) - if len(noise) == 1: - scan_num = noise[0][0] - else: - for i in range(1, len(noise)): - current_value = noise[i][1] - first_value = noise[0][1] - if current_value < noise_factor * first_value: - scan_num = noise[i - 1][0] - else: - scan_num = noise[-1][0] - - if figure: - plt.figure() - plt.plot(range(len(data_list)), epsilon_r_list) - plt.xlabel("scan number") - plt.ylabel("epsilon_r") - - return scan_num - - -def scan_num_random_noise(folder, data_list, figure=True): - """ - The random noise method follows Shelly D. Kelly Chapter14 Analysis of Soils and Minerals Using X-ray Absorption SPectroscopy. - """ - random_noise_list = [] - - # find a region to estimate the random noise. The region should not be affected by a sudden drop of data points at the peak wings. So the function find_smoothest_range is used. - data_path = folder + data_list[0][0] - data = load_data(data_path) - datacopy = copy.deepcopy(data) - data_nor_bg(datacopy, figure=False) - xftf( - datacopy, - kmin=3, - kmax=12, - dk=1.000, - kweight=2.000, - window="Hanning", - rmax_out=30.000, - ) - start_index = find_closest_index(datacopy.k, 9) - end_index = find_closest_index(datacopy.k, 11) - # Filter the data to get only the rows where k is in the range [9, 11] - filtered_data = (datacopy.k[start_index:end_index]) ** 2 * datacopy.chi[ - start_index:end_index - ] - data_series = pd.Series(filtered_data) - # Apply a moving average to estimate the signal - smoothed_signal = data_series.rolling(window=average_num, center=True).mean() - # Fill any NaN values that result from the moving average - smoothed_signal = smoothed_signal.fillna(method="bfill").fillna(method="ffill") - # Estimate noise as the difference between the original data and the smoothed signal - noise = data_series - smoothed_signal - start_index_noise, end_index_noise = find_smoothest_range( - noise, random_noise_window_size - ) - - for n in range(len(data_list)): - data_path = folder + data_list[n][0] - print(data_path) - data = load_data(data_path) - datacopy = copy.deepcopy(data) - data_nor_bg(datacopy, figure=False) - - xftf( - datacopy, - kmin=3, - kmax=12, - dk=1.000, - kweight=2.000, - window="Hanning", - rmax_out=30.000, - ) - if figure: - plt.figure() - plt.subplot(2, 1, 1) - plt.plot(datacopy.k, (datacopy.k) ** 2 * datacopy.chi, label=data_path) - plt.axvspan(9, 11, color="yellow", alpha=0.3) - plt.xlabel("k (Å^(-1))") - plt.xlim(0, np.max(datacopy.k)) - plt.ylabel("$k^2\chi(k)$ ($Å^{-2}$)") - plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) - - plt.subplot(2, 1, 2) - plt.plot(datacopy.k, (datacopy.k) ** 2 * datacopy.chi) - plt.xlabel("k (Å^(-1))") - plt.xlim(9, 11) - plt.ylabel("$k^2\chi(k)$ ($Å^{-2}$)") - plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) - - start_index = find_closest_index(datacopy.k, 9) - end_index = find_closest_index(datacopy.k, 11) - filtered_data = (datacopy.k[start_index:end_index]) ** 2 * datacopy.chi[ - start_index:end_index - ] - data_series = pd.Series(filtered_data) - smoothed_signal = data_series.rolling(window=average_num, center=True).mean() - smoothed_signal = smoothed_signal.fillna(method="bfill").fillna(method="ffill") - noise = data_series - smoothed_signal - noise_flat = noise[start_index_noise:end_index_noise] - - if figure: - plt.figure(figsize=(8, 8)) - plt.subplot(2, 1, 1) - plt.plot(np.arange(len(smoothed_signal)), smoothed_signal, "r.-") - plt.title("Smoothed data averaged by 5 data poins") - - plt.subplot(2, 1, 2) - plt.plot(np.arange(len(noise)), noise, "r.-") - plt.axvspan(start_index_noise, end_index_noise, color="c", alpha=0.5) - plt.title("Random noise and the region for the final standard deviation") - - noise_std = np.std(noise_flat) - print(noise_std) - random_noise_list.append(noise_std) - - noise = list(zip(range(len(data_list)), random_noise_list)) - if len(noise) == 1: - scan_num = noise[0][0] - else: - for i in range(1, len(noise)): - current_value = noise[i][1] - if current_value < 0.1: - scan_num = noise[i][0] - break - else: - scan_num = noise[-1][0] - - if figure: - plt.figure() - plt.plot(range(len(data_list)), random_noise_list) - plt.xlabel("scan number") - plt.ylabel("random noise (%)") - - return scan_num From 1d2fa2180fa99147ca9ba1d53a3551b91905eb8a Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Fri, 24 Jan 2025 11:00:47 -0600 Subject: [PATCH 20/47] Git remove again --- src/firefly/tests/=0.9.0a1 | 43 -------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 src/firefly/tests/=0.9.0a1 diff --git a/src/firefly/tests/=0.9.0a1 b/src/firefly/tests/=0.9.0a1 deleted file mode 100644 index 8b361d66..00000000 --- a/src/firefly/tests/=0.9.0a1 +++ /dev/null @@ -1,43 +0,0 @@ -Requirement already satisfied: ophyd-async in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (0.5.0) -Requirement already satisfied: networkx>=2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (3.2.1) -Requirement already satisfied: numpy<2.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.26.4) -Requirement already satisfied: packaging in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (24.1) -Requirement already satisfied: pint in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (0.24.3) -Requirement already satisfied: bluesky>=1.13.0a3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.13.0a4) -Requirement already satisfied: event-model in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (1.21.0) -Requirement already satisfied: p4p in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (4.1.12) -Requirement already satisfied: pyyaml in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (6.0.2) -Requirement already satisfied: colorlog in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (6.8.2) -Requirement already satisfied: pydantic>=2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (2.8.2) -Requirement already satisfied: pydantic-numpy in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ophyd-async) (5.0.2) -Requirement already satisfied: cycler in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.12.1) -Requirement already satisfied: historydict in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (1.2.6) -Requirement already satisfied: msgpack in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (1.0.8) -Requirement already satisfied: msgpack-numpy in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.4.8) -Requirement already satisfied: toolz in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (0.12.1) -Requirement already satisfied: tqdm>=4.44 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (4.66.5) -Requirement already satisfied: zict<3.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (2.2.0) -Requirement already satisfied: typing-extensions>=4.0.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from bluesky>=1.13.0a3->ophyd-async) (4.12.2) -Requirement already satisfied: importlib-resources in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from event-model->ophyd-async) (6.4.0) -Requirement already satisfied: jsonschema>=3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from event-model->ophyd-async) (4.23.0) -Requirement already satisfied: annotated-types>=0.4.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic>=2.0->ophyd-async) (0.7.0) -Requirement already satisfied: pydantic-core==2.20.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic>=2.0->ophyd-async) (2.20.1) -Requirement already satisfied: epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (7.0.7.99.0.2) -Requirement already satisfied: pvxslibs<1.4.0a1,>=1.3.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (1.3.1) -Requirement already satisfied: nose2>=0.8.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (0.15.1) -Requirement already satisfied: ply in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from p4p->ophyd-async) (3.11) -Requirement already satisfied: appdirs>=1.4.4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (1.4.4) -Requirement already satisfied: flexcache>=0.3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (0.3) -Requirement already satisfied: flexparser>=0.3 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pint->ophyd-async) (0.3.1) -Requirement already satisfied: compress-pickle[lz4] in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (2.1.0) -Requirement already satisfied: ruamel-yaml<0.19.0,>=0.18.5 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (0.18.6) -Requirement already satisfied: semver<4.0.0,>=3.0.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from pydantic-numpy->ophyd-async) (3.0.2) -Requirement already satisfied: setuptools in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2->p4p->ophyd-async) (72.1.0) -Requirement already satisfied: setuptools-dso>=2.9a1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from epicscorelibs<7.0.7.99.1,>=7.0.7.99.0.2->p4p->ophyd-async) (2.11a2) -Requirement already satisfied: attrs>=22.2.0 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (24.2.0) -Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (2023.12.1) -Requirement already satisfied: referencing>=0.28.4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (0.35.1) -Requirement already satisfied: rpds-py>=0.7.1 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from jsonschema>=3->event-model->ophyd-async) (0.20.0) -Requirement already satisfied: ruamel.yaml.clib>=0.2.7 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from ruamel-yaml<0.19.0,>=0.18.5->pydantic-numpy->ophyd-async) (0.2.8) -Requirement already satisfied: heapdict in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from zict<3.0.0->bluesky>=1.13.0a3->ophyd-async) (1.0.1) -Requirement already satisfied: lz4 in /home/beams3/YANNACHEN/miniforge3/envs/haven/lib/python3.10/site-packages (from compress-pickle[lz4]->pydantic-numpy->ophyd-async) (4.3.3) From 59955c2a20232350a6b731dc941c2623ca389933 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 29 Jan 2025 14:47:30 -0600 Subject: [PATCH 21/47] Update following ophyd-async v090a2 --- environment.yml | 2 +- pyproject.toml | 3 ++- src/haven/devices/detectors/aravis.py | 2 +- src/haven/devices/detectors/xspress.py | 26 ++++++++++++------------ src/haven/tests/test_aravis.py | 4 ++-- src/haven/tests/test_xspress.py | 28 +++++++++++++------------- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/environment.yml b/environment.yml index 9a5f8096..5448cb60 100644 --- a/environment.yml +++ b/environment.yml @@ -60,7 +60,7 @@ dependencies: # - bluesky >=1.13.1 - git+https://github.com/bluesky/bluesky.git@v1.13.1rc1 # Replace with pypi version once released - ophyd >=1.6.3 - - ophyd-async ==0.9.0a1 + - ophyd-async >=0.9.0a2 - apstools == 1.6.20 # Leave at 1.6.20 until this is fixed: https://github.com/BCDA-APS/apstools/issues/1022 - pcdsdevices # For extra signal types - pydm >=1.18.0 diff --git a/pyproject.toml b/pyproject.toml index 0186e4c9..beeaf92c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,8 @@ classifiers = [ "Topic :: System :: Hardware", ] keywords = ["synchrotron", "xray", "bluesky"] -dependencies = ["aioca", "aiokafka", "bluesky", "ophyd", "ophyd-async>=0.9.0a1", "apsbss", "xraydb", +dependencies = ["aioca", "aiokafka", "bluesky", "ophyd", +"ophyd-async>=0.9.0a2", "apsbss", "xraydb", "mergedeep", "xrayutilities", "bluesky-queueserver-api", "tomlkit", "apstools", "ophyd-registry", "caproto", "pcdsdevices", "bluesky-adaptive", "tiled[client]", "guarneri>=0.2.0"] diff --git a/src/haven/devices/detectors/aravis.py b/src/haven/devices/detectors/aravis.py index 5ca41563..a8687784 100644 --- a/src/haven/devices/detectors/aravis.py +++ b/src/haven/devices/detectors/aravis.py @@ -20,7 +20,7 @@ def __init__( path_provider = default_path_provider() super().__init__(*args, prefix=prefix, path_provider=path_provider, **kwargs) # Replace a signal that has different enum options - self.drv.trigger_source = epics_signal_rw_rbv( + self.driver.trigger_source = epics_signal_rw_rbv( AravisTriggerSource, # type: ignore f"{prefix}cam1:TriggerSource", ) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index 7cfde058..0b6d0416 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -172,7 +172,7 @@ def __init__( elements: int | Sequence[int] = 1, ev_per_bin: float = 10.0, drv_suffix="det1:", - hdf_suffix="HDF1:", + fileio_suffix="HDF1:", name: str = "", config_sigs: Sequence[SignalR] = (), ): @@ -185,8 +185,8 @@ def __init__( {idx: XspressElement(prefix, element_index=idx) for idx in elements} ) # Area detector IO devices - self.drv = XspressDriverIO(prefix + drv_suffix) - self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix) + self.driver = XspressDriverIO(prefix + drv_suffix) + self.hdf = adcore.NDFileHDFIO(prefix + fileio_suffix) if path_provider is None: path_provider = default_path_provider() @@ -194,17 +194,17 @@ def __init__( self.ev_per_bin, _ = soft_signal_r_and_setter(float, initial_value=ev_per_bin) super().__init__( - XspressController(self.drv), + XspressController(self.driver), adcore.ADHDFWriter( self.hdf, path_provider, lambda: self.name, - XspressDatasetDescriber(self.drv), - self.drv, # <- for DT ndattributes + XspressDatasetDescriber(self.driver), + self.driver, # <- for DT ndattributes ), config_sigs=( - self.drv.acquire_period, - self.drv.acquire_time, + self.driver.acquire_period, + self.driver.acquire_time, self.ev_per_bin, *config_sigs, ), @@ -214,11 +214,11 @@ def __init__( @AsyncStatus.wrap async def stage(self) -> None: self._old_xml_file, *_ = await asyncio.gather( - self.drv.nd_attributes_file.get_value(), + self.driver.nd_attributes_file.get_value(), super().stage(), self._controller.setup_ndattributes(device_name=self.name), - self.drv.erase_on_start.set(False), - self.drv.erase.trigger(), + self.driver.erase_on_start.set(False), + self.driver.erase.trigger(), ) @AsyncStatus.wrap @@ -226,12 +226,12 @@ async def unstage(self) -> None: await super().unstage() if self._old_xml_file is not None: # Restore the original XML attributes file - await self.drv.nd_attributes_file.set(self._old_xml_file) + await self.driver.nd_attributes_file.set(self._old_xml_file) self._old_xml_file = None @property def default_time_signal(self): - return self.drv.acquire_time + return self.driver.acquire_time def ndattribute_xml(params): diff --git a/src/haven/tests/test_aravis.py b/src/haven/tests/test_aravis.py index 9a605a18..0736b9a7 100644 --- a/src/haven/tests/test_aravis.py +++ b/src/haven/tests/test_aravis.py @@ -27,7 +27,7 @@ async def test_camera_trigger_source_choices(camera): > data_type: NotConnected: ca://25idcARV4:HDF1:NDDataType_RBV """ - desc = await camera.drv.trigger_source.describe() + desc = await camera.driver.trigger_source.describe() choices = desc["s255id-gige-A-drv-trigger_source"]["choices"] assert "Software" in choices assert "Line1" in choices @@ -46,7 +46,7 @@ async def test_camera_signals(camera): > data_type: NotConnected: ca://25idcARV4:HDF1:NDDataType_RBV """ - desc = await camera.drv.data_type.describe() + desc = await camera.driver.data_type.describe() cam_source = desc["s255id-gige-A-drv-data_type"]["source"] assert cam_source == "mock+ca://255idgigeA:cam1:DataType_RBV" # Check HDF signal source diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index f0a42bb7..1a9414dc 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -23,9 +23,9 @@ async def test_signals(detector): assert await detector.ev_per_bin.get_value() == 10 # Spot-check some PVs assert ( - detector.drv.acquire_time.source == "mock+ca://255id_xsp:det1:AcquireTime_RBV" + detector.driver.acquire_time.source == "mock+ca://255id_xsp:det1:AcquireTime_RBV" ) - assert detector.drv.acquire.source == "mock+ca://255id_xsp:det1:Acquire_RBV" + assert detector.driver.acquire.source == "mock+ca://255id_xsp:det1:Acquire_RBV" # Individual element's signals assert len(detector.elements) == 4 elem0 = detector.elements[0] @@ -45,14 +45,14 @@ async def test_trigger(detector): set_mock_value(detector.hdf.num_captured, 1) await status # Check that signals were set - get_mock_put(detector.drv.num_images).assert_called_once_with(1, wait=True) + get_mock_put(detector.driver.num_images).assert_called_once_with(1, wait=True) async def test_stage(detector): - assert not get_mock_put(detector.drv.erase).called + assert not get_mock_put(detector.driver.erase).called await detector.stage() - get_mock_put(detector.drv.erase_on_start).assert_called_once_with(False, wait=True) - assert get_mock_put(detector.drv.erase).called + get_mock_put(detector.driver.erase_on_start).assert_called_once_with(False, wait=True) + assert get_mock_put(detector.driver.erase).called async def test_descriptor(detector): @@ -64,10 +64,10 @@ async def test_descriptor(detector): """ # With deadtime correction off, we should get unsigned longs - await detector.drv.deadtime_correction.set(False) + await detector.driver.deadtime_correction.set(False) assert await detector.writer._dataset_describer.np_datatype() == " Date: Wed, 29 Jan 2025 15:59:11 -0600 Subject: [PATCH 22/47] Change _drv to driver --- src/haven/devices/detectors/xspress.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index 0b6d0416..583aa27d 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -51,7 +51,7 @@ def __init__(self, prefix, name=""): class XspressController(DetectorController): def __init__(self, driver: adcore.ADBaseIO) -> None: - self._drv = driver + self.driver = driver def get_deadtime(self, exposure: float) -> float: # Arbitrary value. To-do: fill this in when we know what to @@ -59,22 +59,22 @@ def get_deadtime(self, exposure: float) -> float: return 0.001 async def setup_ndattributes(self, device_name: str): - num_elements = await self._drv.number_of_elements.get_value() + num_elements = await self.driver.number_of_elements.get_value() params = ndattribute_params( device_name=device_name, elements=range(num_elements) ) xml = ndattribute_xml(params) - await self._drv.nd_attributes_file.set(xml) + await self.driver.nd_attributes_file.set(xml) @AsyncStatus.wrap async def prepare(self, trigger_info: TriggerInfo): await asyncio.gather( - self._drv.num_images.set(trigger_info.total_number_of_triggers), - self._drv.image_mode.set(adcore.ImageMode.MULTIPLE), - self._drv.trigger_mode.set(XspressTriggerMode.INTERNAL), + self.driver.num_images.set(trigger_info.total_number_of_triggers), + self.driver.image_mode.set(adcore.ImageMode.MULTIPLE), + self.driver.trigger_mode.set(XspressTriggerMode.INTERNAL), # Hardware deadtime correciton is not reliable # https://github.com/epics-modules/xspress3/issues/57 - self._drv.deadtime_correction.set(False), + self.driver.deadtime_correction.set(False), ) async def wait_for_idle(self): @@ -83,11 +83,11 @@ async def wait_for_idle(self): async def arm(self): self._arm_status = await adcore.start_acquiring_driver_and_ensure_status( - self._drv + self.driver ) async def disarm(self): - await adcore.stop_busy_record(self._drv.acquire, False, timeout=1) + await adcore.stop_busy_record(self.driver.acquire, False, timeout=1) class XspressDatasetDescriber(adcore.ADBaseDatasetDescriber): From cb98e0e2ae654b634502f53b41343ffd2463d0d7 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 29 Jan 2025 20:30:19 -0600 Subject: [PATCH 23/47] black --- src/haven/tests/test_xspress.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index 1a9414dc..f1c0ef14 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -23,7 +23,8 @@ async def test_signals(detector): assert await detector.ev_per_bin.get_value() == 10 # Spot-check some PVs assert ( - detector.driver.acquire_time.source == "mock+ca://255id_xsp:det1:AcquireTime_RBV" + detector.driver.acquire_time.source + == "mock+ca://255id_xsp:det1:AcquireTime_RBV" ) assert detector.driver.acquire.source == "mock+ca://255id_xsp:det1:Acquire_RBV" # Individual element's signals @@ -51,7 +52,9 @@ async def test_trigger(detector): async def test_stage(detector): assert not get_mock_put(detector.driver.erase).called await detector.stage() - get_mock_put(detector.driver.erase_on_start).assert_called_once_with(False, wait=True) + get_mock_put(detector.driver.erase_on_start).assert_called_once_with( + False, wait=True + ) assert get_mock_put(detector.driver.erase).called From addbaa480746e936f25d617e08678e4d73282ee9 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 29 Jan 2025 22:24:51 -0600 Subject: [PATCH 24/47] Update test files for AD --- src/haven/tests/test_aravis.py | 8 ++++---- src/haven/tests/test_xspress.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/haven/tests/test_aravis.py b/src/haven/tests/test_aravis.py index 0736b9a7..e9bb5385 100644 --- a/src/haven/tests/test_aravis.py +++ b/src/haven/tests/test_aravis.py @@ -28,7 +28,7 @@ async def test_camera_trigger_source_choices(camera): """ desc = await camera.driver.trigger_source.describe() - choices = desc["s255id-gige-A-drv-trigger_source"]["choices"] + choices = desc["s255id-gige-A-driver-trigger_source"]["choices"] assert "Software" in choices assert "Line1" in choices @@ -47,11 +47,11 @@ async def test_camera_signals(camera): """ desc = await camera.driver.data_type.describe() - cam_source = desc["s255id-gige-A-drv-data_type"]["source"] + cam_source = desc["s255id-gige-A-driver-data_type"]["source"] assert cam_source == "mock+ca://255idgigeA:cam1:DataType_RBV" # Check HDF signal source - desc = await camera.hdf.data_type.describe() - hdf_source = desc["s255id-gige-A-hdf-data_type"]["source"] + desc = await camera.fileio.data_type.describe() + hdf_source = desc["s255id-gige-A-fileio-data_type"]["source"] assert hdf_source == "mock+ca://255idgigeA:HDF1:DataType_RBV" diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index f1c0ef14..fa3b646e 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -68,10 +68,10 @@ async def test_descriptor(detector): """ # With deadtime correction off, we should get unsigned longs await detector.driver.deadtime_correction.set(False) - assert await detector.writer._dataset_describer.np_datatype() == " Date: Wed, 29 Jan 2025 22:33:28 -0600 Subject: [PATCH 25/47] change 'hdf' to 'fileio' --- src/haven/devices/detectors/xspress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index 583aa27d..d74d35ac 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -186,7 +186,7 @@ def __init__( ) # Area detector IO devices self.driver = XspressDriverIO(prefix + drv_suffix) - self.hdf = adcore.NDFileHDFIO(prefix + fileio_suffix) + self.fileio = adcore.NDFileHDFIO(prefix + fileio_suffix) if path_provider is None: path_provider = default_path_provider() @@ -196,7 +196,7 @@ def __init__( super().__init__( XspressController(self.driver), adcore.ADHDFWriter( - self.hdf, + self.fileio, path_provider, lambda: self.name, XspressDatasetDescriber(self.driver), From 3d4f00ef7d1aa7a9210141dc470f376cf551cded Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 29 Jan 2025 22:45:01 -0600 Subject: [PATCH 26/47] Update test file from 'hdf' to 'fileio' --- src/haven/tests/test_xspress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index fa3b646e..25c17bae 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -15,7 +15,7 @@ async def detector(): det = Xspress3Detector("255id_xsp:", name="vortex_me4", elements=4) await det.connect(mock=True) - set_mock_value(det.hdf.file_path_exists, True) + set_mock_value(det.fileio.file_path_exists, True) return det From 1e506816b72a8ca49ad359d4538d3384c8461116 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 29 Jan 2025 23:41:24 -0600 Subject: [PATCH 27/47] update sim_detector --- src/haven/devices/detectors/sim_detector.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/haven/devices/detectors/sim_detector.py b/src/haven/devices/detectors/sim_detector.py index faa9b01d..a866a234 100644 --- a/src/haven/devices/detectors/sim_detector.py +++ b/src/haven/devices/detectors/sim_detector.py @@ -1,7 +1,11 @@ from ophyd_async.epics.adsimdetector import SimDetector as SimDetectorBase - +from haven._iconfig import load_config from .area_detectors import HavenDetector class SimDetector(HavenDetector, SimDetectorBase): _ophyd_labels_ = {"area_detectors", "detectors"} + + def __init__(self, prefix, path_provider=None, **kwargs): + path_provider = path_provider or load_config().get("area_detector_root_path", "/default/path") + super().__init__(prefix=prefix, path_provider=path_provider, **kwargs) From e8ce44f3e8e74719b551cabda8d6052847b9bdb2 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 00:07:40 -0600 Subject: [PATCH 28/47] black --- src/haven/devices/detectors/sim_detector.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/haven/devices/detectors/sim_detector.py b/src/haven/devices/detectors/sim_detector.py index a866a234..ea37852b 100644 --- a/src/haven/devices/detectors/sim_detector.py +++ b/src/haven/devices/detectors/sim_detector.py @@ -1,5 +1,5 @@ from ophyd_async.epics.adsimdetector import SimDetector as SimDetectorBase -from haven._iconfig import load_config +from haven._iconfig import load_config from .area_detectors import HavenDetector @@ -7,5 +7,7 @@ class SimDetector(HavenDetector, SimDetectorBase): _ophyd_labels_ = {"area_detectors", "detectors"} def __init__(self, prefix, path_provider=None, **kwargs): - path_provider = path_provider or load_config().get("area_detector_root_path", "/default/path") + path_provider = path_provider or load_config().get( + "area_detector_root_path", "/default/path" + ) super().__init__(prefix=prefix, path_provider=path_provider, **kwargs) From d12742d8ebd1c39e3be6171f88973c070d9fb174 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 13:12:45 -0600 Subject: [PATCH 29/47] update environment for qt designer --- environment-designer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment-designer.yml b/environment-designer.yml index def133f4..ab229e35 100644 --- a/environment-designer.yml +++ b/environment-designer.yml @@ -81,3 +81,4 @@ dependencies: # --- Extra Qt UI packages - qtawesome - qasync + - dask >= 2022 From 6e11498aa4f2b52c1c06c0f3022addf5b796adaf Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 19:57:13 -0600 Subject: [PATCH 30/47] Add dask --- src/haven/tests/test_xspress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index 25c17bae..f3f767fb 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -43,7 +43,7 @@ async def test_description(detector): async def test_trigger(detector): status = detector.trigger() await asyncio.sleep(0.1) # Let the event loop turn - set_mock_value(detector.hdf.num_captured, 1) + set_mock_value(detector.fileio.num_captured, 1) await status # Check that signals were set get_mock_put(detector.driver.num_images).assert_called_once_with(1, wait=True) From ace388d867d63f29701c77e99960e2807dd19fa0 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 21:36:45 -0600 Subject: [PATCH 31/47] pytest xspress failed for test_trigger --- src/haven/devices/detectors/xspress.py | 21 +++++++++++++-------- src/haven/tests/test_xspress.py | 8 ++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index d74d35ac..da46c3ea 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -27,6 +27,7 @@ from .area_detectors import HavenDetector, default_path_provider +from ophyd_async.epics.adcore import AreaDetector class XspressTriggerMode(StrictEnum): SOFTWARE = "Software" @@ -135,7 +136,7 @@ def __init__(self, prefix: str, element_index: int, name: str = ""): super().__init__(name=name) -class Xspress3Detector(HavenDetector, StandardDetector): +class Xspress3Detector(HavenDetector, AreaDetector): """A detector controlled by Xspress3 electronics. The elements of the detector are represented on the *mcas* @@ -187,6 +188,8 @@ def __init__( # Area detector IO devices self.driver = XspressDriverIO(prefix + drv_suffix) self.fileio = adcore.NDFileHDFIO(prefix + fileio_suffix) + + self.plugins = {"hdf": self.fileio} if path_provider is None: path_provider = default_path_provider() @@ -194,14 +197,16 @@ def __init__( self.ev_per_bin, _ = soft_signal_r_and_setter(float, initial_value=ev_per_bin) super().__init__( - XspressController(self.driver), - adcore.ADHDFWriter( - self.fileio, - path_provider, - lambda: self.name, - XspressDatasetDescriber(self.driver), - self.driver, # <- for DT ndattributes + controller=XspressController(self.driver), + writer=adcore.ADHDFWriter( + fileio=self.fileio, + path_provider=path_provider, + name_provider=lambda: self.name, + dataset_describer=XspressDatasetDescriber(self.driver), + #driver=self.driver, # <- for DT ndattributes + plugins=self.plugins ), + plugins=self.plugins, config_sigs=( self.driver.acquire_period, self.driver.acquire_time, diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index f3f767fb..bdc25541 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -15,7 +15,7 @@ async def detector(): det = Xspress3Detector("255id_xsp:", name="vortex_me4", elements=4) await det.connect(mock=True) - set_mock_value(det.fileio.file_path_exists, True) + set_mock_value(det.plugins["hdf"].file_path_exists, True) return det @@ -39,11 +39,11 @@ async def test_description(detector): config = await detector.read_configuration() assert f"{detector.name}-ev_per_bin" in config - +@pytest.mark.asyncio async def test_trigger(detector): - status = detector.trigger() + status = await detector.trigger() await asyncio.sleep(0.1) # Let the event loop turn - set_mock_value(detector.fileio.num_captured, 1) + set_mock_value(detector.plugins["hdf"].num_captured, 1) await status # Check that signals were set get_mock_put(detector.driver.num_images).assert_called_once_with(1, wait=True) From 2b9cf4146d2df860e3beaed6eb1677de0ab52a14 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 23:23:23 -0600 Subject: [PATCH 32/47] black and isort --- src/haven/devices/detectors/sim_detector.py | 2 ++ src/haven/devices/detectors/xspress.py | 8 ++++---- src/haven/tests/test_xspress.py | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/haven/devices/detectors/sim_detector.py b/src/haven/devices/detectors/sim_detector.py index ea37852b..e5c59523 100644 --- a/src/haven/devices/detectors/sim_detector.py +++ b/src/haven/devices/detectors/sim_detector.py @@ -1,5 +1,7 @@ from ophyd_async.epics.adsimdetector import SimDetector as SimDetectorBase + from haven._iconfig import load_config + from .area_detectors import HavenDetector diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index da46c3ea..9409638b 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -17,6 +17,7 @@ soft_signal_r_and_setter, ) from ophyd_async.epics import adcore +from ophyd_async.epics.adcore import AreaDetector from ophyd_async.epics.adcore._utils import ( ADBaseDataType, NDAttributeDataType, @@ -27,7 +28,6 @@ from .area_detectors import HavenDetector, default_path_provider -from ophyd_async.epics.adcore import AreaDetector class XspressTriggerMode(StrictEnum): SOFTWARE = "Software" @@ -188,7 +188,7 @@ def __init__( # Area detector IO devices self.driver = XspressDriverIO(prefix + drv_suffix) self.fileio = adcore.NDFileHDFIO(prefix + fileio_suffix) - + self.plugins = {"hdf": self.fileio} if path_provider is None: @@ -203,8 +203,8 @@ def __init__( path_provider=path_provider, name_provider=lambda: self.name, dataset_describer=XspressDatasetDescriber(self.driver), - #driver=self.driver, # <- for DT ndattributes - plugins=self.plugins + # driver=self.driver, # <- for DT ndattributes + plugins=self.plugins, ), plugins=self.plugins, config_sigs=( diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index bdc25541..7ae04e69 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -39,6 +39,7 @@ async def test_description(detector): config = await detector.read_configuration() assert f"{detector.name}-ev_per_bin" in config + @pytest.mark.asyncio async def test_trigger(detector): status = await detector.trigger() From bc0fc82f1ce74482d44e2d84adb250aa1eb5a145 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 30 Jan 2025 23:33:55 -0600 Subject: [PATCH 33/47] flake --- src/haven/devices/detectors/xspress.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index 9409638b..d07cd903 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -11,7 +11,6 @@ DeviceVector, PathProvider, SignalR, - StandardDetector, StrictEnum, TriggerInfo, soft_signal_r_and_setter, From d2e143174f9b13bf0945ed422e11f10ea01cd044 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 22:39:26 -0600 Subject: [PATCH 34/47] Remove arm() disarm() wait_for_idle() --- src/haven/devices/detectors/xspress.py | 18 +++--------------- src/haven/tests/test_xspress.py | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index d07cd903..c42ed1c7 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -16,7 +16,7 @@ soft_signal_r_and_setter, ) from ophyd_async.epics import adcore -from ophyd_async.epics.adcore import AreaDetector +from ophyd_async.epics.adcore import AreaDetector, ADBaseController from ophyd_async.epics.adcore._utils import ( ADBaseDataType, NDAttributeDataType, @@ -49,8 +49,9 @@ def __init__(self, prefix, name=""): super().__init__(prefix=prefix, name=name) -class XspressController(DetectorController): +class XspressController(ADBaseController): def __init__(self, driver: adcore.ADBaseIO) -> None: + super().__init__(driver) self.driver = driver def get_deadtime(self, exposure: float) -> float: @@ -77,19 +78,6 @@ async def prepare(self, trigger_info: TriggerInfo): self.driver.deadtime_correction.set(False), ) - async def wait_for_idle(self): - if self._arm_status: - await self._arm_status - - async def arm(self): - self._arm_status = await adcore.start_acquiring_driver_and_ensure_status( - self.driver - ) - - async def disarm(self): - await adcore.stop_busy_record(self.driver.acquire, False, timeout=1) - - class XspressDatasetDescriber(adcore.ADBaseDatasetDescriber): """The datatype cannot be reliably determined from DataType_RBV. diff --git a/src/haven/tests/test_xspress.py b/src/haven/tests/test_xspress.py index 7ae04e69..b1c605e3 100644 --- a/src/haven/tests/test_xspress.py +++ b/src/haven/tests/test_xspress.py @@ -42,7 +42,7 @@ async def test_description(detector): @pytest.mark.asyncio async def test_trigger(detector): - status = await detector.trigger() + status = detector.trigger() await asyncio.sleep(0.1) # Let the event loop turn set_mock_value(detector.plugins["hdf"].num_captured, 1) await status From f49aa9e385f153036e2cfbe654d620bad31d6243 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 22:44:36 -0600 Subject: [PATCH 35/47] black and isort --- src/haven/devices/area_detector.py | 9 ++------- src/haven/devices/detectors/xspress.py | 3 ++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index b1db1e4e..bd1fe76b 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -9,9 +9,7 @@ import pandas as pd from apstools.devices import CamMixin_V34, SingleTrigger_V34 from ophyd import ADComponent as ADCpt -from ophyd import ( - CamBase, -) +from ophyd import CamBase from ophyd import Component as Cpt from ophyd import DetectorBase as OphydDetectorBase from ophyd import ( @@ -44,10 +42,7 @@ ) from ophyd.areadetector.plugins import StatsPlugin_V31 as OphydStatsPlugin_V31 from ophyd.areadetector.plugins import StatsPlugin_V34 as OphydStatsPlugin_V34 -from ophyd.areadetector.plugins import ( - TIFFPlugin_V31, - TIFFPlugin_V34, -) +from ophyd.areadetector.plugins import TIFFPlugin_V31, TIFFPlugin_V34 from ophyd.flyers import FlyerInterface from ophyd.sim import make_fake_device from ophyd.status import Status, StatusBase, SubscriptionStatus diff --git a/src/haven/devices/detectors/xspress.py b/src/haven/devices/detectors/xspress.py index c42ed1c7..1ba2e37a 100644 --- a/src/haven/devices/detectors/xspress.py +++ b/src/haven/devices/detectors/xspress.py @@ -16,7 +16,7 @@ soft_signal_r_and_setter, ) from ophyd_async.epics import adcore -from ophyd_async.epics.adcore import AreaDetector, ADBaseController +from ophyd_async.epics.adcore import ADBaseController, AreaDetector from ophyd_async.epics.adcore._utils import ( ADBaseDataType, NDAttributeDataType, @@ -78,6 +78,7 @@ async def prepare(self, trigger_info: TriggerInfo): self.driver.deadtime_correction.set(False), ) + class XspressDatasetDescriber(adcore.ADBaseDatasetDescriber): """The datatype cannot be reliably determined from DataType_RBV. From 4f8b3edcabd47ca17edf7e6bd119040553facf0d Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 23:05:43 -0600 Subject: [PATCH 36/47] isort again --- src/haven/devices/area_detector.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index bd1fe76b..b1db1e4e 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -9,7 +9,9 @@ import pandas as pd from apstools.devices import CamMixin_V34, SingleTrigger_V34 from ophyd import ADComponent as ADCpt -from ophyd import CamBase +from ophyd import ( + CamBase, +) from ophyd import Component as Cpt from ophyd import DetectorBase as OphydDetectorBase from ophyd import ( @@ -42,7 +44,10 @@ ) from ophyd.areadetector.plugins import StatsPlugin_V31 as OphydStatsPlugin_V31 from ophyd.areadetector.plugins import StatsPlugin_V34 as OphydStatsPlugin_V34 -from ophyd.areadetector.plugins import TIFFPlugin_V31, TIFFPlugin_V34 +from ophyd.areadetector.plugins import ( + TIFFPlugin_V31, + TIFFPlugin_V34, +) from ophyd.flyers import FlyerInterface from ophyd.sim import make_fake_device from ophyd.status import Status, StatusBase, SubscriptionStatus From 3de9c21540c4168ad05c5a925169f05ae4f0db46 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 23:30:50 -0600 Subject: [PATCH 37/47] isort mannually --- src/haven/devices/area_detector.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index b1db1e4e..de0c46e7 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -8,19 +8,16 @@ import numpy as np import pandas as pd from apstools.devices import CamMixin_V34, SingleTrigger_V34 -from ophyd import ADComponent as ADCpt from ophyd import ( + ADComponent as ADCpt, CamBase, -) -from ophyd import Component as Cpt -from ophyd import DetectorBase as OphydDetectorBase -from ophyd import ( + Component as Cpt, + DetectorBase as OphydDetectorBase, Device, EigerDetectorCam, EpicsSignal, Kind, Lambda750kCam, - OphydObject, Signal, SimDetectorCam, SingleTrigger, @@ -41,10 +38,8 @@ PvaPlugin_V34, ROIPlugin_V31, ROIPlugin_V34, -) -from ophyd.areadetector.plugins import StatsPlugin_V31 as OphydStatsPlugin_V31 -from ophyd.areadetector.plugins import StatsPlugin_V34 as OphydStatsPlugin_V34 -from ophyd.areadetector.plugins import ( + StatsPlugin_V31 as OphydStatsPlugin_V31, + StatsPlugin_V34 as OphydStatsPlugin_V34, TIFFPlugin_V31, TIFFPlugin_V34, ) From 5a2a0a104b15278ec515e5b34d30583d2b1dc68a Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 23:39:52 -0600 Subject: [PATCH 38/47] import package --- src/haven/devices/area_detector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index de0c46e7..41eaea70 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -18,6 +18,7 @@ EpicsSignal, Kind, Lambda750kCam, + OphydObject, Signal, SimDetectorCam, SingleTrigger, From f72eec1dad8b2ddee6937c8cb37d57ae456835bc Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Tue, 11 Feb 2025 23:49:14 -0600 Subject: [PATCH 39/47] isort --- src/haven/devices/area_detector.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index 41eaea70..bd1fe76b 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -8,11 +8,11 @@ import numpy as np import pandas as pd from apstools.devices import CamMixin_V34, SingleTrigger_V34 +from ophyd import ADComponent as ADCpt +from ophyd import CamBase +from ophyd import Component as Cpt +from ophyd import DetectorBase as OphydDetectorBase from ophyd import ( - ADComponent as ADCpt, - CamBase, - Component as Cpt, - DetectorBase as OphydDetectorBase, Device, EigerDetectorCam, EpicsSignal, @@ -39,11 +39,10 @@ PvaPlugin_V34, ROIPlugin_V31, ROIPlugin_V34, - StatsPlugin_V31 as OphydStatsPlugin_V31, - StatsPlugin_V34 as OphydStatsPlugin_V34, - TIFFPlugin_V31, - TIFFPlugin_V34, ) +from ophyd.areadetector.plugins import StatsPlugin_V31 as OphydStatsPlugin_V31 +from ophyd.areadetector.plugins import StatsPlugin_V34 as OphydStatsPlugin_V34 +from ophyd.areadetector.plugins import TIFFPlugin_V31, TIFFPlugin_V34 from ophyd.flyers import FlyerInterface from ophyd.sim import make_fake_device from ophyd.status import Status, StatusBase, SubscriptionStatus From 25632c25e28bd4d96c16520b3de0ecfab9304c27 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Mon, 17 Feb 2025 15:21:04 -0600 Subject: [PATCH 40/47] Added an overlay plugin for area detectors. --- src/haven/devices/detectors/overlay.py | 56 +++++++++++++++++++++ src/haven/devices/detectors/sim_detector.py | 2 + src/haven/tests/test_overlay.py | 31 ++++++++++++ src/haven/tests/test_sim_detector.py | 24 +++++++++ 4 files changed, 113 insertions(+) create mode 100644 src/haven/devices/detectors/overlay.py create mode 100644 src/haven/tests/test_overlay.py create mode 100644 src/haven/tests/test_sim_detector.py diff --git a/src/haven/devices/detectors/overlay.py b/src/haven/devices/detectors/overlay.py new file mode 100644 index 00000000..e0f0baae --- /dev/null +++ b/src/haven/devices/detectors/overlay.py @@ -0,0 +1,56 @@ +from ophyd_async.epics.adcore._core_io import NDPluginBaseIO +from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv +from ophyd_async.core import DeviceVector, Device, StrictEnum + + +class Shape(StrictEnum): + CROSS = "Cross" + RECTANGLE = "Rectangle" + ELLIPSE = "Ellipse" + TEXT = "Text" + + +class DrawMode(StrictEnum): + SET = "Set" + XOR = "XOR" + + +class Font(StrictEnum): + SIX_BY_THIRTEEN = "6x13" + SIX_BY_THIRTEEN_BOLD = "6x13 Bold" + NINE_BY_FIFTEEN = "9x15" + NINE_BY_FIFTEEN_BOLD = "9x15 Bold" + + +class Overlay(Device): + def __init__(self, prefix: str, name: str = ""): + # Modal parameters + self.use = epics_signal_rw_rbv(bool, f"{prefix}Use") + self.description = epics_signal_rw_rbv(str, f"{prefix}Name") + self.shape = epics_signal_rw_rbv(Shape, f"{prefix}Shape") + self.draw_mode = epics_signal_rw_rbv(DrawMode, f"{prefix}DrawMode") + self.red = epics_signal_rw_rbv(int, f"{prefix}Red") + self.green = epics_signal_rw_rbv(int, f"{prefix}Green") + self.blue = epics_signal_rw_rbv(int, f"{prefix}Blue") + self.display_text = epics_signal_rw_rbv(str, f"{prefix}DisplayText") + self.time_format = epics_signal_rw_rbv(str, f"{prefix}TimeStampFormat") + self.font = epics_signal_rw_rbv(Font, f"{prefix}Font") + # Overlay parameters + self.position_x = epics_signal_rw_rbv(int, f"{prefix}PositionX") + self.center_x = epics_signal_rw_rbv(int, f"{prefix}CenterX") + self.size_x = epics_signal_rw_rbv(int, f"{prefix}SizeX") + self.width_x = epics_signal_rw_rbv(int, f"{prefix}WidthX") + self.position_y = epics_signal_rw_rbv(int, f"{prefix}PositionY") + self.center_y = epics_signal_rw_rbv(int, f"{prefix}CenterY") + self.size_y = epics_signal_rw_rbv(int, f"{prefix}SizeY") + self.width_y = epics_signal_rw_rbv(int, f"{prefix}WidthY") + super().__init__(name=name) + + +class OverlayPlugin(NDPluginBaseIO): + def __init__(self, prefix: str, name: str = "", *args, **kwargs): + overlays = 7 + self.overlays = DeviceVector({ + idx: Overlay(f"{prefix}{idx+1}:") for idx in range(overlays) + }) + super().__init__(prefix=prefix, name=name, *args, **kwargs) diff --git a/src/haven/devices/detectors/sim_detector.py b/src/haven/devices/detectors/sim_detector.py index 621b8bbb..4984079e 100644 --- a/src/haven/devices/detectors/sim_detector.py +++ b/src/haven/devices/detectors/sim_detector.py @@ -2,12 +2,14 @@ from ophyd_async.epics.adsimdetector import SimDetector as SimDetectorBase from .area_detectors import default_path_provider +from .overlay import OverlayPlugin class SimDetector(SimDetectorBase): _ophyd_labels_ = {"area_detectors", "detectors"} def __init__(self, prefix, path_provider: PathProvider | None = None, **kwargs): + self.overlay = OverlayPlugin(f"{prefix}Over1:") if path_provider is None: path_provider = default_path_provider() super().__init__(prefix=prefix, path_provider=path_provider, **kwargs) diff --git a/src/haven/tests/test_overlay.py b/src/haven/tests/test_overlay.py new file mode 100644 index 00000000..00ba079a --- /dev/null +++ b/src/haven/tests/test_overlay.py @@ -0,0 +1,31 @@ +import pytest + +from haven.devices.detectors.overlay import OverlayPlugin + + +@pytest.fixture() +async def overlay(): + ovrly = OverlayPlugin(name="overlay", prefix="255idSimDet:Over1:") + await ovrly.connect(mock=True) + return ovrly + + +def test_signals(overlay): + assert overlay.overlays[0].use.source == "mock+ca://255idSimDet:Over1:1:Use_RBV" + assert overlay.overlays[0].description.source == "mock+ca://255idSimDet:Over1:1:Name_RBV" + assert overlay.overlays[0].shape.source == "mock+ca://255idSimDet:Over1:1:Shape_RBV" + assert overlay.overlays[0].draw_mode.source == "mock+ca://255idSimDet:Over1:1:DrawMode_RBV" + assert overlay.overlays[0].red.source == "mock+ca://255idSimDet:Over1:1:Red_RBV" + assert overlay.overlays[0].green.source == "mock+ca://255idSimDet:Over1:1:Green_RBV" + assert overlay.overlays[0].blue.source == "mock+ca://255idSimDet:Over1:1:Blue_RBV" + assert overlay.overlays[0].display_text.source == "mock+ca://255idSimDet:Over1:1:DisplayText_RBV" + assert overlay.overlays[0].time_format.source == "mock+ca://255idSimDet:Over1:1:TimeStampFormat_RBV" + assert overlay.overlays[0].font.source == "mock+ca://255idSimDet:Over1:1:Font_RBV" + assert overlay.overlays[0].position_x.source == "mock+ca://255idSimDet:Over1:1:PositionX_RBV" + assert overlay.overlays[0].center_x.source == "mock+ca://255idSimDet:Over1:1:CenterX_RBV" + assert overlay.overlays[0].size_x.source == "mock+ca://255idSimDet:Over1:1:SizeX_RBV" + assert overlay.overlays[0].width_x.source == "mock+ca://255idSimDet:Over1:1:WidthX_RBV" + assert overlay.overlays[0].position_y.source == "mock+ca://255idSimDet:Over1:1:PositionY_RBV" + assert overlay.overlays[0].center_y.source == "mock+ca://255idSimDet:Over1:1:CenterY_RBV" + assert overlay.overlays[0].size_y.source == "mock+ca://255idSimDet:Over1:1:SizeY_RBV" + assert overlay.overlays[0].width_y.source == "mock+ca://255idSimDet:Over1:1:WidthY_RBV" diff --git a/src/haven/tests/test_sim_detector.py b/src/haven/tests/test_sim_detector.py new file mode 100644 index 00000000..d12cd2c0 --- /dev/null +++ b/src/haven/tests/test_sim_detector.py @@ -0,0 +1,24 @@ +import pytest + + +from haven.devices import SimDetector + + +@pytest.fixture() +async def detector(): + det = SimDetector(name="sim_detector", prefix="255idSimDet:") + await det.connect(mock=True) + return det + + + +def test_signals(detector): + assert detector.driver.detector_state.source == "mock+ca://255idSimDet:cam1:DetectorState_RBV" + assert detector.driver.acquire_time.source == "mock+ca://255idSimDet:cam1:AcquireTime_RBV" + assert detector.driver.acquire.source == "mock+ca://255idSimDet:cam1:Acquire_RBV" + # assert detector.driver.acquire_time_auto.source == "mock+ca://255idSimDet:cam1:AcquireTimeAuto" + assert detector.fileio.array_size0.source == "mock+ca://255idSimDet:HDF1:ArraySize0_RBV" + assert detector.fileio.array_size1.source == "mock+ca://255idSimDet:HDF1:ArraySize1_RBV" + # Plugins + assert detector.overlay.overlays[0].use.source == "mock+ca://255idSimDet:Over1:1:Use_RBV" + assert detector.pva.image == "mock+ca://255idSimDet:..." From f0d1d4b5be73e2313216388a1366ffb4e1c0fdab Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Mon, 17 Feb 2025 21:39:21 -0600 Subject: [PATCH 41/47] Added a PVA plugin to the sim detector. --- src/haven/devices/detectors/image_plugin.py | 9 +++++++ src/haven/devices/detectors/sim_detector.py | 2 ++ src/haven/tests/test_sim_detector.py | 29 +++++++++++++++------ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/haven/devices/detectors/image_plugin.py diff --git a/src/haven/devices/detectors/image_plugin.py b/src/haven/devices/detectors/image_plugin.py new file mode 100644 index 00000000..c0704298 --- /dev/null +++ b/src/haven/devices/detectors/image_plugin.py @@ -0,0 +1,9 @@ +import numpy as np +from ophyd_async.epics.adcore._core_io import NDPluginBaseIO +from ophyd_async.epics.core import epics_signal_r + + +class ImagePlugin(NDPluginBaseIO): + def __init__(self, prefix: str, name: str = "", protocol: str = "ca"): + self.image_array = epics_signal_r(np.ndarray, f"{protocol}://{prefix}ArrayData") + super().__init__(prefix=prefix, name=name) diff --git a/src/haven/devices/detectors/sim_detector.py b/src/haven/devices/detectors/sim_detector.py index 4984079e..3b44dcd8 100644 --- a/src/haven/devices/detectors/sim_detector.py +++ b/src/haven/devices/detectors/sim_detector.py @@ -2,6 +2,7 @@ from ophyd_async.epics.adsimdetector import SimDetector as SimDetectorBase from .area_detectors import default_path_provider +from .image_plugin import ImagePlugin from .overlay import OverlayPlugin @@ -10,6 +11,7 @@ class SimDetector(SimDetectorBase): def __init__(self, prefix, path_provider: PathProvider | None = None, **kwargs): self.overlay = OverlayPlugin(f"{prefix}Over1:") + self.pva = ImagePlugin(f"{prefix}Pva1:", protocol="pva") if path_provider is None: path_provider = default_path_provider() super().__init__(prefix=prefix, path_provider=path_provider, **kwargs) diff --git a/src/haven/tests/test_sim_detector.py b/src/haven/tests/test_sim_detector.py index d12cd2c0..1af84e9c 100644 --- a/src/haven/tests/test_sim_detector.py +++ b/src/haven/tests/test_sim_detector.py @@ -1,6 +1,5 @@ import pytest - from haven.devices import SimDetector @@ -11,14 +10,28 @@ async def detector(): return det - def test_signals(detector): - assert detector.driver.detector_state.source == "mock+ca://255idSimDet:cam1:DetectorState_RBV" - assert detector.driver.acquire_time.source == "mock+ca://255idSimDet:cam1:AcquireTime_RBV" + assert ( + detector.driver.detector_state.source + == "mock+ca://255idSimDet:cam1:DetectorState_RBV" + ) + assert ( + detector.driver.acquire_time.source + == "mock+ca://255idSimDet:cam1:AcquireTime_RBV" + ) assert detector.driver.acquire.source == "mock+ca://255idSimDet:cam1:Acquire_RBV" # assert detector.driver.acquire_time_auto.source == "mock+ca://255idSimDet:cam1:AcquireTimeAuto" - assert detector.fileio.array_size0.source == "mock+ca://255idSimDet:HDF1:ArraySize0_RBV" - assert detector.fileio.array_size1.source == "mock+ca://255idSimDet:HDF1:ArraySize1_RBV" + assert ( + detector.fileio.array_size0.source + == "mock+ca://255idSimDet:HDF1:ArraySize0_RBV" + ) + assert ( + detector.fileio.array_size1.source + == "mock+ca://255idSimDet:HDF1:ArraySize1_RBV" + ) # Plugins - assert detector.overlay.overlays[0].use.source == "mock+ca://255idSimDet:Over1:1:Use_RBV" - assert detector.pva.image == "mock+ca://255idSimDet:..." + assert ( + detector.overlay.overlays[0].use.source + == "mock+ca://255idSimDet:Over1:1:Use_RBV" + ) + assert detector.pva.image_array.source == "mock+pva://255idSimDet:Pva1:ArrayData" From 25483a102784857eef99540e4a6ba57ec56f86c2 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Fri, 21 Feb 2025 16:36:41 -0600 Subject: [PATCH 42/47] Added auto gain signal to Aravis detectors. --- src/haven/devices/detectors/aravis.py | 15 +++++++++++++++ src/haven/devices/detectors/eiger.py | 5 +++++ src/haven/tests/test_aravis.py | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/haven/devices/detectors/aravis.py b/src/haven/devices/detectors/aravis.py index a8dcffa4..2937f169 100644 --- a/src/haven/devices/detectors/aravis.py +++ b/src/haven/devices/detectors/aravis.py @@ -3,6 +3,9 @@ from ophyd_async.epics.core import epics_signal_rw_rbv from .area_detectors import default_path_provider +from .image_plugin import ImagePlugin +from .overlay import OverlayPlugin + class AravisTriggerSource(SubsetEnum): @@ -10,6 +13,12 @@ class AravisTriggerSource(SubsetEnum): LINE1 = "Line1" +class AutoGainMode(SubsetEnum): + OFF = "Off" + ONCE = "Once" + CONTINUOUS = "Continuous" + + class AravisDetector(AravisDetectorBase): _ophyd_labels_ = {"cameras", "detectors"} @@ -18,10 +27,16 @@ def __init__( ): if path_provider is None: path_provider = default_path_provider() + self.overlay = OverlayPlugin(f"{prefix}Over1:") + self.pva = ImagePlugin(f"{prefix}Pva1:", protocol="pva") super().__init__(*args, prefix=prefix, path_provider=path_provider, **kwargs) # Replace a signal that has different enum options self.driver.trigger_source = epics_signal_rw_rbv( AravisTriggerSource, # type: ignore f"{prefix}cam1:TriggerSource", ) + self.driver.gain_auto = epics_signal_rw_rbv( + AutoGainMode, # type: ignore + f"{prefix}cam1:GainAuto", + ) self.set_name(self.name) diff --git a/src/haven/devices/detectors/eiger.py b/src/haven/devices/detectors/eiger.py index 9cf8406f..4c8f5eb6 100644 --- a/src/haven/devices/detectors/eiger.py +++ b/src/haven/devices/detectors/eiger.py @@ -7,6 +7,8 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv from .area_detectors import default_path_provider +from .image_plugin import ImagePlugin +from .overlay import OverlayPlugin class EigerDriverIO(adcore.ADBaseIO): @@ -67,6 +69,9 @@ def __init__( ): if path_provider is None: path_provider = default_path_provider() + # Other (non-data) area detector plugins + self.overlay = OverlayPlugin(f"{prefix}Over1:") + self.pva = ImagePlugin(f"{prefix}Pva1:", protocol="pva") # Area detector IO devices driver = EigerDriverIO(f"{prefix}{drv_suffix}") controller = EigerController(driver) diff --git a/src/haven/tests/test_aravis.py b/src/haven/tests/test_aravis.py index e9bb5385..587038c0 100644 --- a/src/haven/tests/test_aravis.py +++ b/src/haven/tests/test_aravis.py @@ -18,7 +18,7 @@ async def camera(sim_registry): async def test_camera_trigger_source_choices(camera): """Confirm the camera device has the right signals. - Solves this inital error received when using detaul AravisDetector. + Solves this inital error received when using upstream AravisDetector. > drv: NotConnected: > trigger_source: TypeError: 25idcARV4:cam1:TriggerSource_RBV has choices ('Software', 'Line1', 'Line3', 'Action1'), which is not a superset of SubsetEnum['Freerun', 'Line1']. From 4d044166dcfae4ff11d1466b728789775212d168 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Fri, 21 Feb 2025 16:47:17 -0600 Subject: [PATCH 43/47] Fixed signals on aravis detector. --- src/haven/devices/detectors/aravis.py | 13 +++++++++++-- src/haven/tests/test_aravis.py | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/haven/devices/detectors/aravis.py b/src/haven/devices/detectors/aravis.py index 2937f169..96f3db00 100644 --- a/src/haven/devices/detectors/aravis.py +++ b/src/haven/devices/detectors/aravis.py @@ -13,7 +13,7 @@ class AravisTriggerSource(SubsetEnum): LINE1 = "Line1" -class AutoGainMode(SubsetEnum): +class AutoMode(SubsetEnum): OFF = "Off" ONCE = "Once" CONTINUOUS = "Continuous" @@ -35,8 +35,17 @@ def __init__( AravisTriggerSource, # type: ignore f"{prefix}cam1:TriggerSource", ) + self.driver.gain = epics_signal_rw_rbv( + float, # type: ignore + f"{prefix}cam1:Gain", + ) self.driver.gain_auto = epics_signal_rw_rbv( - AutoGainMode, # type: ignore + AutoMode, # type: ignore f"{prefix}cam1:GainAuto", ) + self.driver.acquire_time_auto = epics_signal_rw_rbv( + AutoMode, # type: ignore + f"{prefix}cam1:ExposureAuto", + ) + self.set_name(self.name) diff --git a/src/haven/tests/test_aravis.py b/src/haven/tests/test_aravis.py index 587038c0..1203b91d 100644 --- a/src/haven/tests/test_aravis.py +++ b/src/haven/tests/test_aravis.py @@ -53,6 +53,11 @@ async def test_camera_signals(camera): desc = await camera.fileio.data_type.describe() hdf_source = desc["s255id-gige-A-fileio-data_type"]["source"] assert hdf_source == "mock+ca://255idgigeA:HDF1:DataType_RBV" + # Check a few additional signals needed for live viewing + assert camera.driver.gain.source == "mock+ca://255idgigeA:cam1:Gain_RBV" + assert camera.driver.gain_auto.source == "mock+ca://255idgigeA:cam1:GainAuto_RBV" + assert camera.driver.acquire_time_auto.source == "mock+ca://255idgigeA:cam1:ExposureAuto_RBV" + # ----------------------------------------------------------------------------- From 7db47b9fc32e64c1e192c98cfdaba41518e120ba Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Mon, 24 Feb 2025 23:09:07 -0600 Subject: [PATCH 44/47] update area detector ui files --- src/firefly/area_detector_overlay.ui | 87 ++++++++++++++++++++++++++-- src/firefly/area_detector_viewer.ui | 36 ++++++------ 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index c322d15a..b123c705 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -6,8 +6,8 @@ 0 0 - 263 - 181 + 250 + 192 @@ -63,6 +63,26 @@ haven://${AD}.overlays.overlay_${OV}.shape + + + CROSS + + + + + RECTANGLE + + + + + ELLIPSE + + + + + TEXT + + @@ -75,10 +95,10 @@ - haven://${AD}.overlays.overlay_${OV}.position_y + haven://${AD}.overlays.overlay_${OV}.center_y - Qt::Vertical + 5 @@ -100,7 +120,7 @@ - haven://${AD}.overlays.overlay_${OV}.position_x + haven://${AD}.overlays.overlay_${OV}.center_x 0 @@ -135,7 +155,7 @@ haven://${AD}.overlays.overlay_${OV}.size_y - Qt::Vertical + 5 @@ -178,6 +198,60 @@ + + + + + + + + + haven://${AD}.overlays.overlay_${OV}.width_y + + + 5 + + + + + + + + + Width + + + Qt::AlignCenter + + + + + + + + + + haven://${AD}.overlays.overlay_${OV}.width_x + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -195,6 +269,7 @@ PyDMSlider QFrame
pydm.widgets.slider
+ 1
diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index 884d6a18..70a4f6dd 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -111,7 +111,7 @@ - haven://${AD}.drv.acquire_time + haven://${AD}.driver.acquire_time @@ -145,7 +145,7 @@ - haven://${AD}.drv.gain + haven://${AD}.driver.gain @@ -186,7 +186,7 @@ - haven://${AD}.drv.DetectorState_RBV + haven://${AD}.driver.detector_state @@ -207,7 +207,7 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - haven://${AD}.hdf.array_size_y + haven://${AD}.fileio.array_size0 @@ -230,7 +230,7 @@ 1224 - haven://${AD}.hdf.array_size_x + haven://${AD}.fileio.array_size1 @@ -270,10 +270,10 @@ Auto - haven://${AD}.drv.acquire_time_auto + haven://${AD}.driver.acquire_time_auto - 1 + ONCE @@ -283,10 +283,10 @@ Continuous - haven://${AD}.drv.acquire_time_auto + haven://${AD}.driver.acquire_time_auto - 2 + CONTINUOUS @@ -333,10 +333,10 @@ Auto - haven://${AD}.drv.gain_auto + haven://${AD}.driver.gain_auto - 1 + ONCE @@ -346,10 +346,10 @@ Continuous - haven://${AD}.drv.gain_auto + haven://${AD}.driver.gain_auto - 2 + CONTINUOUS @@ -377,7 +377,7 @@ false - haven://${AD}.drv.acquire + haven://${AD}.driver.acquire false @@ -395,7 +395,7 @@ Are you sure you want to proceed? - 1 + ON None @@ -420,10 +420,10 @@ [{"name": "enable_acquire_buttons", "property": "Enable", "initial_value": "", "expression": "ch[0] == 1", "channels": [{"channel": "${PREFIX}cam1:Acquire", "trigger": true, "use_enum": false}]}] - haven://${AD}.drv.acquire + haven://${AD}.driver.acquire - 0 + OFF @@ -517,7 +517,7 @@ - Datetime + Cursor 3 From 99ff03192c0f2442591cd03e04e9d14e2910b63b Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Wed, 26 Feb 2025 21:48:43 -0600 Subject: [PATCH 45/47] Add period --- src/firefly/area_detector_viewer.py | 5 +- src/firefly/area_detector_viewer.ui | 186 ++++++++++++++++----------- src/firefly/pydm_plugin.py | 1 + src/haven/devices/detectors/eiger.py | 3 + 4 files changed, 117 insertions(+), 78 deletions(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index 51ae5fda..adbb3580 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -52,7 +52,7 @@ def customize_device(self): device_name = name = self.macros()["AD"] device = beamline.devices[device_name] self.device = device - img_pv = device.pva.pv_name.get(as_string=True) + img_pv = device.pva._name #.get(as_string=True) addr = f"pva://{img_pv}" self.image_channel = pydm.PyDMChannel( address=addr, value_slot=self.update_image @@ -67,7 +67,8 @@ def customize_ui(self): # Set some text about the camera use_name = getattr(self.device, "description", None) in [self.device.name, None] if use_name: - lbl_text = self.device.cam.name + print(f"Available attributes in self.device:{dir(self.device.driver)}") + lbl_text = self.device.driver.name else: lbl_text = f"{self.device.description} ({self.device.cam.prefix})" self.ui.camera_description_label.setText(lbl_text) diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index 70a4f6dd..147796b8 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -103,56 +103,65 @@ - - + + - + + + Auto + - haven://${AD}.driver.acquire_time + haven://${AD}.driver.gain_auto + + + ONCE - + - s + Continuous - - Qt::AlignCenter + + haven://${AD}.driver.gain_auto + + + CONTINUOUS - - + + - Gain: + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - + - haven://${AD}.driver.gain + haven://${AD}.driver.acquire_time - + - + s Qt::AlignCenter @@ -161,8 +170,8 @@ - - + + @@ -171,6 +180,39 @@ + + + + + + + + + Auto + + + haven://${AD}.driver.acquire_time_auto + + + ONCE + + + + + + + Continuous + + + haven://${AD}.driver.acquire_time_auto + + + CONTINUOUS + + + + + @@ -190,7 +232,17 @@ - + + + + State: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 0 @@ -249,7 +301,7 @@ - + Shape: @@ -259,53 +311,20 @@ - - - - - - - - - Auto - - - haven://${AD}.driver.acquire_time_auto - - - ONCE - - - - - - - Continuous - - - haven://${AD}.driver.acquire_time_auto - - - CONTINUOUS - - - - - - - + + - State: + Exposure: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - Exposure: + Period: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -313,9 +332,9 @@ - + - + Gain: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -323,33 +342,48 @@ - + - + - - Auto - - haven://${AD}.driver.gain_auto - - - ONCE + haven://${AD}.driver.gain - + - Continuous + + + + Qt::AlignCenter + + + + + + + + + + + - haven://${AD}.driver.gain_auto + haven://${AD}.driver.acquire_period - - CONTINUOUS + + + + + + s + + + Qt::AlignCenter diff --git a/src/firefly/pydm_plugin.py b/src/firefly/pydm_plugin.py index 3ac33ca7..240daacb 100644 --- a/src/firefly/pydm_plugin.py +++ b/src/firefly/pydm_plugin.py @@ -162,6 +162,7 @@ class HavenPlugin(SignalPlugin): def connection_class(channel, address, protocol): # Check if we need the synchronous or asynchronous version try: + print(f"Available attributes in beamline.devices:{dir(beamline.devices[address].source)}") sig = beamline.devices[address] except KeyError: sig = None diff --git a/src/haven/devices/detectors/eiger.py b/src/haven/devices/detectors/eiger.py index 4c8f5eb6..08b2a745 100644 --- a/src/haven/devices/detectors/eiger.py +++ b/src/haven/devices/detectors/eiger.py @@ -104,6 +104,9 @@ def __init__( def default_time_signal(self): return self.driver.acquire_time + @property + def default_period_signal(self): + return self.driver.period_time # ----------------------------------------------------------------------------- # :author: Mark Wolfman From 63e65e2b1a128f6325a1397d97fa376cf1d2e4b7 Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Thu, 27 Feb 2025 21:01:03 -0600 Subject: [PATCH 46/47] update overlay channel --- src/firefly/area_detector_overlay.ui | 22 +++++++++++----------- src/firefly/area_detector_viewer.ui | 10 +++++----- src/firefly/pydm_plugin.py | 9 ++++++--- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/firefly/area_detector_overlay.ui b/src/firefly/area_detector_overlay.ui index b123c705..bc9a68f0 100644 --- a/src/firefly/area_detector_overlay.ui +++ b/src/firefly/area_detector_overlay.ui @@ -44,7 +44,7 @@ - haven://${AD}.overlays.overlay_${OV}.use + haven://${AD}-overlay-overlays-${OV}.use @@ -61,7 +61,7 @@ - haven://${AD}.overlays.overlay_${OV}.shape + haven://${AD}-overlay-overlays-${OV}.shape @@ -95,10 +95,10 @@ - haven://${AD}.overlays.overlay_${OV}.center_y + haven://${AD}-overlay-overlays-${OV}.center_y - 5 + Qt::Vertical @@ -120,7 +120,7 @@ - haven://${AD}.overlays.overlay_${OV}.center_x + haven://${AD}-overlay-overlays-${OV}.center_x 0 @@ -152,10 +152,10 @@ - haven://${AD}.overlays.overlay_${OV}.size_y + haven://${AD}-overlay-overlays-${OV}.size_y - 5 + Qt::Vertical @@ -177,7 +177,7 @@ - haven://${AD}.overlays.overlay_${OV}.size_x + haven://${AD}-overlay-overlays-${OV}.size_x @@ -206,10 +206,10 @@ - haven://${AD}.overlays.overlay_${OV}.width_y + haven://${AD}-overlay-overlays-${OV}.width_y - 5 + Qt::Vertical @@ -231,7 +231,7 @@ - haven://${AD}.overlays.overlay_${OV}.width_x + haven://${AD}-overlay-overlays-${OV}.width_x diff --git a/src/firefly/area_detector_viewer.ui b/src/firefly/area_detector_viewer.ui index 147796b8..08b5b6ee 100644 --- a/src/firefly/area_detector_viewer.ui +++ b/src/firefly/area_detector_viewer.ui @@ -402,7 +402,7 @@ Start - [{"name": "enable_acquire_buttons", "property": "Enable", "initial_value": "False", "expression": "ch[0] == 0", "channels": [{"channel": "${PREFIX}cam1:Acquire", "trigger": true, "use_enum": false}]}] + [{"name": "enable_acquire_buttons", "property": "Enable", "initial_value": "", "expression": "ch[0] == 0", "channels": [{"channel": "${PREFIX}cam1:Acquire", "trigger": true, "use_enum": false}]}] false @@ -494,7 +494,7 @@ - 0 + 2 @@ -517,7 +517,7 @@ QFrame::NoFrame - AD=${AD},OV=6 + AD=${AD},OV=0 area_detector_overlay.ui @@ -540,7 +540,7 @@ QFrame::NoFrame - AD=${AD},OV=7 + AD=${AD},OV=1 area_detector_overlay.ui @@ -563,7 +563,7 @@ QFrame::NoFrame - AD=${AD},OV=8 + AD=${AD},OV=2 area_detector_overlay.ui diff --git a/src/firefly/pydm_plugin.py b/src/firefly/pydm_plugin.py index 240daacb..d75e700e 100644 --- a/src/firefly/pydm_plugin.py +++ b/src/firefly/pydm_plugin.py @@ -13,8 +13,9 @@ import numpy as np from bluesky.protocols import Movable, Subscribable -from ophyd import OphydObject +from ophyd import OphydObject from ophyd.utils.epics_pvs import AlarmSeverity +from ophyd_asyn.core._signal import SignalRW from pydm.data_plugins.plugin import PyDMConnection from qasync import asyncSlot from typhos.plugins.core import SignalConnection, SignalPlugin @@ -162,14 +163,16 @@ class HavenPlugin(SignalPlugin): def connection_class(channel, address, protocol): # Check if we need the synchronous or asynchronous version try: - print(f"Available attributes in beamline.devices:{dir(beamline.devices[address].source)}") sig = beamline.devices[address] except KeyError: sig = None is_ophyd_async = inspect.iscoroutinefunction(getattr(sig, "connect", None)) is_vanilla_ophyd = isinstance(sig, OphydObject) + is_signal_rw = isinstance(sig, SignalRW) # Get the right Connection class and build it - if is_ophyd_async: + if is_signal_rw: + return HavenAsyncConnection(channel, address, protocol) + elif is_ophyd_async: return HavenAsyncConnection(channel, address, protocol) elif is_vanilla_ophyd: return HavenConnection(channel, address, protocol) From 89a2d8d87eac5a13313c5d5e67bd69fdf95952fb Mon Sep 17 00:00:00 2001 From: Yanna Chen Date: Mon, 3 Mar 2025 19:44:24 -0600 Subject: [PATCH 47/47] check PV connection --- src/firefly/area_detector_viewer.py | 13 ++++++++++++- src/firefly/pydm_plugin.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/firefly/area_detector_viewer.py b/src/firefly/area_detector_viewer.py index adbb3580..99a25b78 100644 --- a/src/firefly/area_detector_viewer.py +++ b/src/firefly/area_detector_viewer.py @@ -5,7 +5,7 @@ import pydm import pyqtgraph from PyQt5.QtCore import pyqtSlot -from PyQt5.QtWidgets import QCheckBox, QPushButton +from PyQt5.QtWidgets import QCheckBox, QPushButton, PyDMLineEdit from firefly import display from haven import beamline @@ -41,6 +41,7 @@ def __init__(self, parent=None, args=None, macros=None): ) # Access Gain widgets + self.gain_line_edit = self.findChild(PyDMLineEdit, "GainLineEdit") self.gain_checkbox = self.findChild(QCheckBox, "GainCheckBox") self.gain_push_button = self.findChild(QPushButton, "GainPushButton") @@ -138,6 +139,16 @@ def handle_gain_push_button_click(self): """ self.gain_checkbox.setChecked(False) + @pyqtSlot(bool) + def update_widget_states(self, connected): + """ + Enable or disable widgets based on PV connection state + """ + self.exposure_push_button.setEnabled(not connected) + self.exposure_check_box.setEnabled(not connected) + self.gain_line_edit.setEnabled(not connected) + self.gain_push_button.setEnabled(not connected) + self.gain_check_box.setEnabled(not connected) # ----------------------------------------------------------------------------- # :author: Mark Wolfman diff --git a/src/firefly/pydm_plugin.py b/src/firefly/pydm_plugin.py index d75e700e..b2216712 100644 --- a/src/firefly/pydm_plugin.py +++ b/src/firefly/pydm_plugin.py @@ -15,7 +15,7 @@ from bluesky.protocols import Movable, Subscribable from ophyd import OphydObject from ophyd.utils.epics_pvs import AlarmSeverity -from ophyd_asyn.core._signal import SignalRW +from ophyd_async.core._signal import SignalRW from pydm.data_plugins.plugin import PyDMConnection from qasync import asyncSlot from typhos.plugins.core import SignalConnection, SignalPlugin