diff --git a/docs/source/interfaces/Engineering Diffraction 2.rst b/docs/source/interfaces/Engineering Diffraction 2.rst index c2c7bf1d40ea2ee7fe43f9d43d47c121220a92c9..d5bd85d3d559efba8e7a7f02df4e0417445e79a9 100644 --- a/docs/source/interfaces/Engineering Diffraction 2.rst +++ b/docs/source/interfaces/Engineering Diffraction 2.rst @@ -11,7 +11,9 @@ Interface Overview This custom interface will integrate several tasks related to engineering diffraction. In its current state it provides functionality for creating -new calibration files. This interface is under active development. +and loading calibration files, and focusing ENGINX run files. + +This interface is under active development. General Options ^^^^^^^^^^^^^^^ @@ -27,6 +29,11 @@ Instrument ? Show this documentation page. +Settings + Provides a range of options that apply across the entire interface, currently + providing the option to change the default output directory and force the + recalculation of the vanadium correction files. + Close Close the interface. @@ -37,9 +44,10 @@ Red Stars Calibration ----------- -This tab currently provides a graphical interface to create new calibrations -and visualise them. It also allows for the loading of GSAS parameter files created -by the calibration process to load a previously created calibration into the interface. +This tab currently provides a graphical interface to create new calibrations, using the +:ref:`EnggCalibrate<algm-EnggCalibrate>` algorithm, and visualise them. +It also allows for the loading of GSAS parameter files created by the calibration process +to load a previously created calibration into the interface. When loading an existing calibration, the fields for creating a new calibration will be automatically filled, allowing the recreation of the workspaces and plots generated by @@ -52,14 +60,18 @@ Creating a new calibration file generates 3 GSAS instrument parameter files, one covering all banks and separate ones for each individual bank. All 3 files are written to the same directory: -`Engineering_Mantid/Calibration/` +`<CHOSEN_OUTPUT_DIRECTORY>/Calibration/` If an RB number has been specified the files will also be saved to a user directory in the base directory: -`Engineering_Mantid/User/RBNumber/Calibration/` +`<CHOSEN_OUTPUT_DIRECTORY>/User/RBNumber/Calibration/` + +Cropping +^^^^^^^^ -Currently, the `Engineering_Mantid` directory is created in the current user's home directory. +The interface also provides the ability to restrict a new calibration to one of the two banks +or to a custom list of spectra. Parameters ^^^^^^^^^^ @@ -74,6 +86,13 @@ Calibration Sample Number Path The path to the GSAS parameter file to be loaded. +Bank/Spectra + Select a bank to crop to or specify a custom spectra will be entered. + +Custom Spectra + A comma separated list of spectra to restrict the calibration to. Can be provided as single spectrum numbers + or ranges using hyphens (e.g. 14-150, 405, 500-600). + Focus ----- diff --git a/scripts/Engineering/EnggUtils.py b/scripts/Engineering/EnggUtils.py index 803fc2261dbf676bc9f6fbbf6adb2af52e7448a9..441f7f14a3647ce6962de1b2571915530bd2a57e 100644 --- a/scripts/Engineering/EnggUtils.py +++ b/scripts/Engineering/EnggUtils.py @@ -328,10 +328,9 @@ def crop_data(parent, ws, indices): @returns cropped workspace, with only the spectra corresponding to the indices requested """ # Leave only spectra between min and max - alg = parent.createChildAlgorithm('CropWorkspace') + alg = parent.createChildAlgorithm('ExtractSpectra') alg.setProperty('InputWorkspace', ws) - alg.setProperty('StartWorkspaceIndex', min(indices)) - alg.setProperty('EndWorkspaceIndex', max(indices)) + alg.setProperty('WorkspaceIndexList', indices) alg.execute() return alg.getProperty('OutputWorkspace').value diff --git a/scripts/Engineering/gui/CMakeLists.txt b/scripts/Engineering/gui/CMakeLists.txt index 62c1f5271a99bb83e2417cbef8c7104fb40003d1..bb3f87d2b5ec376aaf81cf9abd3be8966a1942f4 100644 --- a/scripts/Engineering/gui/CMakeLists.txt +++ b/scripts/Engineering/gui/CMakeLists.txt @@ -3,6 +3,9 @@ set(TEST_PY_FILES # Common engineering_diffraction/tabs/common/test/test_vanadium_corrections.py + # Cropping + engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py + engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py # Settings engineering_diffraction/settings/test/test_settings_helper.py engineering_diffraction/settings/test/test_settings_model.py diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui index 6fc3ef07bb6b5f98925cbd3d12d8e0837c69fbba..2f8e4fee2c7fd19560f0d6d89df2db6cd1c0a33c 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>618</width> - <height>192</height> + <width>661</width> + <height>236</height> </rect> </property> <property name="sizePolicy"> @@ -73,52 +73,44 @@ QGroupBox:title { </property> <layout class="QGridLayout" name="gridLayout_2"> <item row="1" column="0"> - <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0" columnstretch="0,0,0"> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,0,0" columnstretch="0,0,0"> <property name="sizeConstraint"> <enum>QLayout::SetDefaultConstraint</enum> </property> <property name="verticalSpacing"> <number>6</number> </property> - <item row="4" column="0" colspan="3"> - <widget class="QRadioButton" name="radio_loadCalib"> - <property name="text"> - <string>Load Existing Calibration</string> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="7" column="0" colspan="2"> - <widget class="QCheckBox" name="check_plotOutput"> - <property name="enabled"> - <bool>true</bool> + <item row="6" column="0" colspan="3"> + <widget class="Line" name="line_crop"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> </property> - <property name="text"> - <string>Plot Calibrated Workspace</string> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="0" column="0" colspan="3"> - <widget class="QRadioButton" name="radio_newCalib"> - <property name="text"> - <string>Create New Calibration</string> - </property> - <property name="checked"> - <bool>true</bool> + <item row="5" column="0" colspan="3"> + <widget class="FileFinder" name="finder_path" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - </widget> - </item> - <item row="6" column="0" colspan="3"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> </property> </widget> </item> <item row="3" column="0" colspan="3"> - <spacer name="verticalSpacer"> + <spacer name="spacer_new_exist"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> @@ -133,16 +125,6 @@ QGroupBox:title { </property> </spacer> </item> - <item row="7" column="2"> - <widget class="QPushButton" name="button_calibrate"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="text"> - <string>Calibrate</string> - </property> - </widget> - </item> <item row="1" column="0" colspan="3"> <widget class="FileFinder" name="finder_vanadium" native="true"> <property name="minimumSize"> @@ -153,6 +135,16 @@ QGroupBox:title { </property> </widget> </item> + <item row="0" column="0" colspan="3"> + <widget class="QRadioButton" name="radio_newCalib"> + <property name="text"> + <string>Create New Calibration</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> <item row="2" column="0" colspan="3"> <widget class="FileFinder" name="finder_sample" native="true"> <property name="sizePolicy"> @@ -169,14 +161,52 @@ QGroupBox:title { </property> </widget> </item> - <item row="5" column="0" colspan="3"> - <widget class="FileFinder" name="finder_path" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="10" column="0" colspan="2"> + <widget class="QCheckBox" name="check_plotOutput"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Plot Calibrated Workspace</string> + </property> + </widget> + </item> + <item row="7" column="0" colspan="3"> + <widget class="QCheckBox" name="check_cropCalib"> + <property name="text"> + <string>Crop Calibration</string> + </property> + </widget> + </item> + <item row="10" column="2"> + <widget class="QPushButton" name="button_calibrate"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Calibrate</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="3"> + <widget class="QRadioButton" name="radio_loadCalib"> + <property name="text"> + <string>Load Existing Calibration</string> </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="9" column="0" colspan="3"> + <widget class="Line" name="line_go"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="8" column="0" colspan="3"> + <widget class="CroppingView" name="widget_cropping" native="true"> <property name="minimumSize"> <size> <width>0</width> @@ -191,7 +221,7 @@ QGroupBox:title { </widget> </item> <item> - <spacer name="verticalSpacer_2"> + <spacer name="spacer_bottom"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> @@ -214,6 +244,12 @@ QGroupBox:title { <header>mantidqt.widgets.filefinder</header> <container>1</container> </customwidget> + <customwidget> + <class>CroppingView</class> + <extends>QWidget</extends> + <header>Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_view</header> + <container>1</container> + </customwidget> </customwidgets> <tabstops> <tabstop>radio_newCalib</tabstop> diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py index 24357c27e2aae6f9594e6d75e3eb6cf146cdae74..fd3ee96f23bc94fcc8b596fb9a2f81e6013ee224 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py @@ -15,6 +15,7 @@ from mantid.api import AnalysisDataService as Ads from mantid.kernel import logger from mantid.simpleapi import EnggCalibrate, DeleteWorkspace, CloneWorkspace, \ CreateWorkspace, AppendSpectra, CreateEmptyTableWorkspace +from mantidqt.plotting.functions import plot from Engineering.EnggUtils import write_ENGINX_GSAS_iparam_file from Engineering.gui.engineering_diffraction.tabs.common import vanadium_corrections from Engineering.gui.engineering_diffraction.tabs.common import path_handling @@ -35,7 +36,9 @@ class CalibrationModel(object): sample_path, plot_output, instrument, - rb_num=None): + rb_num=None, + bank=None, + spectrum_numbers=None): """ Create a new calibration from a vanadium run and sample run :param vanadium_path: Path to vanadium data file. @@ -43,6 +46,8 @@ class CalibrationModel(object): :param plot_output: Whether the output should be plotted. :param instrument: The instrument the data relates to. :param rb_num: The RB number for file creation. + :param bank: Optional parameter to crop by bank + :param spectrum_numbers: Optional parameter to crop using spectrum numbers. """ van_integration, van_curves = vanadium_corrections.fetch_correction_workspaces( vanadium_path, instrument, rb_num=rb_num) @@ -51,31 +56,50 @@ class CalibrationModel(object): path_handling.ENGINEERING_PREFIX, "full_calibration") if full_calib_path is not None and path.exists(full_calib_path): full_calib = path_handling.load_workspace(full_calib_path) - output = self.run_calibration(sample_workspace, van_integration, van_curves, full_calib_ws=full_calib) + output = self.run_calibration(sample_workspace, + van_integration, + van_curves, + bank, + spectrum_numbers, + full_calib_ws=full_calib) else: - output = self.run_calibration(sample_workspace, van_integration, van_curves) + output = self.run_calibration(sample_workspace, van_integration, van_curves, bank, + spectrum_numbers) if plot_output: self._plot_vanadium_curves() - for i in range(2): - difc = [output[i].DIFC] - tzero = [output[i].TZERO] - self._generate_difc_tzero_workspace(difc, tzero, i + 1) - self._plot_difc_tzero() - difc = [output[0].DIFC, output[1].DIFC] - tzero = [output[0].TZERO, output[1].TZERO] + for i in range(len(output)): + if spectrum_numbers: + bank_name = "cropped" + elif bank is None: + bank_name = str(i + 1) + else: + bank_name = bank + difc = output[i].DIFC + tzero = output[i].TZERO + self._generate_difc_tzero_workspace(difc, tzero, bank_name) + if bank is None and spectrum_numbers is None: + self._plot_difc_tzero() + elif spectrum_numbers is None: + self._plot_difc_tzero_single_bank_or_custom(bank) + else: + self._plot_difc_tzero_single_bank_or_custom("cropped") + difc = [i.DIFC for i in output] + tzero = [i.TZERO for i in output] params_table = [] - for i in range(2): + + for i in range(len(difc)): params_table.append([i, difc[i], 0.0, tzero[i]]) self.update_calibration_params_table(params_table) calib_dir = path.join(path_handling.get_output_path(), "Calibration", "") - self.create_output_files(calib_dir, difc, tzero, sample_path, vanadium_path, instrument) + self.create_output_files(calib_dir, difc, tzero, sample_path, vanadium_path, instrument, + bank, spectrum_numbers) if rb_num: user_calib_dir = path.join(path_handling.get_output_path(), "User", rb_num, "Calibration", "") self.create_output_files(user_calib_dir, difc, tzero, sample_path, vanadium_path, - instrument) + instrument, bank, spectrum_numbers) def load_existing_gsas_parameters(self, file_path): if not path.exists(file_path): @@ -139,14 +163,14 @@ class CalibrationModel(object): @staticmethod def _generate_difc_tzero_workspace(difc, tzero, bank): - bank_ws = Ads.retrieve(CalibrationModel._generate_table_workspace_name(bank - 1)) + bank_ws = Ads.retrieve(CalibrationModel._generate_table_workspace_name(bank)) x_val = [] y_val = [] y2_val = [] - difc_to_plot = difc[0] - tzero_to_plot = tzero[0] + difc_to_plot = difc + tzero_to_plot = tzero for irow in range(0, bank_ws.rowCount()): x_val.append(bank_ws.cell(irow, 0)) @@ -185,35 +209,70 @@ class CalibrationModel(object): ax.set_xlabel("Expected Peaks Centre(dSpacing, A)") fig.show() - def run_calibration(self, sample_ws, van_integration, van_curves, full_calib_ws=None): + @staticmethod + def _plot_difc_tzero_single_bank_or_custom(bank): + bank_ws = Ads.retrieve("engggui_difc_zero_peaks_bank_" + str(bank)) + + ax = plot([bank_ws], [0, 1], + plot_kwargs={ + "linestyle": "--", + "marker": "o", + "markersize": "3" + }).gca() + ax.set_title("Engg Gui Difc Zero Peaks Bank " + str(bank)) + ax.legend(("Peaks Fitted", "DifC/TZero Fitted Straight Line")) + ax.set_xlabel("Expected Peaks Centre(dSpacing, A)") + + def run_calibration(self, + sample_ws, + van_integration, + van_curves, + bank, + spectrum_numbers, + full_calib_ws=None): """ Runs the main Engineering calibration algorithm. :param sample_ws: The workspace with the sample data. :param van_integration: The integration values from the vanadium corrections :param van_curves: The curves from the vanadium corrections. :param full_calib_ws: Full pixel calibration of the detector (optional) + :param bank: The bank to crop to, both if none. + :param spectrum_numbers: The spectrum numbers to crop to, no crop if none. :return: The output of the algorithm. """ - output = [None] * 2 - for i in range(2): - table_name = self._generate_table_workspace_name(i) - if full_calib_ws is not None: - output[i] = EnggCalibrate(InputWorkspace=sample_ws, - VanIntegrationWorkspace=van_integration, - VanCurvesWorkspace=van_curves, - Bank=str(i + 1), - FittedPeaks=table_name) + kwargs = { + "InputWorkspace": sample_ws, + "VanIntegrationWorkspace": van_integration, + "VanCurvesWorkspace": van_curves + } + + def run_engg_calibrate(kwargs_to_pass): + return EnggCalibrate(**kwargs_to_pass) + + if full_calib_ws is not None: + kwargs["DetectorPositions"] = full_calib_ws + if spectrum_numbers is None: + if bank is None: + output = [None] * 2 + for i in range(len(output)): + kwargs["Bank"] = str(i+1) + kwargs["FittedPeaks"] = self._generate_table_workspace_name(str(i+1)) + output[i] = run_engg_calibrate(kwargs) else: - output[i] = EnggCalibrate(InputWorkspace=sample_ws, - VanIntegrationWorkspace=van_integration, - VanCurvesWorkspace=van_curves, - Bank=str(i + 1), - FittedPeaks=table_name, - DetectorPositions=full_calib_ws) + output = [None] + kwargs["Bank"] = bank + kwargs["FittedPeaks"] = self._generate_table_workspace_name(bank) + output[0] = run_engg_calibrate(kwargs) + + else: + output = [None] + kwargs["SpectrumNumbers"] = spectrum_numbers + kwargs["FittedPeaks"] = self._generate_table_workspace_name("cropped") + output[0] = run_engg_calibrate(kwargs) return output def create_output_files(self, calibration_dir, difc, tzero, sample_path, vanadium_path, - instrument): + instrument, bank, spectrum_numbers): """ Create output files from the algorithms in the specified directory :param calibration_dir: The directory to save the files into. @@ -222,36 +281,42 @@ class CalibrationModel(object): :param sample_path: The path to the sample data file. :param vanadium_path: The path to the vanadium data file. :param instrument: The instrument (ENGINX or IMAT) + :param bank: Optional parameter to crop by bank + :param spectrum_numbers: Optional parameter to crop using spectrum numbers. """ + kwargs = {"ceria_run": sample_path, "vanadium_run": vanadium_path} + + def south_kwargs(): + kwargs["template_file"] = SOUTH_BANK_TEMPLATE_FILE + kwargs["bank_names"] = ["South"] + + def north_kwargs(): + kwargs["template_file"] = NORTH_BANK_TEMPLATE_FILE + kwargs["bank_names"] = ["North"] + + def generate_output_file(difc_list, tzero_list, bank_name, kwargs_to_pass): + file_path = calibration_dir + self._generate_output_file_name(vanadium_path, sample_path, instrument, + bank=bank_name) + write_ENGINX_GSAS_iparam_file(file_path, difc_list, tzero_list, **kwargs_to_pass) + if not path.exists(calibration_dir): makedirs(calibration_dir) - filename = self._generate_output_file_name(vanadium_path, - sample_path, - instrument, - bank="all") - # Both Banks - file_path = calibration_dir + filename - write_ENGINX_GSAS_iparam_file(file_path, - difc, - tzero, - ceria_run=sample_path, - vanadium_run=vanadium_path) - # North Bank - file_path = calibration_dir + self._generate_output_file_name( - vanadium_path, sample_path, instrument, bank="north") - write_ENGINX_GSAS_iparam_file(file_path, [difc[0]], [tzero[0]], - ceria_run=sample_path, - vanadium_run=vanadium_path, - template_file=NORTH_BANK_TEMPLATE_FILE, - bank_names=["North"]) - # South Bank - file_path = calibration_dir + self._generate_output_file_name( - vanadium_path, sample_path, instrument, bank="south") - write_ENGINX_GSAS_iparam_file(file_path, [difc[1]], [tzero[1]], - ceria_run=sample_path, - vanadium_run=vanadium_path, - template_file=SOUTH_BANK_TEMPLATE_FILE, - bank_names=["South"]) + + if bank is None and spectrum_numbers is None: + generate_output_file(difc, tzero, "all", kwargs) + north_kwargs() + generate_output_file([difc[0]], [tzero[0]], "north", kwargs) + south_kwargs() + generate_output_file([difc[1]], [tzero[1]], "south", kwargs) + elif bank == "1": + north_kwargs() + generate_output_file([difc[0]], [tzero[0]], "north", kwargs) + elif bank == "2": + south_kwargs() + generate_output_file([difc[0]], [tzero[0]], "south", kwargs) + elif bank is None: # Custom cropped files use the north bank template. + north_kwargs() + generate_output_file([difc[0]], [tzero[0]], "cropped", kwargs) @staticmethod def get_info_from_file(file_path): @@ -284,7 +349,7 @@ class CalibrationModel(object): @staticmethod def _generate_table_workspace_name(bank_num): - return "engggui_calibration_bank_" + str(bank_num + 1) + return "engggui_calibration_bank_" + str(bank_num) @staticmethod def _generate_output_file_name(vanadium_path, sample_path, instrument, bank): @@ -305,6 +370,8 @@ class CalibrationModel(object): filename = filename + "bank_North.prm" elif bank == "south": filename = filename + "bank_South.prm" + elif bank == "cropped": + filename = filename + "cropped.prm" else: raise ValueError("Invalid bank name entered") return filename diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py index d7b7cb19acf49c3991730b684a7dec859cfce762..7e81c200e4bd9d60716ecd7abfa8cbe8700d26ca 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py @@ -11,6 +11,8 @@ from copy import deepcopy from Engineering.gui.engineering_diffraction.tabs.common import INSTRUMENT_DICT, create_error_message from Engineering.gui.engineering_diffraction.tabs.common.calibration_info import CalibrationInfo +from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_widget import CroppingWidget + from mantidqt.utils.asynchronous import AsyncTask from mantid.simpleapi import logger from mantidqt.utils.observer_pattern import Observable @@ -26,23 +28,33 @@ class CalibrationPresenter(object): self.current_calibration = CalibrationInfo() self.pending_calibration = CalibrationInfo() - # Connect view signals to local functions. + self.connect_view_signals() + + # Main Window State Variables + self.instrument = "ENGINX" + self.rb_num = None + + # Cropping Options + self.cropping_widget = CroppingWidget(self.view, view=self.view.get_cropping_widget()) + self.view.set_cropping_widget_hidden() + + def connect_view_signals(self): self.view.set_on_calibrate_clicked(self.on_calibrate_clicked) self.view.set_enable_controls_connection(self.set_calibrate_controls_enabled) self.view.set_update_fields_connection(self.set_field_values) self.view.set_on_radio_new_toggled(self.set_create_new_enabled) self.view.set_on_radio_existing_toggled(self.set_load_existing_enabled) - - # Main Window State Variables - self.instrument = "ENGINX" - self.rb_num = None + self.view.set_on_check_cropping_state_changed(self.show_cropping) def on_calibrate_clicked(self): plot_output = self.view.get_plot_output() if self.view.get_new_checked() and self._validate(): vanadium_file = self.view.get_vanadium_filename() sample_file = self.view.get_sample_filename() - self.start_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num) + if self.view.get_crop_checked(): + self.start_cropped_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num) + else: + self.start_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num) elif self.view.get_load_checked(): if not self.validate_path(): return @@ -52,25 +64,39 @@ class CalibrationPresenter(object): self.pending_calibration.set_calibration(vanadium_file, sample_file, instrument) self.set_current_calibration() - def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num): + def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num, bank=None, + spectrum_numbers=None): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param vanadium_path: Path to vanadium data file. :param sample_path: Path to sample data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. + :param bank: Optional parameter to crop by bank. + :param spectrum_numbers: Optional parameter to crop by spectrum number. """ - self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path), { - "plot_output": plot_output, - "instrument": self.instrument, - "rb_num": rb_num - }, + self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path), + { + "plot_output": plot_output, + "instrument": self.instrument, + "rb_num": rb_num, + "bank": bank, + "spectrum_numbers": spectrum_numbers + }, error_cb=self._on_error, success_cb=self._on_success) self.pending_calibration.set_calibration(vanadium_path, sample_path, self.instrument) self.set_calibrate_controls_enabled(False) self.worker.start() + def start_cropped_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num): + if self.cropping_widget.is_custom(): + spec_nums = self.cropping_widget.get_custom_spectra() + self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, spectrum_numbers=spec_nums) + else: + bank = self.cropping_widget.get_bank() + self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, bank=bank) + def set_current_calibration(self, success_info=None): if success_info: logger.information("Thread executed in " + str(success_info.elapsed_time) + " seconds.") @@ -99,6 +125,9 @@ class CalibrationPresenter(object): if not self.validate_run_numbers(): create_error_message(self.view, "Check run numbers/path is valid.") return False + if not self.cropping_widget.is_valid(): + create_error_message(self.view, "Check cropping values are valid.") + return False return True def validate_run_numbers(self): @@ -131,6 +160,7 @@ class CalibrationPresenter(object): if enabled: self.set_calibrate_button_text("Calibrate") self.view.set_check_plot_output_enabled(True) + self.view.set_check_cropping_enabled(True) self.find_files() def set_load_existing_enabled(self, enabled): @@ -138,6 +168,8 @@ class CalibrationPresenter(object): if enabled: self.set_calibrate_button_text("Load") self.view.set_check_plot_output_enabled(False) + self.view.set_check_cropping_enabled(False) + self.view.set_check_cropping_state(0) def set_calibrate_button_text(self, text): self.view.set_calibrate_button_text(text) @@ -146,6 +178,12 @@ class CalibrationPresenter(object): self.view.find_sample_files() self.view.find_vanadium_files() + def show_cropping(self, show): + if show: + self.view.set_cropping_widget_visible() + else: + self.view.set_cropping_widget_hidden() + # ----------------------- # Observers / Observables # ----------------------- diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py index e5ea4fde9e1c83df09746247076e47c28e0698f1..87fc73d965c4855f9b774b8054fe612be9894c54 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py @@ -10,6 +10,7 @@ from __future__ import (absolute_import, division, print_function) import unittest from mantid.py3compat.mock import patch +from mantid.py3compat.mock import MagicMock from Engineering.gui.engineering_diffraction.tabs.calibration.model import CalibrationModel VANADIUM_NUMBER = "307521" @@ -60,14 +61,18 @@ class CalibrationModelTest(unittest.TestCase): @patch(file_path + ".path_handling.load_workspace") @patch(class_path + '.run_calibration') @patch(file_path + '.vanadium_corrections.fetch_correction_workspaces') - def test_having_full_calib_set_uses_file(self, van_corr, calibrate_alg, load_workspace, output_files, - update_table, setting, path): + def test_having_full_calib_set_uses_file(self, van_corr, calibrate_alg, load_workspace, + output_files, update_table, setting, path): path.return_value = True setting.return_value = "mocked/out/path" van_corr.return_value = ("mocked_integration", "mocked_curves") load_workspace.return_value = "mocked_workspace" self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX") - calibrate_alg.assert_called_with("mocked_workspace", "mocked_integration", "mocked_curves", + calibrate_alg.assert_called_with("mocked_workspace", + "mocked_integration", + "mocked_curves", + None, + None, full_calib_ws="mocked_workspace") @patch(class_path + '.update_calibration_params_table') @@ -80,6 +85,7 @@ class CalibrationModelTest(unittest.TestCase): @patch(class_path + '.run_calibration') def test_plotting_check(self, calib, plot_difc_zero, gen_difc, plot_van, van, sample, output_files, update_table): + calib.return_value = [MagicMock(), MagicMock()] van.return_value = ("A", "B") self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX") plot_van.assert_not_called() @@ -90,6 +96,30 @@ class CalibrationModelTest(unittest.TestCase): self.assertEqual(gen_difc.call_count, 2) self.assertEqual(plot_difc_zero.call_count, 1) + @patch(class_path + '.update_calibration_params_table') + @patch(class_path + '.create_output_files') + @patch(file_path + ".path_handling.load_workspace") + @patch(file_path + '.vanadium_corrections.fetch_correction_workspaces') + @patch(class_path + '._plot_vanadium_curves') + @patch(class_path + '._generate_difc_tzero_workspace') + @patch(class_path + '._plot_difc_tzero') + @patch(class_path + '._plot_difc_tzero_single_bank_or_custom') + @patch(class_path + '.run_calibration') + def test_plotting_check_cropped(self, calib, plot_difc_zero_cus, plot_difc_zero, gen_difc, + plot_van, van, sample, output_files, update_table): + calib.return_value = [MagicMock()] + van.return_value = ("A", "B") + self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX") + plot_van.assert_not_called() + plot_difc_zero_cus.assert_not_called() + plot_difc_zero.assert_not_called() + gen_difc.assert_not_called() + self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, True, "ENGINX", bank=1) + plot_van.assert_called_once() + self.assertEqual(gen_difc.call_count, 1) + plot_difc_zero.assert_not_called() + self.assertEqual(plot_difc_zero_cus.call_count, 1) + @patch(class_path + '.update_calibration_params_table') @patch(class_path + '.create_output_files') @patch(file_path + ".path_handling.load_workspace") @@ -144,8 +174,12 @@ class CalibrationModelTest(unittest.TestCase): filename = "output" output_name.return_value = filename - self.model.create_output_files("test/", [0, 0], [1, 1], sample_path, vanadium_path, - "ENGINX") + self.model.create_output_files("test/", [0, 0], [1, 1], + sample_path, + vanadium_path, + "ENGINX", + bank=None, + spectrum_numbers=None) self.assertEqual(make_dirs.call_count, 1) self.assertEqual(write_file.call_count, 3) @@ -157,7 +191,7 @@ class CalibrationModelTest(unittest.TestCase): def test_generate_table_workspace_name(self): self.assertEqual(self.model._generate_table_workspace_name(20), - "engggui_calibration_bank_21") + "engggui_calibration_bank_20") def test_generate_output_file_name_for_north_bank(self): filename = self.model._generate_output_file_name("test/20.raw", "test/10.raw", "ENGINX", @@ -174,6 +208,11 @@ class CalibrationModelTest(unittest.TestCase): "all") self.assertEqual(filename, "ENGINX_20_10_all_banks.prm") + def test_generate_output_file_name_for_cropped_bank(self): + filename = self.model._generate_output_file_name("test/20.raw", "test/10.raw", "ENGINX", + "cropped") + self.assertEqual(filename, "ENGINX_20_10_cropped.prm") + def test_generate_output_file_name_for_invalid_bank(self): self.assertRaises(ValueError, self.model._generate_output_file_name, "test/20.raw", "test/10.raw", "ENGINX", "INVALID") @@ -203,6 +242,91 @@ class CalibrationModelTest(unittest.TestCase): self.assertEqual(ads.retrieve.call_count, 0) + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_no_bank_no_spec_nums_no_full_calib(self, alg): + self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, None) + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="1", + FittedPeaks="engggui_calibration_bank_1") + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="2", + FittedPeaks="engggui_calibration_bank_2") + self.assertEqual(2, alg.call_count) + + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_no_bank_no_spec_nums_full_calib(self, alg): + self.model.run_calibration("sample", + "vanadium_int", + "vanadium_curves", + None, + None, + full_calib_ws="full") + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="1", + FittedPeaks="engggui_calibration_bank_1", + DetectorPositions="full") + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="2", + FittedPeaks="engggui_calibration_bank_2", + DetectorPositions="full") + self.assertEqual(2, alg.call_count) + + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_bank_no_spec_nums_no_full_calib(self, alg): + self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", "1", None) + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="1", + FittedPeaks="engggui_calibration_bank_1") + self.assertEqual(1, alg.call_count) + + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_no_bank_spec_nums_no_full_calib(self, alg): + self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, "1-5, 45-102") + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + SpectrumNumbers="1-5, 45-102", + FittedPeaks="engggui_calibration_bank_cropped") + self.assertEqual(1, alg.call_count) + + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_bank_no_spec_nums_full_calib(self, alg): + self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", "1", None, full_calib_ws="full") + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + Bank="1", + FittedPeaks="engggui_calibration_bank_1", + DetectorPositions="full") + self.assertEqual(1, alg.call_count) + + @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate") + def test_run_calibration_no_bank_spec_nums_full_calib(self, alg): + self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, "45-102", full_calib_ws="full") + + alg.assert_any_call(InputWorkspace="sample", + VanIntegrationWorkspace="vanadium_int", + VanCurvesWorkspace="vanadium_curves", + SpectrumNumbers="45-102", + FittedPeaks="engggui_calibration_bank_cropped", + DetectorPositions="full") + self.assertEqual(1, alg.call_count) + if __name__ == '__main__': unittest.main() diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py index 3c9d9d67968e0b6230d61575d45a2243c6e5d775..0590fbb07bc0a7c9a39640397ee4ad699ce61cfb 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py @@ -21,10 +21,13 @@ class CalibrationPresenterTest(unittest.TestCase): def setUp(self): self.view = mock.create_autospec(view.CalibrationView) self.model = mock.create_autospec(model.CalibrationModel) + self.view.get_cropping_widget.return_value = MagicMock() self.presenter = presenter.CalibrationPresenter(self.model, self.view) + self.presenter.cropping_widget = MagicMock() @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker") def test_worker_started_with_right_params(self, worker_method): + self.view.get_crop_checked.return_value = False self.view.get_vanadium_filename.return_value = "307521" self.view.get_sample_filename.return_value = "305738" self.view.get_plot_output.return_value = True @@ -33,6 +36,32 @@ class CalibrationPresenterTest(unittest.TestCase): self.presenter.on_calibrate_clicked() worker_method.assert_called_with("307521", "305738", True, None) + @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker") + def test_worker_started_with_right_params_crop_bank(self, worker_method): + self.view.get_crop_checked.return_value = True + self.view.get_vanadium_filename.return_value = "307521" + self.view.get_sample_filename.return_value = "305738" + self.view.get_plot_output.return_value = True + self.view.is_searching.return_value = False + self.presenter.cropping_widget.is_custom.return_value = False + self.presenter.cropping_widget.get_bank.return_value = "bank" + + self.presenter.on_calibrate_clicked() + worker_method.assert_called_with("307521", "305738", True, None, bank="bank") + + @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker") + def test_worker_started_with_right_params_crop_spec_nums(self, worker_method): + self.view.get_crop_checked.return_value = True + self.view.get_vanadium_filename.return_value = "307521" + self.view.get_sample_filename.return_value = "305738" + self.view.get_plot_output.return_value = True + self.view.is_searching.return_value = False + self.presenter.cropping_widget.is_custom.return_value = True + self.presenter.cropping_widget.get_custom_spectra.return_value = "1-56,401-809" + + self.presenter.on_calibrate_clicked() + worker_method.assert_called_with("307521", "305738", True, None, spectrum_numbers="1-56,401-809") + @patch(tab_path + ".presenter.create_error_message") @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker") def test_worker_not_started_while_finder_is_searching(self, worker_method, err_msg): @@ -61,6 +90,22 @@ class CalibrationPresenterTest(unittest.TestCase): worker_method.assert_not_called() self.assertEqual(err_msg.call_count, 1) + @patch(tab_path + ".presenter.create_error_message") + @patch(tab_path + ".presenter.CalibrationPresenter.validate_run_numbers") + @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker") + def test_worker_not_started_when_cropping_invalid(self, worker_method, validator, err_msg): + self.view.get_vanadium_filename.return_value = "307521" + self.view.get_sample_filename.return_value = "305738" + self.view.get_plot_output.return_value = True + self.view.is_searching.return_value = False + self.view.get_load_checked.return_value = False + validator.return_value = True + self.presenter.cropping_widget.is_valid.return_value = False + + self.presenter.on_calibrate_clicked() + worker_method.assert_not_called() + self.assertEqual(err_msg.call_count, 1) + def test_controls_disabled_disables_both(self): self.presenter.set_calibrate_controls_enabled(False) @@ -233,6 +278,13 @@ class CalibrationPresenterTest(unittest.TestCase): self.check_calibration_equal(self.presenter.pending_calibration, expected_pending) + def test_cropping_disabled_when_loading_calib(self): + self.presenter.set_load_existing_enabled(True) + + self.assertEqual(self.view.set_cropping_widget_hidden.call_count, 1) + self.view.set_check_cropping_enabled.assert_called_with(False) + self.view.set_check_cropping_state.assert_called_with(0) + def check_calibration_equal(self, a, b): self.assertEqual(a.get_vanadium(), b.get_vanadium()) self.assertEqual(a.get_sample(), b.get_sample()) diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py index 25a9ece34c6f24bc39b33dc6673b43c3b8565cc0..2d564dddfd51b2a88357e925ed9a68c4c6f96509 100644 --- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py @@ -58,6 +58,9 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib): def set_update_fields_connection(self, slot): self.sig_update_fields.connect(slot) + def set_on_check_cropping_state_changed(self, slot): + self.check_cropCalib.stateChanged.connect(slot) + # ================= # Component Setters # ================= @@ -90,6 +93,18 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib): def set_calibrate_button_text(self, text): self.button_calibrate.setText(text) + def set_cropping_widget_visible(self): + self.widget_cropping.show() + + def set_cropping_widget_hidden(self): + self.widget_cropping.hide() + + def set_check_cropping_enabled(self, enabled): + self.check_cropCalib.setEnabled(enabled) + + def set_check_cropping_state(self, state): + self.check_cropCalib.setCheckState(state) + # ================= # Component Getters # ================= @@ -121,6 +136,12 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib): def get_load_checked(self): return self.radio_loadCalib.isChecked() + def get_crop_checked(self): + return self.check_cropCalib.isChecked() + + def get_cropping_widget(self): + return self.widget_cropping + # ================= # State Getters # ================= diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/__init__.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py new file mode 100644 index 0000000000000000000000000000000000000000..1ffcbd9a030f09caa3ecc80a1689d3ba66ecaa1d --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py @@ -0,0 +1,69 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + +import re + +ENGINX_MAX_SPECTRA = 2513 +VALID_PUNCT = [",", " ", "-"] +SPLITTING_REGEX = ",|-" + + +class CroppingModel(object): + def validate_and_clean_spectrum_numbers(self, numbers): + numbers = numbers.strip() + try: + if self.validate_spectrum_numbers(numbers): + numbers = self._clean_spectrum_numbers(numbers) + return "", numbers + else: + return "Invalid spectrum numbers entered. Limits are 0-" + str(ENGINX_MAX_SPECTRA), "" + except ValueError as e: + return str(e), "" + + def validate_spectrum_numbers(self, numbers): + if self._validate_numeric_or_valid_punct(numbers): + if "-" in numbers or "," in numbers: + return self._validate_spectra_list(numbers) + else: + return self.validate_spectrum(numbers) + return False + + @staticmethod + def _validate_numeric_or_valid_punct(string): + if all(c.isdigit() or c in VALID_PUNCT for c in string): + return True + else: + raise ValueError("Invalid characters entered. Only numeric characters, ',', and '-' are allowed.") + + def _validate_spectra_list(self, numbers): + numbers = re.split(SPLITTING_REGEX, numbers) + return all(self.validate_spectrum(i) for i in numbers) + + @staticmethod + def validate_spectrum(number): + number = number.strip() + return number.isdigit() and 0 <= int(number) <= ENGINX_MAX_SPECTRA + + def _clean_spectrum_numbers(self, numbers): + numbers = [word.strip() for word in numbers.split(",")] + return ",".join([self._clean_ranges(i) for i in numbers]) + + @staticmethod + def _clean_ranges(word): + if "-" in word: + nums = word.split("-") + num1, num2 = (i.strip() for i in nums) + if num1 and num2: + if int(num1) > int(num2): + word = "-".join([num2, num1]) + elif int(num1) < int(num2): + word = "-".join([num1, num2]) + else: # Not a valid range + raise ValueError("Ranges cannot contain the same value twice. Invalid Range: " + word) + return word diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..7481cd493260c5ea6a2f62675b8b807781031c50 --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py @@ -0,0 +1,79 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + + +class CroppingPresenter(object): + def __init__(self, model, view): + self.model = model + self.view = view + + self.bank = 0 + self.custom_spectra_enabled = False + self.custom_spectra = "" + self.custom_valid = True + + # Connect view signals to local functions + self.view.set_on_combo_changed(self.on_combo_changed) + self.view.set_on_custom_spectra_changed(self.on_spectra_changed) + + self.on_combo_changed(0) + + # Signal Activated Functions + + def on_combo_changed(self, index): + if index == 0: + self.bank = 1 + self.custom_spectra_enabled = False + self.set_custom_visibility(False) + elif index == 1: + self.bank = 2 + self.custom_spectra_enabled = False + self.set_custom_visibility(False) + else: + self.bank = 0 + self.custom_spectra_enabled = True + self.set_custom_visibility(True) + + def on_spectra_changed(self, text): + error, value = self.model.validate_and_clean_spectrum_numbers(text) + if error == "": + self.custom_spectra = value + self.set_invalid_status(error) + + # Getters + + def get_custom_spectra(self): + return self.custom_spectra + + def get_custom_spectra_enabled(self): + return self.custom_spectra_enabled + + def get_bank(self): + return self.bank + + def is_valid(self): + return self.custom_valid + + # Setters + + def set_custom_visibility(self, visible): + if visible: + self.on_spectra_changed(self.view.get_custom_spectra_text()) + self.view.set_custom_spectra_entry_visible() + else: + self.custom_valid = True # Make custom valid if not used, makes validation easier. + self.view.set_custom_spectra_entry_hidden() + + def set_invalid_status(self, text): + if text: + self.view.set_invalid_indicator_visible(text) + self.custom_valid = False + else: + self.view.set_invalid_indicator_hidden() + self.custom_valid = True diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py new file mode 100644 index 0000000000000000000000000000000000000000..0a828da157100d26f203547990d97e0314e857f6 --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py @@ -0,0 +1,57 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) +from qtpy import QtWidgets + +from mantidqt.utils.qt import load_ui + +Ui_cropping, _ = load_ui(__file__, "cropping_widget.ui") + + +class CroppingView(QtWidgets.QWidget, Ui_cropping): + def __init__(self, parent): + super(CroppingView, self).__init__(parent) + self.setupUi(self) + self.widget_custom.hide() + + # ================= + # Slot Connectors + # ================= + + def set_on_combo_changed(self, slot): + self.combo_bank.currentIndexChanged.connect(slot) + + def set_on_custom_spectra_changed(self, slot): + self.edit_custom.textChanged.connect(slot) + + # ================= + # Component Setters + # ================= + + def set_custom_spectra_entry_hidden(self): + self.widget_custom.hide() + + def set_custom_spectra_entry_visible(self): + self.widget_custom.show() + + def set_invalid_indicator_hidden(self): + self.label_customValid.hide() + + def set_invalid_indicator_visible(self, string): + self.label_customValid.setToolTip(string) + self.label_customValid.show() + + # ================= + # Component Getters + # ================= + + def get_combo_value(self): + return self.combo_bank.currentText() + + def get_custom_spectra_text(self): + return self.edit_custom.text() diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..64a68a4d01a1da88f286af8fe8683d917ecb7b41 --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py @@ -0,0 +1,34 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + +from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_model import CroppingModel +from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_view import CroppingView +from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_presenter import CroppingPresenter + + +class CroppingWidget(object): + def __init__(self, parent, view=None): + if view is None: + self.view = CroppingView(parent) + else: + self.view = view + self.model = CroppingModel() + self.presenter = CroppingPresenter(self.model, self.view) + + def is_valid(self): + return self.presenter.is_valid() + + def get_custom_spectra(self): + return self.presenter.get_custom_spectra() + + def get_bank(self): + return str(self.presenter.get_bank()) + + def is_custom(self): + return self.presenter.get_custom_spectra_enabled() diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui new file mode 100644 index 0000000000000000000000000000000000000000..452075824fbea9f6ac28a356008f6d019ae1f45e --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>516</width> + <height>86</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_bank"> + <property name="text"> + <string>Select Bank/Spectra:</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QComboBox" name="combo_bank"> + <item> + <property name="text"> + <string>1 (North)</string> + </property> + </item> + <item> + <property name="text"> + <string>2 (South)</string> + </property> + </item> + <item> + <property name="text"> + <string>Custom Spectra</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QWidget" name="widget_custom" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_custom"> + <property name="text"> + <string>Custom Spectra:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="edit_custom"> + <property name="placeholderText"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_customValid"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>10</horstretch> + <verstretch>20</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>10</width> + <height>0</height> + </size> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>170</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>170</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="WindowText"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>118</red> + <green>116</green> + <blue>108</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <property name="text"> + <string>*</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/__init__.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py new file mode 100644 index 0000000000000000000000000000000000000000..c823a4f48519f8a885e9d3035334c951b4dc9a09 --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py @@ -0,0 +1,69 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + +import unittest + +from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_model import CroppingModel + + +class CroppingModelTest(unittest.TestCase): + def setUp(self): + self.model = CroppingModel() + + def test_validate_single_valid_spectra(self): + self.assertTrue(self.model.validate_spectrum_numbers("1,2,3,4,5,6,7,8,9,10")) + + def test_validate_single_valid_spectra_regular_whitespace(self): + self.assertTrue(self.model.validate_spectrum_numbers("1, 2, 3, 4, 5, 6, 7, 8, 9, 10")) + + def test_validate_single_valid_spectra_irregular_whitespace(self): + self.assertTrue(self.model.validate_spectrum_numbers("1, 2,3,4, 5,6 ,7, 8, 9, 10")) + + def test_validate_single_spectra_invalid_negative(self): + self.assertFalse(self.model.validate_spectrum_numbers("1,2,3,4,-5,6,7,8,9,10")) + + def test_validate_single_spectra_invalid_spectrum(self): + self.assertFalse(self.model.validate_spectrum_numbers("1,2,3,4,5,6,77777,8,9,")) + + def test_validate_ranged_spectra(self): + self.assertTrue(self.model.validate_spectrum_numbers("1-5, 5, 3 , 2-7, 7-13")) + + def test_clean_spectrum_numbers_regular_whitespace(self): + self.assertEqual(self.model._clean_spectrum_numbers("1, 2, 5, 76, 3"), "1,2,5,76,3") + + def test_clean_spectrum_numbers_irregular_whitespace(self): + self.assertEqual(self.model._clean_spectrum_numbers("1 , 2, 5 , 76, 3 "), + "1,2,5,76,3") + + def test_clean_spectrum_numbers_regular_ranges(self): + self.assertEqual(self.model._clean_spectrum_numbers("1-2, 5-76, 3"), "1-2,5-76,3") + + def test_clean_spectrum_numbers_reversed_ranges(self): + self.assertEqual(self.model._clean_spectrum_numbers("2-1, 76-5, 3"), "1-2,5-76,3") + + def test_clean_spectrum_numbers_equal_range(self): + self.assertRaisesRegexp(ValueError, + "Ranges cannot contain the same value twice. Invalid Range:*", + self.model._clean_spectrum_numbers, "1-1, 76-76, 3") + + def test_validate_and_clean_with_valid_input(self): + self.assertEqual(self.model.validate_and_clean_spectrum_numbers("1-6, 7-23, 46, 1"), + ("", "1-6,7-23,46,1")) + + def test_validate_and_clean_reverse_ranges(self): + self.assertEqual(self.model.validate_and_clean_spectrum_numbers("6-1, 7-24, 6-4,1"), + ("", "1-6,7-24,4-6,1")) + + def test_validate_and_clean_equal_ranges(self): + self.assertEqual(self.model.validate_and_clean_spectrum_numbers("6-6, 7-24, 6-4,1"), + ("Ranges cannot contain the same value twice. Invalid Range: 6-6", "")) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..84cb6f592b28af9a18f8a40a2c871aae90907f88 --- /dev/null +++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py @@ -0,0 +1,66 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + +import unittest + +from mantid.py3compat import mock +from Engineering.gui.engineering_diffraction.tabs.common.cropping import cropping_model, cropping_view, cropping_presenter + +dir_path = "Engineering.gui.engineering_diffraction.tabs.common.cropping" + + +class CroppingPresenterTest(unittest.TestCase): + def setUp(self): + self.view = mock.create_autospec(cropping_view.CroppingView) + self.model = mock.create_autospec(cropping_model.CroppingModel) + self.presenter= cropping_presenter.CroppingPresenter(self.model, self.view) + + def test_combo_changed_index_bank_1(self): + self.presenter.on_combo_changed(0) + + self.assertEqual(self.presenter.bank, 1) + self.assertFalse(self.presenter.custom_spectra_enabled) + self.view.set_custom_spectra_entry_hidden.assert_called_with() + + def test_combo_changed_index_bank_2(self): + self.presenter.on_combo_changed(1) + + self.assertEqual(self.presenter.bank, 2) + self.assertFalse(self.presenter.custom_spectra_enabled) + self.view.set_custom_spectra_entry_hidden.assert_called_with() + + def test_combo_changed_index_custom(self): + self.model.validate_and_clean_spectrum_numbers.return_value = ("", "1400") + self.presenter.on_combo_changed(2) + + self.assertEqual(self.presenter.bank, 0) + self.assertTrue(self.presenter.custom_spectra_enabled) + self.view.set_custom_spectra_entry_visible.assert_called_with() + + def test_custom_spectra_changed_valid(self): + self.model.validate_and_clean_spectrum_numbers.return_value = ("", "1-5, 3-45") + + self.presenter.on_spectra_changed("1-5, 45-3") + + self.assertEqual(self.presenter.custom_spectra, "1-5, 3-45") + self.assertTrue(self.presenter.custom_valid) + self.view.set_invalid_indicator_hidden.assert_called_with() + + def test_custom_spectra_changed_invalid(self): + self.model.validate_and_clean_spectrum_numbers.return_value = ("Error lol", "") + + self.presenter.on_spectra_changed("1-5, 45-3") + + self.assertEqual(self.presenter.custom_spectra, "") + self.assertFalse(self.presenter.custom_valid) + self.view.set_invalid_indicator_visible.assert_called_with("Error lol") + + +if __name__ == '__main__': + unittest.main()