diff --git a/nsls2ptycho/core/ptycho b/nsls2ptycho/core/ptycho
index 666bd93..3b55bcb 160000
--- a/nsls2ptycho/core/ptycho
+++ b/nsls2ptycho/core/ptycho
@@ -1 +1 @@
-Subproject commit 666bd9397a26070f82fae053910e8f57085158f8
+Subproject commit 3b55bcbf4d38078241dc6a273124860bf5c849ab
diff --git a/nsls2ptycho/core/ptycho_param.py b/nsls2ptycho/core/ptycho_param.py
index 4ef26f0..795b912 100644
--- a/nsls2ptycho/core/ptycho_param.py
+++ b/nsls2ptycho/core/ptycho_param.py
@@ -78,7 +78,7 @@ def __init__(self):
self.pha_min = -1.0 #
self.gpu_flag = True # whether to use GPU
- self.gpus = [1, 2, 3] # should be a list of gpu numbers, ex: [0, 2, 3]
+ self.gpus = [0, 1] # should be a list of gpu numbers, ex: [0, 2, 3]
self.gpu_batch_size = 256 # should be 4^n, ex: 4, 16, 64, 256, 1024, 4096, ...
self.use_NCCL = False
self.use_CUDA_MPI = False
diff --git a/nsls2ptycho/core/widgets/mplcanvastool.py b/nsls2ptycho/core/widgets/mplcanvastool.py
index 0ebbf68..2d7c364 100644
--- a/nsls2ptycho/core/widgets/mplcanvastool.py
+++ b/nsls2ptycho/core/widgets/mplcanvastool.py
@@ -214,7 +214,7 @@ def reset(self):
self.canvas.draw()
def draw_image(self, image, cmap='gray', init_roi=False, use_log=False):
- print(cmap, init_roi, use_log)
+ #print(cmap, init_roi, use_log)
if use_log:
print('log scale')
image_data = np.nan_to_num(np.log(image + 1.))
diff --git a/nsls2ptycho/ptycho_gui.py b/nsls2ptycho/ptycho_gui.py
index fca9dec..b04f6dc 100644
--- a/nsls2ptycho/ptycho_gui.py
+++ b/nsls2ptycho/ptycho_gui.py
@@ -29,7 +29,6 @@
import h5py
import numpy as np
from numpy import pi
-import matplotlib.pyplot as plt
import traceback
# for frontend-backend communication
@@ -46,6 +45,8 @@
class MainWindow(QtWidgets.QMainWindow, ui_ptycho.Ui_MainWindow):
+ _mainwindow_signal = QtCore.pyqtSignal()
+
def __init__(self, parent=None, param:Param=None):
super().__init__(parent)
self.setupUi(self)
@@ -76,6 +77,8 @@ def __init__(self, parent=None, param:Param=None):
self.ck_position_correction_flag.clicked.connect(self.updateCorrFlg)
self.ck_refine_data_flag.clicked.connect(self.updateRefineDataFlg)
self.ck_postprocessing_flag.clicked.connect(self.showNoPostProcessingWarning)
+ self.ck_batch_crop_flag.clicked.connect(self.updateBatchCropDataFlg)
+ self.cb_dataloader.currentTextChanged.connect(self.updateBatchCropDataFlg)
self.btn_recon_start.clicked.connect(self.start)
self.btn_recon_stop.clicked.connect(self.stop)
@@ -144,6 +147,7 @@ def __init__(self, parent=None, param:Param=None):
self.updatePcFlg()
self.updateCorrFlg()
self.updateRefineDataFlg()
+ self.updateBatchCropDataFlg()
self.checkGpuAvail()
self.updateGpuFlg()
self.resetExperimentalParameters() # probably not necessary
@@ -180,8 +184,6 @@ def resetButtons(self):
self.btn_recon_batch_start.setEnabled(True)
self.btn_recon_batch_stop.setEnabled(False)
self.recon_bar.setValue(0)
- #plt.ioff()
- plt.close('all')
# close the mmap arrays
# removing these arrays, can be changed later if needed
if self._prb is not None:
@@ -573,11 +575,12 @@ def start(self, batch_mode=False):
def stop(self, batch_mode=False):
- if self._ptycho_gpu_thread is not None and self._ptycho_gpu_thread.isRunning():
+ if self._ptycho_gpu_thread is not None:
if batch_mode:
self._ptycho_gpu_thread.finished.disconnect(self._batch_manager)
- self._ptycho_gpu_thread.kill() # first kill the mpi processes
- self._ptycho_gpu_thread.quit() # then quit QThread gracefully
+ if self._ptycho_gpu_thread.isRunning():
+ self._ptycho_gpu_thread.kill() # first kill the mpi processes
+ self._ptycho_gpu_thread.quit() # then quit QThread gracefully
self._ptycho_gpu_thread = None
self.resetButtons()
if self.reconStepWindow is not None:
@@ -883,6 +886,20 @@ def updateRefineDataFlg(self):
self.param.refine_data_flag = flag
+ def updateBatchCropDataFlg(self):
+ if self.cb_dataloader.currentText() != "Load from databroker":
+ flag = False
+ self.ck_batch_crop_flag.setChecked(flag)
+ self.ck_batch_crop_flag.setEnabled(flag)
+ else:
+ flag = self.ck_batch_crop_flag.isChecked()
+ self.ck_batch_crop_flag.setEnabled(True)
+ self.sp_batch_x0.setEnabled(flag)
+ self.sp_batch_y0.setEnabled(flag)
+ self.sp_batch_width.setEnabled(flag)
+ self.sp_batch_height.setEnabled(flag)
+
+
def showNoPostProcessingWarning(self):
if not self.ck_postprocessing_flag.isChecked():
print("[WARNING] Post-processing is turned off. No result will be written to disk!", file=sys.stderr)
@@ -915,12 +932,14 @@ def resetMPIFlg(self):
def batchStart(self):
- '''
- Currently only support load from h5.
- '''
- if self.cb_dataloader.currentText() == "Load from databroker":
- print("[WARNING] Batch mode with databroker is not yet supported. Abort.", file=sys.stderr)
+ if not self.ck_batch_crop_flag.isChecked() and not self.ck_batch_run_flag.isChecked():
+ print("[WARNING] Choose least one action (Crop or Run). Stop.", file=sys.stderr)
return
+
+ if self.cb_dataloader.currentText() == "Load from databroker":
+ if not self.ck_batch_crop_flag.isChecked():
+ print("[WARNING] Batch mode with databroker is set, but \"Crop data\" is not.\n"
+ "[WARNING] Will attempt to load h5 from working directory", file=sys.stderr)
try:
self._scan_numbers = parse_range(self.le_batch_items.text(), self.sp_batch_step.value())
@@ -944,10 +963,19 @@ def batchStop(self):
'''
Brute-force abortion of the entire batch. No resumption is possible.
'''
- #self._ptycho_gpu_thread.finished.disconnect(self._batch_manager)
self._scan_numbers = None
self.le_scan_num.textChanged.connect(self.forceLoad)
self.stop(True)
+ if self.roiWindow is not None:
+ if self.roiWindow._worker_thread is not None:
+ self.roiWindow._worker_thread.disconnect()
+ ## thread.terminate() freezes the whole GUI -- why?
+ #if self.roiWindow._worker_thread.isRunning():
+ # self.roiWindow._worker_thread.terminate()
+ # self.roiWindow._worker_thread.wait()
+ self.roiWindow._worker_thread = None
+ self.roiWindow = None
+ self.resetButtons()
def _batch_manager(self):
@@ -958,19 +986,61 @@ def _batch_manager(self):
is not helping.
'''
# TODO: think what if anything goes wrong in the middle. Is this robust?
+ if self._scan_numbers is None:
+ return
+
if len(self._scan_numbers) > 0:
scan_num = self._scan_numbers.pop()
- print("begin processing scan " + str(scan_num) + "...")
+ print("[BATCH] begin processing scan " + str(scan_num) + "...")
self.le_scan_num.setText(str(scan_num))
- self.loadExpParam()
- self.start(True)
self.btn_recon_batch_start.setEnabled(False)
self.btn_recon_batch_stop.setEnabled(True)
+
+ if self.ck_batch_crop_flag.isChecked():
+ self._batch_crop() # also handles "Run" if needed
+ elif self.ck_batch_run_flag.isChecked():
+ self._batch_run() # h5 exists, just "Run"
+ else:
+ raise
else:
- print("batch processing complete!")
+ print("[BATCH] batch processing complete!")
self._scan_numbers = None
self.le_scan_num.textChanged.connect(self.forceLoad)
self.resetButtons()
+ if self.roiWindow is not None:
+ self.roiWindow = None
+
+
+ def _batch_crop(self):
+ # ugly hack: pretend the ROI window exists, take the first frame for finding bad pixels,
+ # mimic human input, and run the reconstruction (if checked)
+
+ # first get params from databroker
+ eventloop = self._batch_eventloop = QtCore.QEventLoop()
+ self._mainwindow_signal.connect(eventloop.quit)
+ self.loadExpParam()
+ eventloop.exec()
+
+ # then invoke the h5 worker in RoiWindow
+ if self.roiWindow is not None:
+ self.roiWindow.close()
+ img = self._viewDataFrameBroker(0)
+ self.roiWindow = RoiWindow(image=img, main_window=self)
+ #self.roiWindow.roi_changed.connect(self._get_roi_slot)
+ self.roiWindow.canvas._eventHandler.set_curr_roi(self.roiWindow.canvas.ax,
+ (self.sp_batch_x0.value(), self.sp_batch_y0.value()),
+ self.sp_batch_width.value(), self.sp_batch_height.value())
+ #print("ROI:", self.roiWindow.canvas.get_red_roi())
+ self.roiWindow.save_to_h5()
+ #self.btn_recon_batch_stop.clicked.connect(self.roiWindow._worker_thread.terminate)
+ if not self.ck_batch_run_flag.isChecked():
+ self.roiWindow._worker_thread.finished.connect(self._batch_manager)
+ else:
+ self.roiWindow._worker_thread.finished.connect(self._batch_run)
+
+
+ def _batch_run(self):
+ self.start(True)
def switchProbeBatch(self):
@@ -1081,7 +1151,7 @@ def _get_roi_slot(self, x0, y0, width, height):
print(x0, y0, width, height)
- def loadExpParam(self):
+ def loadExpParam(self):
scan_num = self.le_scan_num.text()
try:
@@ -1126,7 +1196,7 @@ def _loadExpParamBroker(self, scan_id:int):
thread.start()
- def _setExpParamBroker(self, it, metadata:dict):
+ def _setExpParamBroker(self, it, metadata:dict):
'''
Notes:
1. The parameter "it" is just a placeholder for the signal
@@ -1155,6 +1225,7 @@ def _setExpParamBroker(self, it, metadata:dict):
self.cb_scan_type.setCurrentText(metadata['scan_type'])
self._scan_points = metadata['points']
print("done")
+ self._mainwindow_signal.emit()
def setLoadButton(self):
diff --git a/nsls2ptycho/roi_gui.py b/nsls2ptycho/roi_gui.py
index 2da2935..a581c2d 100644
--- a/nsls2ptycho/roi_gui.py
+++ b/nsls2ptycho/roi_gui.py
@@ -183,6 +183,7 @@ def save_to_h5(self):
self.cx, self.cy, threshold, badpixels, blue_rois)
thread.finished.connect(lambda: self.btn_save_to_h5.setEnabled(True))
thread.exception_handler = master.exception_handler
+ thread.setTerminationEnabled()
self.btn_save_to_h5.setEnabled(False)
thread.start()
diff --git a/nsls2ptycho/ui/ui_ptycho.py b/nsls2ptycho/ui/ui_ptycho.py
index 79df8d7..8b9ca48 100644
--- a/nsls2ptycho/ui/ui_ptycho.py
+++ b/nsls2ptycho/ui/ui_ptycho.py
@@ -961,6 +961,49 @@ def setupUi(self, MainWindow):
self.btn_recon_batch_stop.setMaximumSize(QtCore.QSize(80, 16777215))
self.btn_recon_batch_stop.setObjectName("btn_recon_batch_stop")
self.horizontalLayout_11.addWidget(self.btn_recon_batch_stop)
+ self.ck_batch_run_flag = QtWidgets.QCheckBox(self.tab_3)
+ self.ck_batch_run_flag.setGeometry(QtCore.QRect(20, 220, 141, 20))
+ self.ck_batch_run_flag.setChecked(True)
+ self.ck_batch_run_flag.setObjectName("ck_batch_run_flag")
+ self.widget = QtWidgets.QWidget(self.tab_3)
+ self.widget.setGeometry(QtCore.QRect(20, 190, 644, 31))
+ self.widget.setObjectName("widget")
+ self.horizontalLayout_16 = QtWidgets.QHBoxLayout(self.widget)
+ self.horizontalLayout_16.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_16.setObjectName("horizontalLayout_16")
+ self.ck_batch_crop_flag = QtWidgets.QCheckBox(self.widget)
+ self.ck_batch_crop_flag.setObjectName("ck_batch_crop_flag")
+ self.horizontalLayout_16.addWidget(self.ck_batch_crop_flag)
+ spacerItem16 = QtWidgets.QSpacerItem(28, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_16.addItem(spacerItem16)
+ self.label_62 = QtWidgets.QLabel(self.widget)
+ self.label_62.setObjectName("label_62")
+ self.horizontalLayout_16.addWidget(self.label_62)
+ self.sp_batch_x0 = QtWidgets.QSpinBox(self.widget)
+ self.sp_batch_x0.setMaximum(100000000)
+ self.sp_batch_x0.setObjectName("sp_batch_x0")
+ self.horizontalLayout_16.addWidget(self.sp_batch_x0)
+ self.label_63 = QtWidgets.QLabel(self.widget)
+ self.label_63.setObjectName("label_63")
+ self.horizontalLayout_16.addWidget(self.label_63)
+ self.sp_batch_y0 = QtWidgets.QSpinBox(self.widget)
+ self.sp_batch_y0.setMaximum(100000000)
+ self.sp_batch_y0.setObjectName("sp_batch_y0")
+ self.horizontalLayout_16.addWidget(self.sp_batch_y0)
+ self.label_64 = QtWidgets.QLabel(self.widget)
+ self.label_64.setObjectName("label_64")
+ self.horizontalLayout_16.addWidget(self.label_64)
+ self.sp_batch_width = QtWidgets.QSpinBox(self.widget)
+ self.sp_batch_width.setMaximum(100000000)
+ self.sp_batch_width.setObjectName("sp_batch_width")
+ self.horizontalLayout_16.addWidget(self.sp_batch_width)
+ self.label_65 = QtWidgets.QLabel(self.widget)
+ self.label_65.setObjectName("label_65")
+ self.horizontalLayout_16.addWidget(self.label_65)
+ self.sp_batch_height = QtWidgets.QSpinBox(self.widget)
+ self.sp_batch_height.setMaximum(100000000)
+ self.sp_batch_height.setObjectName("sp_batch_height")
+ self.horizontalLayout_16.addWidget(self.sp_batch_height)
self.tabWidget.addTab(self.tab_3, "")
self.verticalLayout_5.addWidget(self.tabWidget)
self.console_info = QtWidgets.QTextEdit(self.centralwidget)
@@ -1168,6 +1211,13 @@ def retranslateUi(self, MainWindow):
self.le_prb_path_batch.setToolTip(_translate("MainWindow", "Set probe filename template. Ex: \"recon_*_t1_probe_ave.npy\", where \"*\" will be replaced by the scan number."))
self.btn_recon_batch_start.setText(_translate("MainWindow", "start"))
self.btn_recon_batch_stop.setText(_translate("MainWindow", "stop"))
+ self.ck_batch_run_flag.setText(_translate("MainWindow", "Run reconstruction"))
+ self.ck_batch_crop_flag.setToolTip(_translate("MainWindow", "This is effective only when \"Load from databroker\" is set."))
+ self.ck_batch_crop_flag.setText(_translate("MainWindow", "Crop data:"))
+ self.label_62.setText(_translate("MainWindow", "x0"))
+ self.label_63.setText(_translate("MainWindow", "y0"))
+ self.label_64.setText(_translate("MainWindow", "w"))
+ self.label_65.setText(_translate("MainWindow", "h"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Batch mode"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.menuWindows.setTitle(_translate("MainWindow", "Windows"))
diff --git a/nsls2ptycho/ui/ui_ptycho.ui b/nsls2ptycho/ui/ui_ptycho.ui
index 6db00f6..c45faca 100644
--- a/nsls2ptycho/ui/ui_ptycho.ui
+++ b/nsls2ptycho/ui/ui_ptycho.ui
@@ -2320,6 +2320,113 @@
+
+
+
+ 20
+ 220
+ 141
+ 20
+
+
+
+ Run reconstruction
+
+
+ true
+
+
+
+
+
+ 20
+ 190
+ 644
+ 31
+
+
+
+ -
+
+
+ This is effective only when "Load from databroker" is set.
+
+
+ Crop data:
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 28
+ 20
+
+
+
+
+ -
+
+
+ x0
+
+
+
+ -
+
+
+ 100000000
+
+
+
+ -
+
+
+ y0
+
+
+
+ -
+
+
+ 100000000
+
+
+
+ -
+
+
+ w
+
+
+
+ -
+
+
+ 100000000
+
+
+
+ -
+
+
+ h
+
+
+
+ -
+
+
+ 100000000
+
+
+
+
+