Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 16 additions & 1 deletion rascal2/dialogs/startup_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,27 @@ def reject(self):
if self.parent().centralWidget() is self.parent().startup_dlg:
self.parent().startup_dlg.setVisible(True)

def project_start_success(self):
def project_start_success(self, warning):
self.parent().presenter.initialise_ui()

if not self.parent().toolbar.isEnabled():
self.parent().toolbar.setEnabled(True)
self.accept()

if warning is not None:
message = (
"The result JSON was not loaded because it is invalid. "
"The other project files were loaded successfully. "
)

message_box = QtWidgets.QMessageBox(self)
message_box.setStyleSheet("QMessageBox QTextEdit{color: red; font-family: monospace; font-weight:500;}")
message_box.setWindowTitle(self.windowTitle())
message_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
message_box.setText(message)
message_box.setDetailedText("\n".join(warning))
message_box.exec()

def project_start_failed(self, exception, args):
folder_name = args[0]
error = str(exception).strip().replace("\n", "")
Expand Down
105 changes: 105 additions & 0 deletions rascal2/ui/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import shutil
import sys
import warnings
from json import JSONDecodeError
from pathlib import Path

Expand All @@ -11,6 +12,10 @@
from rascal2.paths import EXAMPLES_PATH, EXAMPLES_TEMP_PATH


class InvalidResultWarning(Warning):
"""Warning for invalid calculation results."""


def copy_example_project(load_path):
"""Copy example project to temp directory so user does not modify original.

Expand Down Expand Up @@ -39,6 +44,106 @@ def copy_example_project(load_path):
return str(load_path)


def validate_plot_data(project, results):
"""Validate plot data.

Parameters
----------
project : ratapi.Project
The project
results : Union[ratapi.outputs.Results, ratapi.outputs.BayesResults]
The calculation results.
"""
if results is None:
return

num_of_contrasts = len(project.contrasts)
num_of_domains = 1 if project.calculation == "normal" else 2

sub_rough_size = len(results.contrastParams.subRoughs)
if sub_rough_size != num_of_contrasts:
warnings.warn(
"The contrastParams.subRoughs entry in results has an incorrect size. "
f"The size ({sub_rough_size}) should be equal to the number of contrast ({num_of_contrasts}).",
InvalidResultWarning,
stacklevel=1,
)

for attr in ["reflectivity", "shiftedData", "sldProfiles", "resampledLayers"]:
entry = getattr(results, attr)
num_rows = len(entry)
if num_rows != num_of_contrasts:
warnings.warn(
f"The {attr} entry in results has an incorrect number of rows. "
f"The number of rows ({num_rows}) should be equal to the number of contrast ({num_of_contrasts}).",
InvalidResultWarning,
stacklevel=1,
)

for i in range(num_rows):
if isinstance(entry[i], list):
if len(entry[i]) != num_of_domains:
warnings.warn(
f"The {attr} entry in results has an incorrect number of columns. "
f"Row {i} has {len(entry[i])} columns instead of {num_of_domains}.",
InvalidResultWarning,
stacklevel=1,
)
for j in range(len(entry[i])):
if len(entry[i][j].shape) != 2 or entry[i][j].shape[1] < 2:
warnings.warn(
f"The {attr} entry (row {i}, column {j}) in results has incorrect dimensions.",
InvalidResultWarning,
stacklevel=1,
)
else:
if len(entry[i].shape) != 2 or entry[i].shape[1] < 2:
warnings.warn(
f"The {attr} entry (row {i}) in results has incorrect dimensions.",
InvalidResultWarning,
stacklevel=1,
)

if isinstance(results, ratapi.outputs.BayesResults):
for attr in ["reflectivity", "sld"]:
entry = getattr(results.predictionIntervals, attr)
dim_source = results.sldProfiles if attr == "sld" else results.reflectivity
num_rows = len(entry)
if num_rows != num_of_contrasts:
warnings.warn(
f"The predictionIntervals.{attr} entry in results has an incorrect number of rows. "
f"The number of rows ({num_rows}) should match the number of contrast ({num_of_contrasts}).",
InvalidResultWarning,
stacklevel=1,
)

for i in range(num_rows):
if isinstance(entry[i], list):
if len(entry[i]) != num_of_domains:
warnings.warn(
f"The predictionIntervals.{attr} entry in results has an incorrect number of columns. "
f"Row {i} has {len(entry[i])} columns instead of {num_of_domains}.",
InvalidResultWarning,
stacklevel=1,
)

for j in range(len(entry[i])):
if entry[i][j].shape != (5, dim_source[i][j].shape[0]):
warnings.warn(
f"The predictionIntervals.{attr} entry (row {i}, column {j}) "
"in results has incorrect dimensions.",
InvalidResultWarning,
stacklevel=1,
)
else:
if entry[i].shape != (5, dim_source[i].shape[0]):
warnings.warn(
f"The predictionIntervals.{attr} entry (row {i}) in results has incorrect dimensions.",
InvalidResultWarning,
stacklevel=1,
)


class MainWindowModel(QtCore.QObject):
"""Manages project data and communicates to view via signals.

Expand Down
14 changes: 12 additions & 2 deletions rascal2/ui/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from rascal2.core.writer import write_result_to_zipped_csvs
from rascal2.settings import update_recent_projects

from .model import MainWindowModel
from .model import InvalidResultWarning, MainWindowModel, validate_plot_data


class MainWindowPresenter:
Expand Down Expand Up @@ -45,19 +45,29 @@ def create_project(self, name: str, save_path: str):
self.initialise_ui()

def load_project(self, load_path: str):
"""Load an existing RAT project then initialise UI.
"""Load an existing RAT project.

Parameters
----------
load_path : str
The path from which to load the project.

"""
warning = None
self.model.load_project(load_path)
with warnings.catch_warnings(record=True) as records:
validate_plot_data(self.model.project, self.model.results)
if records:
self.model.results = None
warning = [
str(record.message) for record in records if isinstance(record.message, InvalidResultWarning)
]
if self.model.results is None:
self.model.results = self.quick_run()
update_recent_projects(load_path)

return warning

def load_r1_project(self, load_path: str):
"""Load a RAT project from a RasCAL-1 project file.

Expand Down
41 changes: 40 additions & 1 deletion tests/ui/test_model.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import patch
Expand All @@ -8,9 +9,11 @@
from ratapi.outputs import CalculationResults, ContrastParams
from ratapi.utils.enums import Calculations

from rascal2.ui.model import MainWindowModel
from rascal2.ui.model import MainWindowModel, validate_plot_data
from tests.utils import check_results_equal

DATA_PATH = Path(__file__, "../../data/").resolve()


@pytest.fixture
def empty_results():
Expand Down Expand Up @@ -77,6 +80,42 @@ def test_save_project(empty_results, model):
assert '"fitParams": []' in results


@pytest.mark.parametrize(
("result_file", "warning_count"),
(
("results_normal_calculate.json", 5),
("results_domains_dream.json", 10),
("results_domains_ns.json", 10),
),
)
def test_validate_plot_data_throws_warnings(result_file, warning_count):
project = Project()
result = Results.load(DATA_PATH / result_file)
with warnings.catch_warnings(record=True) as records:
validate_plot_data(project, result)
assert len(records) == warning_count


@pytest.mark.parametrize(
("result_file", "calc_type", "num_of_contrast"),
(
("results_normal_calculate.json", Calculations.Normal, 2),
("results_domains_dream.json", Calculations.Domains, 1),
("results_domains_ns.json", Calculations.Domains, 1),
),
)
def test_validate_plot_data(result_file, calc_type, num_of_contrast):
project = Project(calculation=calc_type)
for i in range(num_of_contrast):
project.contrasts.append(name=f"Contrast {i}")

result = Results.load(DATA_PATH / result_file)
with warnings.catch_warnings(record=True) as records:
validate_plot_data(project, result)
print([m.message for m in records])
assert len(records) == 0


def test_save_project_as_script(model):
model.project = Project(calculation="domains", name="test project")
with TemporaryDirectory() as tmpdir:
Expand Down
Loading