Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b7c3a77
feat: update FileSelectionWidget and ceus engine for single DICOM sel…
OmidChaghaneh Feb 9, 2026
0eec1da
Fix frame slider synchronization and frame-based display in CEUS GUI
OmidChaghaneh Feb 9, 2026
2a0e661
Merge branch '48-urgent-philips-high-quality-data-loading' into 36-dc…
OmidChaghaneh Feb 12, 2026
37911ec
Add compact enhancement controls and pseudo-colouring to CEUS ROI dra…
OmidChaghaneh Feb 12, 2026
bef8435
Update gitignore to include generated UI files
OmidChaghaneh Feb 13, 2026
12b8086
fix(ceus): resolve freeze after loading NIfTI segmentation by connect…
OmidChaghaneh Feb 13, 2026
efab415
feat(ceus): connect analysis screen to application workflow
OmidChaghaneh Feb 13, 2026
049e447
fix(ceus): standardize imports and fix NameErrors in analysis widgets
OmidChaghaneh Feb 13, 2026
513177a
feat(ceus): implement analysis discovery and background execution
OmidChaghaneh Feb 13, 2026
021226b
feat(ceus): add missing analysis parameters widget
OmidChaghaneh Feb 13, 2026
dc4e9eb
fix: update .gitignore to include UI files and add missing .ui and _u…
OmidChaghaneh Feb 18, 2026
61af6fd
Fix silent hang after segmentation loading and update ceus engine sub…
OmidChaghaneh Feb 18, 2026
0f92600
Merge branch 29-ceus-bug-report-gui-cannot-load-saved-nifti-voi into …
OmidChaghaneh Feb 18, 2026
2c7affe
Fix ModuleNotFoundError by updating image preprocessing imports in GUI
OmidChaghaneh Feb 18, 2026
3948282
Fix pkl_roi loader missing and TypeErrors in worker threads
OmidChaghaneh Feb 18, 2026
df216c3
Merge main into branch and resolve conflicts
OmidChaghaneh Apr 21, 2026
2f273d0
removed _ui.py files, updated ceus submodule to commit from merged PR
davidspector67 May 3, 2026
c380f7d
removed pseudocoloring for RGB images, removed direct reference to ba…
davidspector67 May 3, 2026
c876f6b
Fix CEUS analysis execution and handle NIfTI phantom_name AttributeError
OmidChaghaneh May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ fixed_wheels/
.vscode/
.DS_Store
*.so
/**/*_ui.py
Visualization_Results/
/**/*_ui.py
2 changes: 1 addition & 1 deletion engines/qus
Submodule qus updated 56 files
+418 −0 CLI-Demos/bmode_radiomics_demo.ipynb
+5 −3 README.md
+3 −3 quantus/analysis/README.md
+0 −24 quantus/analysis/bmode/analysis_methods/bmode_intensity.py
+0 −25 quantus/analysis/bmode/analysis_methods/bmode_snr.py
+3 −3 quantus/analysis/bmode/framework.py
+372 −0 quantus/analysis/bmode/functions.py
+297 −0 quantus/analysis/bmode/radiomics_utils.py
+147 −0 quantus/analysis/bmode/test_bmode_radiomics.py
+12 −15 quantus/analysis/options.py
+0 −85 quantus/analysis/paramap/analysis_methods/attenuation_coef.py
+0 −64 quantus/analysis/paramap/analysis_methods/bsc.py
+0 −368 quantus/analysis/paramap/analysis_methods/bsc_stft.py
+0 −75 quantus/analysis/paramap/analysis_methods/central_freq_shift.py
+0 −31 quantus/analysis/paramap/analysis_methods/compute_power_spectra.py
+0 −159 quantus/analysis/paramap/analysis_methods/hscan.py
+0 −76 quantus/analysis/paramap/analysis_methods/lizzi_feleppa.py
+0 −37 quantus/analysis/paramap/analysis_methods/nakagami_params.py
+3 −4 quantus/analysis/paramap/framework.py
+849 −0 quantus/analysis/paramap/functions.py
+3 −3 quantus/analysis_config/README.md
+0 −30 quantus/analysis_config/utc_config/config_loaders/clarius_C3_config.py
+0 −31 quantus/analysis_config/utc_config/config_loaders/clarius_L15_config.py
+0 −57 quantus/analysis_config/utc_config/config_loaders/custom.py
+0 −31 quantus/analysis_config/utc_config/config_loaders/philips_3d_config.py
+0 −27 quantus/analysis_config/utc_config/config_loaders/pkl_rf.py
+168 −0 quantus/analysis_config/utc_config/functions.py
+8 −9 quantus/analysis_config/utc_config/options.py
+3 −3 quantus/data_export/README.md
+10 −0 quantus/data_export/bmode_csv/framework.py
+3 −0 quantus/data_export/bmode_csv/functions.py
+0 −93 quantus/data_export/csv/export_funcs/bsc_stft_arr.py
+0 −26 quantus/data_export/csv/export_funcs/descr_vals.py
+0 −40 quantus/data_export/csv/export_funcs/hscan_arr.py
+0 −70 quantus/data_export/csv/export_funcs/hscan_stats.py
+0 −38 quantus/data_export/csv/export_funcs/paramap_arr.py
+0 −54 quantus/data_export/csv/export_funcs/radiomics_stats.py
+2 −3 quantus/data_export/csv/framework.py
+288 −0 quantus/data_export/csv/functions.py
+13 −15 quantus/data_export/options.py
+2 −2 quantus/seg_loading/README.md
+34 −4 quantus/seg_loading/functions.py
+10 −14 quantus/seg_loading/options.py
+0 −0 quantus/seg_loading/seg_loaders/__init__.py
+0 −37 quantus/seg_loading/seg_loaders/nifti_voi.py
+2 −2 quantus/visualizations/README.md
+18 −0 quantus/visualizations/bmode/framework.py
+8 −0 quantus/visualizations/bmode/functions.py
+14 −16 quantus/visualizations/options.py
+2 −3 quantus/visualizations/paramap/framework.py
+491 −0 quantus/visualizations/paramap/functions.py
+0 −228 quantus/visualizations/paramap/visualization_funcs/plot_bsc_stft.py
+0 −164 quantus/visualizations/paramap/visualization_funcs/plot_hscan_result.py
+0 −66 quantus/visualizations/paramap/visualization_funcs/plot_hscan_wavelets.py
+0 −53 quantus/visualizations/paramap/visualization_funcs/plot_ps_window_data.py
+ requirements.txt
34 changes: 23 additions & 11 deletions src/ceus/analysis_loading/analysis_loading_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

from ..mvc.base_controller import BaseController
from .analysis_loading_view_coordinator import AnalysisLoadingViewCoordinator
from engines.ceus.src.data_objs import UltrasoundImage, CeusSeg
from engines.ceus.src.data_objs.image import UltrasoundImage
from engines.ceus.src.data_objs.seg import CeusSeg
from engines.ceus.src.time_series_analysis.curves.framework import CurvesAnalysis


Expand Down Expand Up @@ -64,20 +65,31 @@ def _connect_signals(self) -> None:
def _setup_analysis_options(self) -> None:
"""Setup available analysis types and functions in the view."""
analysis_types, analysis_functions = self._model.get_analysis_types()
print(f"DEBUG: Available analysis types: {list(analysis_types.keys())}")

# Automatically select "Paramap" as the analysis type
paramap_type = "paramap"
if paramap_type in analysis_types:
self._selected_analysis_type = paramap_type
if self._model.set_analysis_type(paramap_type):
# Get available functions for Paramap analysis
available_functions = self._model.get_analysis_functions(paramap_type)
# Automatically select the best available analysis type
# Prefer curves_paramap, then curves, or just the first available one
# For CEUS, 'curves' is the standard non-parametric analysis.
selected_type = None
for preferred in ["curves", "curves_paramap", "paramap"]:
if preferred in analysis_types:
selected_type = preferred
break

if not selected_type and analysis_types:
selected_type = list(analysis_types.keys())[0]

if selected_type:
self._selected_analysis_type = selected_type
if self._model.set_analysis_type(selected_type):
# Get available functions for selected analysis type
available_functions = self._model.get_analysis_functions(selected_type)
# Skip analysis type selection and go directly to function selection
self._view_coordinator.show_function_selection(available_functions)
else:
self._view_coordinator.show_error("Failed to set Paramap analysis type")
self._view_coordinator.show_error(f"Failed to set {selected_type} analysis type")
else:
self._view_coordinator.show_error("Paramap analysis type not available")
self._view_coordinator.show_error("No analysis types available")

def _on_user_action(self, action_name: str, action_data: Any) -> None:
"""
Expand All @@ -98,7 +110,7 @@ def _on_user_action(self, action_name: str, action_data: Any) -> None:
print(f"DEBUG: Controller received analysis_execution_started action")
print(f"DEBUG: action_data = {action_data}")
self._handle_analysis_execution(action_data)
elif action_name == "analysis_completed":
elif action_name == "analysis_loading_completed":
self._handle_analysis_completion(action_data)
else:
# Forward unknown actions to application controller
Expand Down
42 changes: 26 additions & 16 deletions src/ceus/analysis_loading/analysis_loading_view_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
from PyQt6.QtWidgets import QWidget, QStackedWidget
from PyQt6.QtCore import pyqtSignal

from quantus.gui.mvc.base_view import BaseViewMixin
from ..mvc.base_view import BaseViewMixin
from .views.analysis_function_selection_widget import AnalysisFunctionSelectionWidget
from quantus.gui.config_loading.views.analysis_params_widget import AnalysisParamsWidget
from .views.analysis_params_widget import AnalysisParamsWidget
from .views.analysis_execution_widget import AnalysisExecutionWidget
from quantus.data_objs import UltrasoundRfImage, BmodeSeg, RfAnalysisConfig
from quantus.analysis.paramap.framework import ParamapAnalysis
from engines.ceus.src.data_objs.image import UltrasoundImage
from engines.ceus.src.data_objs.seg import CeusSeg
from engines.ceus.src.time_series_analysis.curves.framework import CurvesAnalysis


class AnalysisLoadingViewCoordinator(QStackedWidget, BaseViewMixin):
Expand All @@ -40,19 +41,14 @@ class AnalysisLoadingViewCoordinator(QStackedWidget, BaseViewMixin):
# ============================================================================


def __init__(self, image_data: UltrasoundRfImage, seg_data: BmodeSeg, config_data: RfAnalysisConfig, parent: Optional[QWidget] = None):
def __init__(self, image_data: UltrasoundImage, seg_data: CeusSeg, config_data, parent: Optional[QWidget] = None):
super().__init__(parent)
self.__init_base_view__(parent)
self._image_data = image_data
self._seg_data = seg_data
self._config_data = config_data

print(f"DEBUG: AnalysisLoadingViewCoordinator - image_data = {image_data is not None}")
if image_data is not None:
print(f"DEBUG: AnalysisLoadingViewCoordinator - scan_name = {image_data.scan_name}")
print(f"DEBUG: AnalysisLoadingViewCoordinator - phantom_name = {image_data.phantom_name}")
else:
print(f"DEBUG: AnalysisLoadingViewCoordinator - image_data is None!")

# Widget instances
self._function_selection_widget: Optional[AnalysisFunctionSelectionWidget] = None
Expand All @@ -63,7 +59,7 @@ def __init__(self, image_data: UltrasoundRfImage, seg_data: BmodeSeg, config_dat
self._selected_analysis_type: Optional[str] = None
self._selected_functions: List[str] = []
self._analysis_params: dict = {}
self._analysis_data: Optional[ParamapAnalysis] = None
self._analysis_data: Optional[CurvesAnalysis] = None

# Note: Analysis type selection is now skipped - Paramap is automatically selected
# The controller will call show_function_selection directly
Expand Down Expand Up @@ -106,12 +102,16 @@ def show_error(self, error_message: str) -> None:
error_message: Error message to display
"""
current_widget: BaseViewMixin = self.currentWidget()
current_widget.show_error(error_message)
if current_widget:
current_widget.show_error(error_message)
else:
print(f"ERROR (no active widget): {error_message}")

def clear_error(self) -> None:
"""Clear error message in the current widget."""
current_widget: BaseViewMixin = self.currentWidget()
current_widget.clear_error()
if current_widget:
current_widget.clear_error()

# ============================================================================
# NAVIGATION METHODS - Methods to show different widgets
Expand Down Expand Up @@ -160,13 +160,19 @@ def show_params_configuration(self, required_params: List[str], selected_functio
print(f"DEBUG: Creating AnalysisParamsWidget with image_data = {self._image_data is not None}")
if self._image_data is not None:
print(f"DEBUG: Passing scan_name = {self._image_data.scan_name}")
print(f"DEBUG: Passing phantom_name = {self._image_data.phantom_name}")
if hasattr(self._image_data, 'phantom_name'):
print(f"DEBUG: Passing phantom_name = {self._image_data.phantom_name}")
self._params_widget = AnalysisParamsWidget(self._image_data, self._seg_data, self._config_data)
self._params_widget.setup_ui()
self._params_widget.connect_signals()
self._params_widget.params_configured.connect(self._on_params_configured)
self._params_widget.back_requested.connect(self._on_params_back)
self.addWidget(self._params_widget)
else:
# Update data in existing widget
self._params_widget._image_data = self._image_data
self._params_widget._seg_data = self._seg_data
self._params_widget._config_data = self._config_data

print(f"DEBUG: Calling set_required_params...")
self._params_widget.set_required_params(required_params, selected_functions)
Expand Down Expand Up @@ -198,6 +204,10 @@ def show_analysis_execution(self, execution_summary: Dict) -> None:
print(f"DEBUG: AnalysisExecutionWidget created and added to stack")
else:
print(f"DEBUG: Using existing AnalysisExecutionWidget")
# Update data in existing widget
self._execution_widget._image_data = self._image_data
self._execution_widget._seg_data = self._seg_data
self._execution_widget._config_data = self._config_data

print(f"DEBUG: Setting execution summary...")
self._execution_widget.set_execution_summary(execution_summary)
Expand All @@ -208,7 +218,7 @@ def show_analysis_execution(self, execution_summary: Dict) -> None:
self._execution_widget.clear_error()
print(f"DEBUG: show_analysis_execution completed - execution screen should be visible")

def show_analysis_results(self, analysis_data: ParamapAnalysis) -> None:
def show_analysis_results(self, analysis_data: CurvesAnalysis) -> None:
"""
Show analysis results in the execution widget.

Expand Down Expand Up @@ -260,7 +270,7 @@ def _on_execution_started(self, execution_data: dict) -> None:
self._emit_user_action("analysis_execution_started", execution_data)
print(f"DEBUG: user_action signal emitted")

def _on_analysis_confirmed(self, analysis_data: ParamapAnalysis) -> None:
def _on_analysis_confirmed(self, analysis_data: CurvesAnalysis) -> None:
"""
Handle analysis completion confirmation.

Expand Down
Loading