From ba6e8e472345548c849eff5497b4b571f31dd28a Mon Sep 17 00:00:00 2001 From: Marie Backman Date: Wed, 4 Dec 2024 16:28:37 -0500 Subject: [PATCH] Add option for const Q binning per run (#134) * add new table column 'Const. Q Bin.' * change ReductionTableColumnIndex base class to IntEnum * pass const Q binning option to lr_reduction * add unit test for ReductionTableHandler * saving and loading of parameter const_q * update stored configuration state upon checkbox state changed * update codecov-action version * add unit test for ConstQCheckBoxHandler * add ReductionTableHandler unit testing * add release note --- .github/workflows/test-and-deploy.yml | 2 +- ...pulate_reduction_table_from_list_lrdata.py | 15 +- RefRed/calculations/lr_data.py | 5 + .../update_reduction_table_metadata.py | 39 ++-- RefRed/configuration/export_xml_config.py | 3 + ...oad_reduction_table_from_lconfigdataset.py | 2 + RefRed/configuration/loading_configuration.py | 15 +- RefRed/gui_handling/gui_utility.py | 6 +- RefRed/initialization/gui.py | 32 +++- RefRed/interfaces/mytablewidget.py | 7 +- RefRed/interfaces/refred_main_interface.ui | 5 + RefRed/lconfigdataset.py | 1 + RefRed/main.py | 6 + .../individual_reduction_settings_handler.py | 5 + RefRed/reduction/live_reduction_handler.py | 3 +- .../const_q_checkbox_handler.py | 56 ++++++ .../reduction_table_handler.py | 168 ++++++++++++------ .../update_reduction_table.py | 12 +- docs/release_notes.rst | 1 + .../data/REFL_188298_data_reduction_script.py | 14 +- .../REF_L_188299_to_188301_plus_norm_runs.py | 6 +- .../configuration/test_export_xml_config.py | 5 + .../test_loading_configuration.py | 2 + .../reduction/test_live_reduction_handler.py | 21 +++ .../test_const_q_checkbox_handler.py | 37 ++++ .../test_reduction_table_handling.py | 86 +++++++++ .../test_update_reduction_table.py | 8 +- test/utilities.py | 5 + 28 files changed, 435 insertions(+), 132 deletions(-) create mode 100644 RefRed/reduction_table_handling/const_q_checkbox_handler.py create mode 100644 test/unit/RefRed/reduction/test_live_reduction_handler.py create mode 100644 test/unit/RefRed/reduction_table_handling/test_const_q_checkbox_handler.py create mode 100644 test/unit/RefRed/reduction_table_handling/test_reduction_table_handling.py create mode 100644 test/utilities.py diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 00fcfb28..3f39c17c 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -32,7 +32,7 @@ jobs: - name: Run Tests run: xvfb-run -a python -m pytest -vv --cov=RefRed --cov-report=xml --cov-report=term test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} # required diff --git a/RefRed/autopopulatemaintable/populate_reduction_table_from_list_lrdata.py b/RefRed/autopopulatemaintable/populate_reduction_table_from_list_lrdata.py index 5a671ba6..e0422f14 100644 --- a/RefRed/autopopulatemaintable/populate_reduction_table_from_list_lrdata.py +++ b/RefRed/autopopulatemaintable/populate_reduction_table_from_list_lrdata.py @@ -4,7 +4,8 @@ import numpy as np # application imports -from RefRed.interfaces.mytablewidget import ReductionTableColumIndex +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex +from RefRed.reduction_table_handling.reduction_table_handler import ReductionTableHandler from RefRed.tabledata import TableData @@ -36,11 +37,12 @@ def __init__(self, parent=None, list_lrdata=None, list_wks=None, list_run=None, self.big_table_data: TableData = self.parent.big_table_data self.reductionTable_col = ( - int(ReductionTableColumIndex.DATA_RUN) if self.is_data else int(ReductionTableColumIndex.NORM_RUN) + ReductionTableColumnIndex.DATA_RUN if self.is_data else ReductionTableColumnIndex.NORM_RUN ) if is_data: - self.clear_reductionTable() + o_reduction_table_handler = ReductionTableHandler(parent=self.parent) + o_reduction_table_handler.clear_reduction_table() self.insert_runs_into_table() @@ -123,12 +125,5 @@ def insert_data_runs_into_table(self): str_run = str(_run) self.parent.ui.reductionTable.item(_index, self.reductionTable_col).setText(str_run) - def clear_reductionTable(self): - nbr_row = self.parent.ui.reductionTable.rowCount() - nbr_col = self.parent.ui.reductionTable.columnCount() - for _row in range(nbr_row): - for _col in range(1, nbr_col): - self.parent.ui.reductionTable.item(_row, _col).setText("") - def clear_big_table_data(self): self.big_table_data = TableData(self.parent.REDUCTIONTABLE_MAX_ROWCOUNT) diff --git a/RefRed/calculations/lr_data.py b/RefRed/calculations/lr_data.py index 7eebedf7..33d181b6 100644 --- a/RefRed/calculations/lr_data.py +++ b/RefRed/calculations/lr_data.py @@ -189,6 +189,8 @@ def __init__( self.back_flag: bool = True self.two_backgrounds: bool = False + self.const_q: bool = False # reduction using constant Q binning + self.all_plot_axis = AllPlotAxis() self.tof_auto_flag = True self.new_detector_geometry_flag = self.is_nexus_taken_after_refDate() @@ -207,6 +209,7 @@ def __init__( self.low_res_flag = bool(lconfig.data_low_res_flag) self.back_flag = bool(lconfig.data_back_flag) self.two_backgrounds = bool(lconfig.data_two_backgrounds) + self.const_q = bool(lconfig.const_q) else: self.peak = [int(lconfig.norm_peak[0]), int(lconfig.norm_peak[1])] self.back = [int(lconfig.norm_back[0]), int(lconfig.norm_back[1])] @@ -215,6 +218,7 @@ def __init__( self.low_res_flag = bool(lconfig.norm_low_res_flag) self.back_flag = bool(lconfig.norm_back_flag) self.two_backgrounds = bool(lconfig.norm_two_backgrounds) + self.const_q = bool(lconfig.const_q) elif reduction_table_cell is not None: self.tof_auto_flag = reduction_table_cell.tof_auto_flag @@ -228,6 +232,7 @@ def __init__( self.low_res_flag = reduction_table_cell.low_res_flag self.back_flag = reduction_table_cell.back_flag self.two_backgrounds = reduction_table_cell.two_backgrounds + self.const_q = reduction_table_cell.const_q else: pf = PeakFinderDerivation(list(range(len(self.ycountsdata))), self.ycountsdata) diff --git a/RefRed/calculations/update_reduction_table_metadata.py b/RefRed/calculations/update_reduction_table_metadata.py index dc4c1b0d..324679ae 100644 --- a/RefRed/calculations/update_reduction_table_metadata.py +++ b/RefRed/calculations/update_reduction_table_metadata.py @@ -1,4 +1,5 @@ -from qtpy import QtWidgets, QtCore +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex +from RefRed.reduction_table_handling.reduction_table_handler import ReductionTableHandler class UpdateReductionTableMetadata(object): @@ -18,36 +19,18 @@ def update(self): q_range = lrdata.q_range lambda_range = lrdata.lambda_range incident_angle = lrdata.incident_angle + const_q = lrdata.const_q [qmin, qmax] = q_range str_qmin = "%.4f" % qmin - _item_min = QtWidgets.QTableWidgetItem(str_qmin) - _item_min.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) str_qmax = "%.4f" % qmax - _item_max = QtWidgets.QTableWidgetItem(str_qmax) - _item_max.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - [lmin, lmax] = lambda_range - _item_lmin = QtWidgets.QTableWidgetItem(str(lmin)) - _item_lmin.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - _item_lmax = QtWidgets.QTableWidgetItem(str(lmax)) - _item_lmax.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - incident_angle = incident_angle str_incident_angle = "%.2f" % incident_angle - _item_incident = QtWidgets.QTableWidgetItem(str_incident_angle) - _item_incident.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - parent.ui.reductionTable.setItem(row, 6, _item_min) - parent.ui.reductionTable.setItem(row, 7, _item_max) - parent.ui.reductionTable.setItem(row, 4, _item_lmin) - parent.ui.reductionTable.setItem(row, 5, _item_lmax) - parent.ui.reductionTable.setItem(row, 3, _item_incident) - - def sortIntArray(self, _array): - [_element1, _element2] = _array - _element1 = int(_element1) - _element2 = int(_element2) - _element_min = min([_element1, _element2]) - _element_max = max([_element1, _element2]) - return [_element_min, _element_max] + + handler = ReductionTableHandler(parent) + handler.set_table_item_text(row, ReductionTableColumnIndex.Q_MIN, str_qmin) + handler.set_table_item_text(row, ReductionTableColumnIndex.Q_MAX, str_qmax) + handler.set_table_item_text(row, ReductionTableColumnIndex.LAMBDA_MIN, str(lmin)) + handler.set_table_item_text(row, ReductionTableColumnIndex.LAMBDA_MAX, str(lmax)) + handler.set_table_item_text(row, ReductionTableColumnIndex.TWO_THETA, str_incident_angle) + handler.set_checkbox_state(row, ReductionTableColumnIndex.CONST_Q_BINS, const_q) diff --git a/RefRed/configuration/export_xml_config.py b/RefRed/configuration/export_xml_config.py index 1834932c..c2b79dd1 100644 --- a/RefRed/configuration/export_xml_config.py +++ b/RefRed/configuration/export_xml_config.py @@ -61,6 +61,7 @@ def main_part(self): q_range = _data.q_range lambda_range = _data.lambda_range incident_angle = _data.incident_angle + const_q = _data.const_q _norm: Optional[LRData] = _big_table_data[row, 1] if _norm is not None: @@ -176,6 +177,8 @@ def main_part(self): str_array.append(o_general_settings.dead_time.to_xml(indent=" ") + "\n") # dead time settings + str_array.append(' ' + str(const_q) + '\n') + str_array.append(' \n') str_array.append(' \n') diff --git a/RefRed/configuration/load_reduction_table_from_lconfigdataset.py b/RefRed/configuration/load_reduction_table_from_lconfigdataset.py index 829c7835..b25dec88 100644 --- a/RefRed/configuration/load_reduction_table_from_lconfigdataset.py +++ b/RefRed/configuration/load_reduction_table_from_lconfigdataset.py @@ -122,6 +122,7 @@ def update_lrdata(self, lrdata=None, lconfig=None, type='data', row=0): tof_auto_flag = lconfig.tof_auto_flag tof_range = lconfig.tof_range + const_q = lconfig.const_q # using lconfig values lrdata.peak = [peak1, peak2] @@ -134,6 +135,7 @@ def update_lrdata(self, lrdata=None, lconfig=None, type='data', row=0): lrdata.tof_range = tof_range lrdata.tof_auto_flag = tof_auto_flag lrdata.full_file_name = full_file_name + lrdata.const_q = const_q index_col = 0 if type == 'data' else 1 reduction_table_index_col = index_col + 1 diff --git a/RefRed/configuration/loading_configuration.py b/RefRed/configuration/loading_configuration.py index 1a86003a..29c38254 100644 --- a/RefRed/configuration/loading_configuration.py +++ b/RefRed/configuration/loading_configuration.py @@ -5,7 +5,7 @@ from typing import Any # third party imports -from qtpy import QtGui, QtCore, QtWidgets +from qtpy import QtWidgets from qtpy.QtWidgets import QFileDialog # application imports @@ -22,6 +22,7 @@ ) from RefRed.lconfigdataset import LConfigDataset from RefRed.plot.clear_plots import ClearPlots +from RefRed.reduction_table_handling.reduction_table_handler import ReductionTableHandler from RefRed.status_message_handler import StatusMessageHandler from RefRed.tabledata import TableData from RefRed.utilities import str2bool @@ -233,6 +234,8 @@ def get_item_boolean(item_name: str, default) -> bool: _lambda_max = self.getNodeValue(node, "to_lambda_range") iMetadata.lambda_range = [_lambda_min, _lambda_max] + iMetadata.const_q = get_item_boolean("const_q", default=False) + iMetadata.tof_units = "micros" _data_sets = self.getNodeValue(node, "data_sets") @@ -299,14 +302,8 @@ def clear_display(self): ClearPlots(parent=self.parent, is_data=True, is_norm=True, all_plots=True) def clear_reductionTable(self): - nbr_row = self.parent.ui.reductionTable.rowCount() - nbr_col = self.parent.ui.reductionTable.columnCount() - _brush_color = QtGui.QBrush() - _brush_color.setColor(QtCore.Qt.black) - for _row in range(nbr_row): - for _col in range(1, nbr_col): - self.parent.ui.reductionTable.item(_row, _col).setText("") - self.parent.ui.reductionTable.item(_row, _col).setForeground(_brush_color) + reduction_table_handler = ReductionTableHandler(parent=self.parent) + reduction_table_handler.clear_reduction_table() def populate_reduction_table_from_lconfigdataset(self): PopulateReductionTable(parent=self.parent) diff --git a/RefRed/gui_handling/gui_utility.py b/RefRed/gui_handling/gui_utility.py index b791f81a..3de64db2 100644 --- a/RefRed/gui_handling/gui_utility.py +++ b/RefRed/gui_handling/gui_utility.py @@ -8,7 +8,7 @@ # application imports from RefRed import WINDOW_TITLE -from RefRed.interfaces.mytablewidget import ReductionTableColumIndex +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex from RefRed.tabledata import TableData if TYPE_CHECKING: # imported only when running mypy but not imported when the application is running @@ -81,7 +81,7 @@ def get_current_table_reduction_check_box_checked(self) -> int: ------- Row index, or `NULL_ACTIVE_ROW` if no row is active """ - column_index = int(ReductionTableColumIndex.PLOTTED) + column_index = ReductionTableColumnIndex.PLOTTED for row_index in range(self.parent.REDUCTIONTABLE_MAX_ROWCOUNT): check_box: QtWidgets.QCheckBox = self.parent.ui.reductionTable.cellWidget(row_index, column_index) if check_box.checkState() == Qt.Checked: @@ -116,7 +116,7 @@ def get_other_row_with_same_run_number_as_row( def get_norm_runnumber(table_row_index: int) -> str: r"""run number of the direct beam run stored in row `index` of the reduction table""" - table_column_index = int(ReductionTableColumIndex.NORM_RUN) + table_column_index = ReductionTableColumnIndex.NORM_RUN return str(self.parent.ui.reductionTable.item(table_row_index, table_column_index).text()) all_rows = [row] diff --git a/RefRed/initialization/gui.py b/RefRed/initialization/gui.py index 609085b8..236f8a00 100644 --- a/RefRed/initialization/gui.py +++ b/RefRed/initialization/gui.py @@ -3,6 +3,7 @@ import socket from RefRed import WINDOW_TITLE +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex from RefRed.plot.all_plot_axis import AllPlotAxis from RefRed.gui_handling.gui_utility import GuiUtility from RefRed.gui_handling.update_plot_widget_status import UpdatePlotWidgetStatus @@ -20,9 +21,10 @@ class Gui(object): "\u03bbmax (\u00c5)", "Qmin (1/\u00c5)", "Qmax (1/\u00c5)", + "Const. Q Bin.", "Comments", ] - column_widths = [60, 200, 200, 65, 85, 85, 95, 95, 400] + column_widths = [60, 200, 200, 65, 85, 85, 95, 95, 95, 300] stitching_column_widths = [150, 60, 60] gui_size_coeff = 2.0 / 3.0 @@ -132,7 +134,7 @@ def set_main_table(self): for row_index in range(self.parent.REDUCTIONTABLE_MAX_ROWCOUNT): for col_index in range(len(self.column_widths)): - if col_index == 0: + if col_index == ReductionTableColumnIndex.PLOTTED: _widget = QtWidgets.QCheckBox() _widget.setChecked(False) _widget.setEnabled(True) @@ -143,12 +145,36 @@ def set_main_table(self): _widget.stateChanged.connect(_signal_func) parent.ui.reductionTable.setCellWidget(row_index, col_index, _widget) - elif (col_index == 1) or (col_index == 2): + elif (col_index == ReductionTableColumnIndex.DATA_RUN) or ( + col_index == ReductionTableColumnIndex.NORM_RUN + ): _item = QtWidgets.QTableWidgetItem() _flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable _item.setFlags(_flags) parent.ui.reductionTable.setItem(row_index, col_index, _item) + elif col_index == ReductionTableColumnIndex.CONST_Q_BINS: + # Create checkbox + _widget = QtWidgets.QCheckBox() + _widget.setChecked(False) + _widget.setEnabled(True) + # connect handler to state change + _widget.stateChanged.connect( + lambda state=0, row=row_index: self.parent.reduction_table_const_q_handler(state, row) + ) + + # Create a layout to center the checkbox + _layout = QtWidgets.QHBoxLayout() + _layout.addWidget(_widget) + _layout.setAlignment(_widget, QtCore.Qt.AlignCenter) # Align the button to the center + _layout.setContentsMargins(0, 0, 0, 0) + + # Create a QWidget to hold the layout + _container = QtWidgets.QWidget() + _container.setLayout(_layout) + + parent.ui.reductionTable.setCellWidget(row_index, col_index, _container) + else: _item = QtWidgets.QTableWidgetItem() _item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) diff --git a/RefRed/interfaces/mytablewidget.py b/RefRed/interfaces/mytablewidget.py index c2e010c3..c050406a 100644 --- a/RefRed/interfaces/mytablewidget.py +++ b/RefRed/interfaces/mytablewidget.py @@ -1,5 +1,5 @@ # standard imports -from enum import Enum +from enum import IntEnum # third party imports from qtpy import QtWidgets @@ -29,7 +29,7 @@ def setUI(self, ui_parent): self.parent = ui_parent -class ReductionTableColumIndex(Enum): +class ReductionTableColumnIndex(IntEnum): """ Enumeration class associating a column index to a word easy to understand what the column index is for """ @@ -42,7 +42,8 @@ class ReductionTableColumIndex(Enum): LAMBDA_MAX = 5 Q_MIN = 6 Q_MAX = 7 - COMMENTS = 8 + CONST_Q_BINS = 8 + COMMENTS = 9 def __int__(self): return self.value diff --git a/RefRed/interfaces/refred_main_interface.ui b/RefRed/interfaces/refred_main_interface.ui index 4d1bd81a..82240b0c 100644 --- a/RefRed/interfaces/refred_main_interface.ui +++ b/RefRed/interfaces/refred_main_interface.ui @@ -4553,6 +4553,11 @@ New Column + + + New Column + + diff --git a/RefRed/lconfigdataset.py b/RefRed/lconfigdataset.py index b8eeeb9d..b5193399 100644 --- a/RefRed/lconfigdataset.py +++ b/RefRed/lconfigdataset.py @@ -45,6 +45,7 @@ class LConfigDataset(object): q_range = ['0', '0'] lambda_range = ['0', '0'] + const_q = False reduce_q_axis = [] reduce_y_axis = [] diff --git a/RefRed/main.py b/RefRed/main.py index 03d51311..f64f4065 100644 --- a/RefRed/main.py +++ b/RefRed/main.py @@ -38,6 +38,7 @@ from RefRed.preview_config.preview_config import PreviewConfig from RefRed.reduction.live_reduction_handler import LiveReductionHandler from RefRed.reduction.reduced_data_handler import ReducedDataHandler +from RefRed.reduction_table_handling.const_q_checkbox_handler import ConstQCheckBoxHandler from RefRed.reduction_table_handling.reduction_table_check_box import ReductionTableCheckBox from RefRed.reduction_table_handling.update_reduction_table import UpdateReductionTable from RefRed.reduction_table_handling.reduction_table_right_click import ReductionTableRightClick @@ -233,6 +234,11 @@ def logy_toggle_data_stitching(self, checked): def reduction_table_visibility_changed_test(self, state, row): ReductionTableCheckBox(parent=self, row_selected=row) + @config_file_has_been_modified + def reduction_table_const_q_handler(self, state, row): + """Handles updating stored configuration upon change in const Q checkbox state""" + ConstQCheckBoxHandler(parent=self, row=row, state=state) + def file_loaded(self, row, is_data_displayed, is_display_requested): """Event call-back used to display plots and re-enable the reduction table after loading""" if is_display_requested: diff --git a/RefRed/reduction/individual_reduction_settings_handler.py b/RefRed/reduction/individual_reduction_settings_handler.py index 8e8ea7e3..17e0733b 100644 --- a/RefRed/reduction/individual_reduction_settings_handler.py +++ b/RefRed/reduction/individual_reduction_settings_handler.py @@ -50,6 +50,7 @@ def retrieve(self): self._tof_range = self.get_tof_range() self._output_workspace_name = self.define_output_workspace_name(run_numbers=self._data_run_numbers) + self._const_q = self.get_const_q() def to_dict(self): """ @@ -77,6 +78,7 @@ def norm_setting(setting: str) -> Optional[Any]: norm_x_range_flag=self._norm_low_res_flag, norm_x_range=self._norm_low_res_range, tof_range=self._tof_range, + const_q=self._const_q, ) return pars @@ -200,3 +202,6 @@ def get_data_run_numbers(self): def get_run_numbers(self, column_index=1): run_numbers = self.parent.ui.reductionTable.item(self.row_index, column_index).text() return str(run_numbers) + + def get_const_q(self): + return bool(self.data.const_q) diff --git a/RefRed/reduction/live_reduction_handler.py b/RefRed/reduction/live_reduction_handler.py index a781d60c..a6eab912 100644 --- a/RefRed/reduction/live_reduction_handler.py +++ b/RefRed/reduction/live_reduction_handler.py @@ -83,6 +83,7 @@ def run(self): q, r, dr, info = template.process_from_template( reduction_pars['data_files'], template_data, + q_summing=reduction_pars['const_q'], # const Q binning info=True, normalize=reduction_pars['apply_normalization'], ) @@ -144,7 +145,7 @@ def export(self): script += "reduction_pars = json.loads('%s')\n" % json_pars script += "template_data = reduction_template_reader.ReductionParameters()\n" script += "template_data.from_dict(reduction_pars)\n" - script += "q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization'])\n\n" # noqa: E501 + script += "q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization'])\n\n" # noqa: E501 with open(filename, 'w') as fd: fd.write(script) diff --git a/RefRed/reduction_table_handling/const_q_checkbox_handler.py b/RefRed/reduction_table_handling/const_q_checkbox_handler.py new file mode 100644 index 00000000..5fd26285 --- /dev/null +++ b/RefRed/reduction_table_handling/const_q_checkbox_handler.py @@ -0,0 +1,56 @@ +# standard imports + +# third-party imports +from qtpy.QtCore import Qt # type: ignore + +# package imports +from RefRed.tabledata import TableData + + +class ConstQCheckBoxHandler: + """ + Handler for the constant Q binning checkbox in the reduction table. + + This class updates the `const_q` attribute in the reflectometry data, normalization data, and + reduction configuration based on the state of the constant Q binning checkbox. + """ + + def __init__(self, parent=None, row: int = -1, state: Qt.CheckState = 0): + """ + Initializes the handler and updates the stored configuration based on the checkbox state. + - Converts the checkbox state to a boolean value. + - Retrieves the reflectometry data, normalization data, and reduction configuration from the parent. + + Parameters + ---------- + parent + The parent object containing the big table data + row + The row index in the table + state + The state of the checkbox + """ + if row == -1: + return + if parent is None: + return + + self.parent = parent + + # convert checkbox state to bool + const_q = state == Qt.Checked + + # get the stored configuration + big_table_data: TableData = parent.big_table_data + data = big_table_data.reflectometry_data(row) + norm = big_table_data.normalization_data(row) + config = big_table_data.reduction_config(row) + + if data is None: + return + + # update the stored configuration + config.const_q = const_q + data.const_q = const_q + if norm is not None: + norm.const_q = const_q diff --git a/RefRed/reduction_table_handling/reduction_table_handler.py b/RefRed/reduction_table_handling/reduction_table_handler.py index 9c2d649f..f0525c64 100644 --- a/RefRed/reduction_table_handling/reduction_table_handler.py +++ b/RefRed/reduction_table_handling/reduction_table_handler.py @@ -1,12 +1,12 @@ # standard imports # third party imports -from qtpy import QtWidgets, QtCore -from qtpy.QtCore import Qt # type: ignore +from qtpy import QtCore, QtGui, QtWidgets # application imports from RefRed import WINDOW_TITLE from RefRed.gui_handling.gui_utility import GuiUtility +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex from RefRed.plot.clear_plots import ClearPlots from RefRed.tabledata import TableData @@ -18,10 +18,11 @@ class ReductionTableHandler(object): def __init__(self, parent=None): self.parent = parent + self.table = parent.ui.reductionTable def full_clear(self): self.__clear_big_table_data() - self.__clear_reduction_table() + self.clear_reduction_table() self.__clear_metadata() self.__clear_plots() self.__reset_default_config_file_name() @@ -39,42 +40,25 @@ def clear_rows_selected(self): self.__clear_plots() self.__clear_rows_big_table_data() self.__clear_rows_reduction_table() - self.__shifs_none_empty_rows_reduction_table() + self.__shift_none_empty_rows_reduction_table() self.__to_do_if_table_empty() def __to_do_if_table_empty(self): """If the table is now empty, various reset algos""" - _cell_value = str(self.parent.ui.reductionTable.item(0, 1).text()) + _cell_value = str(self.table.item(0, 1).text()) if _cell_value == "": self.__reset_default_config_file_name() def __clear_rows_reduction_table(self): _from_row = self.from_row _to_row = self.to_row - _nbr_col = self.parent.ui.reductionTable.columnCount() + _nbr_col = self.table.columnCount() for row_index in range(_from_row, _to_row + 1): for col_index in range(_nbr_col): - if col_index == 0: - _widget = QtWidgets.QCheckBox() - _widget.setChecked(False) - _widget.setEnabled(True) - _sig_func = lambda state=0, row=row_index: self.parent.reduction_table_visibility_changed_test( # noqa: E501, E731 - state, row - ) - _widget.stateChanged.connect(_sig_func) - self.parent.ui.reductionTable.setCellWidget(row_index, col_index, _widget) - - elif (col_index == 1) or (col_index == 2): - _item = QtWidgets.QTableWidgetItem() - _item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) - self.parent.ui.reductionTable.setItem(row_index, col_index, _item) - - else: - _item = QtWidgets.QTableWidgetItem() - _item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.parent.ui.reductionTable.setItem(row_index, col_index, _item) - - def __shifs_none_empty_rows_reduction_table(self): + self.__clear_reduction_table_cell(row_index, col_index) + + def __shift_none_empty_rows_reduction_table(self): + """Shift rows in the reduction table to eliminate empty rows""" _nbr_row = self.parent.REDUCTIONTABLE_MAX_ROWCOUNT _to_row = self.to_row if _to_row == (_nbr_row - 1): @@ -82,30 +66,13 @@ def __shifs_none_empty_rows_reduction_table(self): _from_row = self.from_row _row_offset = 0 - _nbr_col = self.parent.ui.reductionTable.columnCount() + _nbr_col = self.table.columnCount() for row_index in range(_to_row + 1, _nbr_row): _target_row_index = _from_row + _row_offset for col_index in range(_nbr_col): - if col_index == 0: - _widget = self.parent.ui.reductionTable.cellWidget(_target_row_index, col_index) - _widget.setChecked(self.__is_row_selected_checked(row_index)) - else: - _cell_value = self.parent.ui.reductionTable.item(row_index, col_index).text() - self.parent.ui.reductionTable.item(_target_row_index, col_index).setText(_cell_value) + self.__copy_reduction_table_cell_value(row_index, _target_row_index, col_index) _row_offset += 1 - def __is_row_selected_checked(self, row_selected): - _widget = self.parent.ui.reductionTable.cellWidget(row_selected, 0) - current_state = _widget.checkState() - if current_state == Qt.Unchecked: - return False - else: - return True - - def __get_checkbox_signal_function(self, row_index): - root_function_name = "self.parent.reduction_table_visibility_changed_" + str(row_index) - return root_function_name - def __clear_rows_big_table_data(self): r"""Delete rows with indexes in the range [self.from_row, self.to_row] @@ -125,7 +92,7 @@ def __is_row_displayed_in_range_selected(self): return False def __get_range_row_selected(self): - selected_range = self.parent.ui.reductionTable.selectedRanges() + selected_range = self.table.selectedRanges() self.to_row = selected_range[0].bottomRow() self.from_row = selected_range[0].topRow() @@ -155,12 +122,109 @@ def __clear_plots(self): plot_ix=True, ) - def __clear_reduction_table(self): - nbr_row = self.parent.ui.reductionTable.rowCount() - nbr_col = self.parent.ui.reductionTable.columnCount() + def clear_reduction_table(self): + nbr_row = self.table.rowCount() + nbr_col = self.table.columnCount() for _row in range(nbr_row): - for _col in range(1, nbr_col): - self.parent.ui.reductionTable.item(_row, _col).setText("") + for _col in range(1, nbr_col): # skips first column: plotted checkbox + self.__clear_reduction_table_cell(_row, _col) def __clear_big_table_data(self): self.parent.big_table_data = TableData(self.parent.REDUCTIONTABLE_MAX_ROWCOUNT) + + def __copy_reduction_table_cell_value(self, from_row, to_row, col): + """Copy the table cell value from one row to another in the same column""" + # copy cell containing text + from_item = self.__get_table_cell_item(from_row, col) + to_item = self.__get_table_cell_item(to_row, col) + if from_item and to_item: + text = from_item.text() + text_foreground = from_item.foreground() + to_item.setText(text) + to_item.setForeground(text_foreground) + # copy cell containing checkbox + from_checkbox = self.__get_table_cell_checkbox_widget(from_row, col) + to_checkbox = self.__get_table_cell_checkbox_widget(to_row, col) + if from_checkbox and to_checkbox: + checked = from_checkbox.isChecked() + to_checkbox.setChecked(checked) + + def __clear_reduction_table_cell(self, row: int, col: int): + """ + Clear the reduction table cell given by row and col + - cells containing a QTableWidgetItem are set to empty string + - cells containing a QCheckBox are set as unchecked + """ + item = self.__get_table_cell_item(row, col) + if item: + item.setText("") + item.setForeground(QtGui.QBrush(QtCore.Qt.black)) # type: ignore + item.setBackground(QtGui.QBrush()) # reset to default color + if col in (ReductionTableColumnIndex.DATA_RUN, ReductionTableColumnIndex.NORM_RUN): + flags = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled # type: ignore + else: + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled # type: ignore + item.setFlags(flags) + checkbox_widget = self.__get_table_cell_checkbox_widget(row, col) + if checkbox_widget: + checkbox_widget.setChecked(False) + checkbox_widget.setEnabled(True) + + def set_table_item_text(self, row: int, col: int, text: str): + """ + Set the text in the reduction table cell given by row and col + + Parameters + ---------- + row: int + col: int + text: str + + Raises + ------ + ValueError + If the cell does not contain a QTableWidgetItem + """ + item = self.__get_table_cell_item(row, col) + if item is None: + raise ValueError("Reduction table cell does not contain a QTableWidgetItem") + item.setText(text) + + def __get_table_cell_item(self, row, col): + """Get QTableWidgetItem in the reduction table cell given by row and col""" + return self.table.item(row, col) + + def __get_table_cell_checkbox_widget(self, row, col): + """ + Get QCheckBox widget in the reduction table cell given by row and col, + whether it's the topmost cell widget or a child of the cell widget + """ + cell_widget = self.table.cellWidget(row, col) + if cell_widget is None: + return None + # check if the cell widget is a checkbox + if isinstance(cell_widget, QtWidgets.QCheckBox): + return cell_widget + # check if the cell widget has a checkbox as a child + checkbox = cell_widget.findChild(QtWidgets.QCheckBox) + return checkbox + + def set_checkbox_state(self, row: int, col: int, checked: bool): + """ + Set the state of the checkbox in the reduction table cell given by row and col + + Parameters + ---------- + row: int + col: int + checked: bool + + Raises + ------ + ValueError + If the cell does not contain a QCheckBox + """ + checkbox = self.__get_table_cell_checkbox_widget(row, col) + if checkbox is None: + raise ValueError("Reduction table cell does not contain a QCheckBox") + checkbox.setChecked(checked) diff --git a/RefRed/reduction_table_handling/update_reduction_table.py b/RefRed/reduction_table_handling/update_reduction_table.py index 3afbf712..61370414 100644 --- a/RefRed/reduction_table_handling/update_reduction_table.py +++ b/RefRed/reduction_table_handling/update_reduction_table.py @@ -5,10 +5,11 @@ from qtpy.QtWidgets import QApplication # application imports +import RefRed.colors from RefRed.calculations.check_list_run_compatibility_thread import CheckListRunCompatibilityThread from RefRed.calculations.locate_list_run import LocateListRun from RefRed.calculations.run_sequence_breaker import RunSequenceBreaker -import RefRed.colors +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex from RefRed.tabledata import TableData @@ -38,18 +39,19 @@ def __init__(self, parent=None, row=0, col=1, runs=None): # check if nexus can be found list_run_object = LocateListRun(list_run=list_run) + col_comments = ReductionTableColumnIndex.COMMENTS if list_run_object.list_run_not_found != []: str_list_run_not_found = [str(x) for x in list_run_object.list_run_not_found] runs_not_located = ', '.join(str_list_run_not_found) mess = "Can not locate %s run(s): %s" % (data_type, runs_not_located) - self.parent.ui.reductionTable.item(row, 8).setText(mess) + self.parent.ui.reductionTable.item(row, col_comments).setText(mess) _color = QtGui.QColor(RefRed.colors.VALUE_BAD) - self.parent.ui.reductionTable.item(row, 8).setBackground(_color) + self.parent.ui.reductionTable.item(row, col_comments).setBackground(_color) else: mess = "%s runs have been located!" % data_type - self.parent.ui.reductionTable.item(row, 8).setText(mess) + self.parent.ui.reductionTable.item(row, col_comments).setText(mess) _color = QtGui.QColor(RefRed.colors.VALUE_OK) - self.parent.ui.reductionTable.item(row, 8).setBackground(_color) + self.parent.ui.reductionTable.item(row, col_comments).setBackground(_color) list_run_found = list(list_run_object.list_run_found) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index e112fd76..2b68701e 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -21,6 +21,7 @@ Next **Of interest to the User**: - MR #135: update popup message for v4 deprecation +- MR #134: add column in the reduction table for constant Q binning during reduction **Of interest to the Developer:** diff --git a/test/data/REFL_188298_data_reduction_script.py b/test/data/REFL_188298_data_reduction_script.py index b45d9055..d4df470a 100644 --- a/test/data/REFL_188298_data_reduction_script.py +++ b/test/data/REFL_188298_data_reduction_script.py @@ -6,35 +6,35 @@ reduction_pars = json.loads('{"data_files": "188298", "norm_file": "188230", "data_peak_range": [128, 141], "substract_background": true, "background_roi": [125, 144], "data_x_range_flag": true, "data_x_range": [70, 185], "apply_normalization": true, "norm_peak_range": [130, 139], "subtract_norm_background": true, "norm_background_roi": [127, 142], "norm_x_range_flag": true, "norm_x_range": [75, 187], "tof_range": [51977.0051592, 65268.5238077], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188299", "norm_file": "188231", "data_peak_range": [129, 141], "substract_background": true, "background_roi": [126, 144], "data_x_range_flag": true, "data_x_range": [70, 181], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [74, 193], "tof_range": [41761.0114019, 55052.5300504], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188300", "norm_file": "188232", "data_peak_range": [128, 141], "substract_background": true, "background_roi": [125, 144], "data_x_range_flag": true, "data_x_range": [72, 185], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [78, 184], "tof_range": [31419.955747, 44711.4743955], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188301", "norm_file": "188233", "data_peak_range": [129, 142], "substract_background": true, "background_roi": [126, 145], "data_x_range_flag": true, "data_x_range": [74, 183], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [111, 157], "tof_range": [20879.5826929, 34171.1013413], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188302", "norm_file": "188234", "data_peak_range": [129, 141], "substract_background": true, "background_roi": [126, 144], "data_x_range_flag": true, "data_x_range": [70, 183], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [120, 147], "tof_range": [9964.0239461, 23255.5425945], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188303", "norm_file": "188234", "data_peak_range": [130, 140], "substract_background": true, "background_roi": [127, 143], "data_x_range_flag": true, "data_x_range": [75, 181], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [120, 147], "tof_range": [9964.0239461, 23255.5425945], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188304", "norm_file": "188234", "data_peak_range": [129, 138], "substract_background": true, "background_roi": [126, 141], "data_x_range_flag": true, "data_x_range": [75, 180], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [120, 147], "tof_range": [9964.0239461, 23255.5425945], "incident_medium_selected": "Si", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) diff --git a/test/data/REF_L_188299_to_188301_plus_norm_runs.py b/test/data/REF_L_188299_to_188301_plus_norm_runs.py index 0352e600..37ba5836 100644 --- a/test/data/REF_L_188299_to_188301_plus_norm_runs.py +++ b/test/data/REF_L_188299_to_188301_plus_norm_runs.py @@ -6,15 +6,15 @@ reduction_pars = json.loads('{"data_files": "188299", "norm_file": "188231", "data_peak_range": [129, 141], "substract_background": true, "background_roi": [126, 144], "data_x_range_flag": true, "data_x_range": [70, 181], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [74, 193], "tof_range": [41761.0114019, 55052.5300504], "incident_medium_selected": "", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188300", "norm_file": "188232", "data_peak_range": [128, 141], "substract_background": true, "background_roi": [125, 144], "data_x_range_flag": true, "data_x_range": [72, 185], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [78, 184], "tof_range": [31419.955747, 44711.4743955], "incident_medium_selected": "", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) reduction_pars = json.loads('{"data_files": "188301", "norm_file": "188233", "data_peak_range": [129, 142], "substract_background": true, "background_roi": [126, 145], "data_x_range_flag": true, "data_x_range": [74, 183], "apply_normalization": true, "norm_peak_range": [131, 139], "subtract_norm_background": true, "norm_background_roi": [128, 142], "norm_x_range_flag": true, "norm_x_range": [111, 157], "tof_range": [20879.5826929, 34171.1013413], "incident_medium_selected": "", "q_step": 0.02, "scaling_factor_flag": true, "scaling_factor_file": "test/data/sf_186529_Si_auto.cfg", "angle_offset": 0.01, "angle_offset_error": 0.001, "tof_steps": 40.0}') template_data = reduction_template_reader.ReductionParameters() template_data.from_dict(reduction_pars) -q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, info=True, normalize=reduction_pars['apply_normalization']) +q, r, dr, info = template.process_from_template(reduction_pars['data_files'], template_data, q_summing=reduction_pars['const_q'], info=True, normalize=reduction_pars['apply_normalization']) diff --git a/test/unit/RefRed/configuration/test_export_xml_config.py b/test/unit/RefRed/configuration/test_export_xml_config.py index 91502572..f71b9b0a 100644 --- a/test/unit/RefRed/configuration/test_export_xml_config.py +++ b/test/unit/RefRed/configuration/test_export_xml_config.py @@ -23,6 +23,11 @@ def test_header_part(self, mantid_mock, refred_mock): assert '1.0.0' in header assert 'RefRed-2.0.0' in header + def test_main_part(self): + config = ExportXMLConfig(MagicMock()) + config.main_part() + assert len(config.str_array) == 55 + if __name__ == "__main__": pytest.main() diff --git a/test/unit/RefRed/configuration/test_loading_configuration.py b/test/unit/RefRed/configuration/test_loading_configuration.py index ee5f0c1e..9e322567 100644 --- a/test/unit/RefRed/configuration/test_loading_configuration.py +++ b/test/unit/RefRed/configuration/test_loading_configuration.py @@ -156,6 +156,7 @@ def test_getMetadataObject(self): 'norm_x_range_flag': 'norm_x_range_flag', 'data_full_file_name': 'dataFullFileName1,dataFullFileName2', 'norm_full_file_name': 'normFullFileName1,normFullFileName2', + 'const_q': True, } def side_effect(_, arg, default=""): @@ -172,6 +173,7 @@ def side_effect(_, arg, default=""): assert config.data_back[0] == values['back_roi1_from'] assert config.data_back[1] == values['back_roi1_to'] assert config.data_low_res == [values['x_min_pixel'], values['x_max_pixel']] + assert config.const_q == values['const_q'] if __name__ == '__main__': diff --git a/test/unit/RefRed/reduction/test_live_reduction_handler.py b/test/unit/RefRed/reduction/test_live_reduction_handler.py new file mode 100644 index 00000000..41e601d7 --- /dev/null +++ b/test/unit/RefRed/reduction/test_live_reduction_handler.py @@ -0,0 +1,21 @@ +from RefRed.main import MainGui +from RefRed.reduction.live_reduction_handler import LiveReductionHandler +from test.utilities import load_run_from_reduction_table + + +class TestLiveReductionHandler: + def test_run(self, qtbot): + main_window = MainGui() + qtbot.addWidget(main_window) + main_window.ui.useNormalizationFlag.setChecked(False) + # load one run + row = 0 + load_run_from_reduction_table(main_window, row=row, col=1, run="188300") + qtbot.wait(2000) + # run reduction + handler = LiveReductionHandler(main_window) + assert len(handler.big_table_data[row][2].reduce_q_axis) == 0 + assert len(handler.big_table_data[row][2].reduce_y_axis) == 0 + handler.run() + assert len(handler.big_table_data[row][2].reduce_q_axis) == 69 + assert len(handler.big_table_data[row][2].reduce_y_axis) == 69 diff --git a/test/unit/RefRed/reduction_table_handling/test_const_q_checkbox_handler.py b/test/unit/RefRed/reduction_table_handling/test_const_q_checkbox_handler.py new file mode 100644 index 00000000..d8cddb3d --- /dev/null +++ b/test/unit/RefRed/reduction_table_handling/test_const_q_checkbox_handler.py @@ -0,0 +1,37 @@ +from unittest import mock +from unittest.mock import Mock + +from RefRed.calculations.lr_data import LRData +from RefRed.lconfigdataset import LConfigDataset +from RefRed.reduction_table_handling.const_q_checkbox_handler import ConstQCheckBoxHandler +from qtpy.QtCore import Qt # type: ignore + +from RefRed.tabledata import TableData + + +def test_const_q_checkbox_handler(): + big_table_data = TableData(max_row_count=1) + row = 0 + + with mock.patch.object(LRData, '__init__', return_value=None): + instance = LRData() + big_table_data.set_reflectometry_data(row, instance) + big_table_data.set_normalization_data(row, instance) + big_table_data.set_reduction_config(row, LConfigDataset()) + + data = big_table_data.reflectometry_data(row) + norm = big_table_data.normalization_data(row) + config = big_table_data.reduction_config(row) + + parent = Mock() + parent.big_table_data = big_table_data + + ConstQCheckBoxHandler(parent, row, state=Qt.Checked) + assert data.const_q is True + assert norm.const_q is True + assert config.const_q is True + + ConstQCheckBoxHandler(parent, row, state=Qt.Unchecked) + assert data.const_q is False + assert norm.const_q is False + assert config.const_q is False diff --git a/test/unit/RefRed/reduction_table_handling/test_reduction_table_handling.py b/test/unit/RefRed/reduction_table_handling/test_reduction_table_handling.py new file mode 100644 index 00000000..cbd8fc8e --- /dev/null +++ b/test/unit/RefRed/reduction_table_handling/test_reduction_table_handling.py @@ -0,0 +1,86 @@ +from unittest import mock +import pytest +from qtpy import QtWidgets + +from RefRed.interfaces.mytablewidget import ReductionTableColumnIndex +from RefRed.main import MainGui +from RefRed.reduction_table_handling.reduction_table_handler import ReductionTableHandler + + +NBR_ROWS = 6 + + +@pytest.fixture +def setup_main_window_reduction_table(): + window_main = MainGui() + table = window_main.ui.reductionTable + for irow in range(NBR_ROWS): + data_run = str(1000 + irow) + norm_run = str(2000 + irow) + table.item(irow, ReductionTableColumnIndex.DATA_RUN).setText(data_run) + table.item(irow, ReductionTableColumnIndex.NORM_RUN).setText(norm_run) + table.item(irow, ReductionTableColumnIndex.TWO_THETA).setText("3.0") + table.item(irow, ReductionTableColumnIndex.LAMBDA_MIN).setText("2.5") + table.item(irow, ReductionTableColumnIndex.LAMBDA_MAX).setText("6.5") + table.item(irow, ReductionTableColumnIndex.Q_MIN).setText("0.1") + table.item(irow, ReductionTableColumnIndex.Q_MAX).setText("1.7") + table.item(irow, ReductionTableColumnIndex.COMMENTS).setText("comment " + data_run) + yield window_main + + +class TestReductionTableHandling: + def test_clear_reduction_table(self, setup_main_window_reduction_table, qtbot): + window_main = setup_main_window_reduction_table + qtbot.addWidget(window_main) + table = window_main.ui.reductionTable + + for i in range(NBR_ROWS): + assert table.item(i, ReductionTableColumnIndex.DATA_RUN).text() == str(1000 + i) + assert table.item(i, ReductionTableColumnIndex.NORM_RUN).text() == str(2000 + i) + + handler = ReductionTableHandler(parent=window_main) + handler.full_clear() + + for i in range(NBR_ROWS): + assert table.item(i, ReductionTableColumnIndex.DATA_RUN).text() == "" + assert table.item(i, ReductionTableColumnIndex.NORM_RUN).text() == "" + assert table.item(i, ReductionTableColumnIndex.TWO_THETA).text() == "" + assert table.item(i, ReductionTableColumnIndex.LAMBDA_MAX).text() == "" + assert table.item(i, ReductionTableColumnIndex.LAMBDA_MAX).text() == "" + assert table.item(i, ReductionTableColumnIndex.Q_MIN).text() == "" + assert table.item(i, ReductionTableColumnIndex.Q_MAX).text() == "" + assert table.item(i, ReductionTableColumnIndex.COMMENTS).text() == "" + plotted_checkbox = table.cellWidget(i, ReductionTableColumnIndex.PLOTTED) + assert not plotted_checkbox.isChecked() + const_q_checkbox = table.cellWidget(i, ReductionTableColumnIndex.CONST_Q_BINS).findChild( + QtWidgets.QCheckBox + ) + assert not const_q_checkbox.isChecked() + + def test_clear_rows_selected(self, setup_main_window_reduction_table, qtbot): + window_main = setup_main_window_reduction_table + qtbot.addWidget(window_main) + from_row = 2 + to_row = 3 # inclusive + nbr_deleted = to_row + 1 - from_row + + # clear selected rows + with mock.patch.object(ReductionTableHandler, '_ReductionTableHandler__get_range_row_selected'): + handler = ReductionTableHandler(parent=window_main) + handler.from_row = from_row + handler.to_row = to_row + handler.clear_rows_selected() + + table = window_main.ui.reductionTable + # check that the rows up to from_row are unchanged + for i in range(from_row): + assert table.item(i, ReductionTableColumnIndex.DATA_RUN).text() == str(1000 + i) + assert table.item(i, ReductionTableColumnIndex.NORM_RUN).text() == str(2000 + i) + # check that the rows after to_row have been shifted up + for i in range(from_row, NBR_ROWS - nbr_deleted): + assert table.item(i, ReductionTableColumnIndex.DATA_RUN).text() == str(1000 + i + nbr_deleted) + assert table.item(i, ReductionTableColumnIndex.NORM_RUN).text() == str(2000 + i + nbr_deleted) + # check that the rows below are cleared + for i in range(NBR_ROWS - nbr_deleted, NBR_ROWS): + assert table.item(i, ReductionTableColumnIndex.DATA_RUN).text() == "" + assert table.item(i, ReductionTableColumnIndex.NORM_RUN).text() == "" diff --git a/test/unit/RefRed/reduction_table_handling/test_update_reduction_table.py b/test/unit/RefRed/reduction_table_handling/test_update_reduction_table.py index f233aec7..2358f50c 100644 --- a/test/unit/RefRed/reduction_table_handling/test_update_reduction_table.py +++ b/test/unit/RefRed/reduction_table_handling/test_update_reduction_table.py @@ -1,4 +1,5 @@ from RefRed.main import MainGui +from test.utilities import load_run_from_reduction_table # third party packages import pytest @@ -7,13 +8,6 @@ wait = 1000 -def load_run_from_reduction_table(window_main, row: int, col: int, run: str): - """Add run number in reduction table cell and press Enter to load a run""" - window_main.ui.reductionTable.setCurrentCell(row, col) - window_main.ui.reductionTable.currentItem().setText(run) - window_main.ui.table_reduction_cell_enter_pressed() - - @pytest.mark.parametrize("is_display_checked", [True, False]) @mock.patch("RefRed.main.DisplayPlots") @mock.patch("RefRed.calculations.locate_list_run.FileFinder.findRuns") diff --git a/test/utilities.py b/test/utilities.py new file mode 100644 index 00000000..63e0da92 --- /dev/null +++ b/test/utilities.py @@ -0,0 +1,5 @@ +def load_run_from_reduction_table(window_main, row: int, col: int, run: str): + """Add run number in reduction table cell and press Enter to load a run""" + window_main.ui.reductionTable.setCurrentCell(row, col) + window_main.ui.reductionTable.currentItem().setText(run) + window_main.ui.table_reduction_cell_enter_pressed()