From 63b15ca1effe557abbd28df1f9638b774c9bb083 Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 09:12:33 +0000 Subject: [PATCH 1/6] Refactor Async Dialog UI componenets into QVBoxLayouts --- mantidimaging/gui/ui/async_task_dialog.ui | 53 ++++++++++++----------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/mantidimaging/gui/ui/async_task_dialog.ui b/mantidimaging/gui/ui/async_task_dialog.ui index 2713ee7bd9c..2d8ce7ba828 100644 --- a/mantidimaging/gui/ui/async_task_dialog.ui +++ b/mantidimaging/gui/ui/async_task_dialog.ui @@ -20,33 +20,36 @@ Progress - - - - Progress - - - - - - - 0 - - - true - - - false - - - - + + + Progress + + + + + + + 0 + + + true + + + false + + + + + + + + + Cancel - - - - + + + From 86815cf238ba62008c6f8de8460d8f2283624df9 Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 09:18:45 +0000 Subject: [PATCH 2/6] Add Progress Plot to Dialog Window --- mantidimaging/gui/dialogs/async_task/view.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mantidimaging/gui/dialogs/async_task/view.py b/mantidimaging/gui/dialogs/async_task/view.py index 1f0cb0e0044..d79ffd26662 100644 --- a/mantidimaging/gui/dialogs/async_task/view.py +++ b/mantidimaging/gui/dialogs/async_task/view.py @@ -4,6 +4,7 @@ from typing import Any from collections.abc import Callable +from pyqtgraph import PlotWidget from mantidimaging.core.utility.progress_reporting import Progress from mantidimaging.gui.mvp_base import BaseDialogView @@ -23,6 +24,11 @@ def __init__(self, parent: QMainWindow): self.progressBar.setMinimum(0) self.progressBar.setMaximum(1000) + self.progress_plot = PlotWidget() + self.PlotVerticalLayout.addWidget(self.progress_plot) + self.progress_plot.hide() + self.progress_plot.setLogMode(y=True) + self.progress_plot.setMinimumHeight(300) self.show_timer = QTimer(self) self.cancelButton.clicked.connect(self.presenter.stop_progress) @@ -63,6 +69,10 @@ def set_progress(self, progress: float, message: str): # Update progress bar self.progressBar.setValue(int(progress * 1000)) + def set_progress_plot(self, x: list, y: list): + self.progress_plot.show() + self.progress_plot.plotItem.plot(x, y) + def show_delayed(self, timeout) -> None: self.show_timer.singleShot(timeout, self.show_from_timer) self.show_timer.start() From 7f527ea543e018e3730099ef04629c5febcb782c Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 10:46:00 +0000 Subject: [PATCH 3/6] Add extra_info Argument to Progress Update --- .../core/utility/progress_reporting/progress.py | 11 ++++++++--- .../utility/progress_reporting/test/progress_test.py | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mantidimaging/core/utility/progress_reporting/progress.py b/mantidimaging/core/utility/progress_reporting/progress.py index 36077d47db4..b953272d27c 100644 --- a/mantidimaging/core/utility/progress_reporting/progress.py +++ b/mantidimaging/core/utility/progress_reporting/progress.py @@ -9,7 +9,8 @@ from mantidimaging.core.utility.memory_usage import get_memory_usage_linux_str -ProgressHistory = NamedTuple('ProgressHistory', [('time', float), ('step', int), ('msg', str)]) +ProgressHistory = NamedTuple('ProgressHistory', [('time', float), ('step', int), ('msg', str), + ('extra_info', dict | None)]) class ProgressHandler: @@ -167,7 +168,11 @@ def _format_time(t: SupportsInt) -> str: t = int(t) return f'{t // 3600:02}:{t % 3600 // 60:02}:{t % 60:02}' - def update(self, steps: int = 1, msg: str = "", force_continue: bool = False) -> None: + def update(self, + steps: int = 1, + msg: str = "", + force_continue: bool = False, + extra_info: dict | None = None) -> None: """ Updates the progress of the task. @@ -188,7 +193,7 @@ def update(self, steps: int = 1, msg: str = "", force_continue: bool = False) -> msg = f"{f'{msg}' if len(msg) > 0 else ''} | {self.current_step}/{self.end_step} | " \ f"Time: {self._format_time(self.execution_time())}, ETA: {self._format_time(eta)}" - step_details = ProgressHistory(time.perf_counter(), self.current_step, msg) + step_details = ProgressHistory(time.perf_counter(), self.current_step, msg, extra_info) self.progress_history.append(step_details) # process progress callbacks diff --git a/mantidimaging/core/utility/progress_reporting/test/progress_test.py b/mantidimaging/core/utility/progress_reporting/test/progress_test.py index ea5bdbc6a7c..e358de4f2c2 100644 --- a/mantidimaging/core/utility/progress_reporting/test/progress_test.py +++ b/mantidimaging/core/utility/progress_reporting/test/progress_test.py @@ -257,20 +257,20 @@ def test_format_time(self): def test_calculate_mean_time(self): progress_history = [] - progress_history.append(ProgressHistory(100, 0, "")) + progress_history.append(ProgressHistory(100, 0, "", None)) self.assertEqual(Progress.calculate_mean_time(progress_history), 0) # first step 5 seconds - progress_history.append(ProgressHistory(105, 1, "")) + progress_history.append(ProgressHistory(105, 1, "", None)) self.assertEqual(Progress.calculate_mean_time(progress_history), 5) # second step 10 seconds - progress_history.append(ProgressHistory(115, 2, "")) + progress_history.append(ProgressHistory(115, 2, "", None)) self.assertEqual(Progress.calculate_mean_time(progress_history), 7.5) for i in range(1, 50): # add many 2 second steps - progress_history.append(ProgressHistory(115 + (i * 2), 2 + (i * 2), "")) + progress_history.append(ProgressHistory(115 + (i * 2), 2 + (i * 2), "", None)) self.assertEqual(Progress.calculate_mean_time(progress_history), 2) From b53c8b660cbd6ecc2092947a5c4f0465022966ef Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 10:46:44 +0000 Subject: [PATCH 4/6] Place Iterations and Losses in MIProgressCallback --- mantidimaging/core/reconstruct/cil_recon.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mantidimaging/core/reconstruct/cil_recon.py b/mantidimaging/core/reconstruct/cil_recon.py index b42edb13c17..663578eecc0 100644 --- a/mantidimaging/core/reconstruct/cil_recon.py +++ b/mantidimaging/core/reconstruct/cil_recon.py @@ -44,10 +44,14 @@ def __init__(self, verbose=1, progress: Progress | None = None) -> None: def __call__(self, algo: Algorithm) -> None: if self.progress: - self.progress.update(steps=1, - msg=f'CIL: Iteration {self.iteration_count } of {algo.max_iteration}' - f': Objective {algo.get_last_objective():.2f}', - force_continue=False) + extra_info = {'iterations': algo.iterations, 'losses': algo.loss} + self.progress.update( + steps=1, + msg=f'CIL: Iteration {self.iteration_count } of {algo.max_iteration}' + f': Objective {algo.get_last_objective():.2f}', + force_continue=False, + extra_info=extra_info, + ) self.iteration_count += 1 @@ -407,6 +411,7 @@ def full(images: ImageStack, LOG.info(f'Reconstructed 3D volume with shape: {volume.shape}') t1 = time.perf_counter() LOG.info(f"full reconstruction time: {t1-t0}s for shape {images.data.shape}") + ImageStack(volume).metadata['convergence'] = {'iterations': algo.iterations, 'losses': algo.loss} return ImageStack(volume) From 327beaa79c561bbf4a329492f9e2aebf89e7c3cf Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 10:48:32 +0000 Subject: [PATCH 5/6] Call set_progress_plot with Iterations and Losses --- mantidimaging/gui/dialogs/async_task/presenter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mantidimaging/gui/dialogs/async_task/presenter.py b/mantidimaging/gui/dialogs/async_task/presenter.py index fbf4c856fd2..0f8901a5dc0 100644 --- a/mantidimaging/gui/dialogs/async_task/presenter.py +++ b/mantidimaging/gui/dialogs/async_task/presenter.py @@ -20,12 +20,14 @@ class Notification(Enum): class AsyncTaskDialogPresenter(QObject, ProgressHandler): progress_updated = pyqtSignal(float, str) + progress_plot_updated = pyqtSignal(list, list) def __init__(self, view): super().__init__() self.view = view self.progress_updated.connect(self.view.set_progress) + self.progress_plot_updated.connect(self.view.set_progress_plot) self.model = AsyncTaskDialogModel() self.model.task_done.connect(self.view.handle_completion) @@ -62,10 +64,19 @@ def do_start_processing(self) -> None: def task_is_running(self) -> None: return self.model.task_is_running + def update_progress_plot(self, iterations: list, losses: list) -> None: + y = [a[0] for a in losses] + self.progress_plot_updated.emit(iterations, y) + def progress_update(self) -> None: msg = self.progress.last_status_message() + progress_info = self.progress.progress_history + extra_info = progress_info[-1].extra_info self.progress_updated.emit(self.progress.completion(), msg if msg is not None else '') + if extra_info: + self.update_progress_plot(extra_info['iterations'], extra_info['losses']) + def show_stop_button(self, show: bool = False) -> None: self.view.show_cancel_button(show) From 1b8b2e93f64b30b10b1e97fa0f4e0abb7d722859 Mon Sep 17 00:00:00 2001 From: JackEAllen Date: Thu, 14 Nov 2024 12:08:40 +0000 Subject: [PATCH 6/6] Release Notes --- .../feature-2375-display-convergence-plot-during-reconstruction | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/release_notes/next/feature-2375-display-convergence-plot-during-reconstruction diff --git a/docs/release_notes/next/feature-2375-display-convergence-plot-during-reconstruction b/docs/release_notes/next/feature-2375-display-convergence-plot-during-reconstruction new file mode 100644 index 00000000000..19cf5531417 --- /dev/null +++ b/docs/release_notes/next/feature-2375-display-convergence-plot-during-reconstruction @@ -0,0 +1,2 @@ +#2375: Add Convergence Plot in AsyncTaskDialog Displayed During Reconstruction +