我正在开发一个 PyQt6 应用程序,该应用程序可以实时采集随时间变化的光子强度跟踪并实时绘制它,然后调用 pyo3 函数对数据进行后处理。用户可以选择是否“自动执行多次采集”。例如,如果他们选择连续进行 5 次采集,软件将按顺序执行采集。 在我的主窗口中,我已经初始化了这些处理队列获取实时数据的变量:
self.pull_from_queue_timer = QTimer()
self.pull_from_queue_timer.timeout.connect(partial(IntensityTracing.pull_from_queue, self))
在控制器类中,我有以下功能:
start_button_pressed
通过执行各种操作来初始化采集过程,包括 GUI 操作和调用另一个函数,该函数将实际调用负责启动采集的外部库:
@staticmethod
def start_button_pressed(app):
IntensityTracingButtonsActions.clear_intensity_grid_widgets(app)
app.acquisition_stopped = False
app.warning_box = None
app.settings.setValue(SETTINGS_ACQUISITION_STOPPED, False)
#app.control_inputs[DOWNLOAD_BUTTON].setEnabled(app.write_data and app.acquisition_stopped)
#self.set_download_button_icon()
warn_title, warn_msg = MessagesUtilities.invalid_inputs_handler(
app.bin_width_micros,
app.time_span,
app.acquisition_time_millis,
app.control_inputs[SETTINGS_FREE_RUNNING_MODE],
app.enabled_channels,
app.selected_conn_channel,
)
if warn_title and warn_msg:
message_box = BoxMessage.setup(
warn_title, warn_msg, QMessageBox.Icon.Warning, GUIStyles.set_msg_box_style(), app.test_mode
)
app.warning_box = message_box
return
app.control_inputs[START_BUTTON].setEnabled(False)
app.control_inputs[STOP_BUTTON].setEnabled(app.free_running_acquisition_time)
app.intensity_charts.clear()
app.intensity_lines.clear()
app.last_acquisition_ns = 0
app.gt_charts.clear()
app.cps_ch.clear()
for chart in app.intensity_charts:
chart.setVisible(False)
for chart in app.gt_charts:
chart.setVisible(False)
for channel, curr_conn in app.intensity_connectors.items():
curr_conn.disconnect()
remove_widget(app.layouts[PLOT_GRIDS_CONTAINER], app.widgets[GT_WIDGET_WRAPPER])
gt_widget = create_gt_wait_layout(app)
insert_widget(app.layouts[PLOT_GRIDS_CONTAINER], gt_widget, 1)
app.intensity_connectors.clear()
app.gt_lines.clear()
app.intensity_charts_wrappers.clear()
QApplication.processEvents()
IntensityTracingButtonsActions.intensity_tracing_start(app)
if not app.widgets[GT_WIDGET_WRAPPER].isVisible():
IntensityTracingButtonsActions.show_gt_widget(app, True)
IntensityTracing.start_photons_tracing(app)
start_photons_tracing
函数与外部库通信开始采集并启动连接到pull_from_queue函数的定时器:
@staticmethod
def start_photons_tracing(app):
try:
free_running_mode = app.control_inputs[SETTINGS_FREE_RUNNING_MODE].isChecked()
acquisition_time_millis = (
None if app.acquisition_time_millis in (0, None) or
free_running_mode
else app.acquisition_time_millis
)
print("Selected firmware: " + (str(app.selected_firmware)))
print("Free running enabled: " + str(free_running_mode))
print("Acquisition time (ms): " + str(acquisition_time_millis))
print("Time span (s): " + str(app.time_span))
print("Max points: " + str(40 * app.time_span))
print("Bin width (µs): " + str(app.bin_width_micros))
result = flim_labs.start_intensity_tracing(
enabled_channels=app.enabled_channels,
bin_width_micros=app.bin_width_micros,
write_bin=False,
write_data=True,
acquisition_time_millis=acquisition_time_millis,
firmware_file=app.selected_firmware,
)
if app.acquisitions_count >= app.selected_average:
app.widgets[PROGRESS_BAR_WIDGET].clear_acquisition_timer(app)
if not free_running_mode:
app.widgets[PROGRESS_BAR_WIDGET].update_acquisitions_count()
app.widgets[PROGRESS_BAR_WIDGET].setVisible(True)
file_bin = result.bin_file
if file_bin != "":
print("File bin written in: " + str(file_bin))
app.blank_space.hide()
app.pull_from_queue_timer.start(1)
app.last_cps_update_time.start()
except Exception as e:
error_title, error_msg = MessagesUtilities.error_handler(str(e))
BoxMessage.setup(
error_title,
error_msg,
QMessageBox.Icon.Critical,
GUIStyles.set_msg_box_style(),
app.test_mode
)
pull_from_queue
函数通过调用外部库检索实时数据。如果用户设置了按顺序完成一定数量的采集,一旦完成一个并且尚未达到所选的采集数量,则再次调用
start_button_pressed 重新启动该过程:
@staticmethod
def pull_from_queue(app):
val = flim_labs.pull_from_queue()
if len(val) > 0:
for v in val:
if v == ('end',): # End of acquisition
print("Got end of acquisition, stopping")
IntensityTracing.stop_button_pressed(app)
if app.acquisitions_count < app.selected_average:
IntensityTracingButtonsActions.start_button_pressed(app)
break
((time_ns), (intensities)) = v
IntensityTracing.calculate_cps(app, time_ns[0], intensities)
这是
stop_button_pressed
函数,称为:
def stop_button_pressed(app, app_close = False):
time.sleep(0.5)
try:
flim_labs.request_stop()
except:
pass
if app.acquisitions_count >= app.selected_average:
app.acquisition_count = 0
else:
app.acquisitions_count = app.acquisitions_count + 1
app.widgets[PROGRESS_BAR_WIDGET].update_acquisitions_count()
app.last_cps_update_time.invalidate()
app.control_inputs[START_BUTTON].setEnabled(len(app.enabled_channels) > 0)
app.control_inputs[STOP_BUTTON].setEnabled(False)
if not app_close and app.acquisitions_count == app.selected_average:
remove_widget(app.layouts[PLOT_GRIDS_CONTAINER], app.widgets[GT_WIDGET_WRAPPER])
gt_widget = create_gt_loading_layout(app)
insert_widget(app.layouts[PLOT_GRIDS_CONTAINER], gt_widget, 1)
QApplication.processEvents()
app.pull_from_queue_timer.stop()
for channel, curr_conn in app.intensity_connectors.items():
curr_conn.pause()
if not app_close:
if app.acquisitions_count == app.selected_average:
FCSPostProcessing.get_input(app)
问题是这样的:如果我选择执行多次采集,此过程会随机且意外地冻结,有时会关闭应用程序而不会释放任何错误。调试时,应用程序总是在 stop_button_pressed 内冻结,但冻结的点是随机的。
当我进行多次采集时,我尝试使用另一个 QTimer 单独处理逻辑,但没有任何改变。问题可能出在哪里?谢谢。
flim_labs.pull_from_queue()
被阻塞,它可以冻结 GUI。
尝试使用 QThread 将耗时的操作(如(flim_labs.start_intensity_tracing()
和
flim_labs.pull_from_queue()
)移至分离线程。