diff --git a/Framework/Properties/Mantid.properties.template b/Framework/Properties/Mantid.properties.template index ac49718914c49cf6b3b0bce1f12ab5fa1dfcbb95..1c43db9424f4365395f8d03f50c4797c1b27db2e 100644 --- a/Framework/Properties/Mantid.properties.template +++ b/Framework/Properties/Mantid.properties.template @@ -20,7 +20,7 @@ default.instrument = Q.convention = Inelastic # Set of PyQt interfaces to add to the Interfaces menu, separated by a space. Interfaces are seperated from their respective categories by a "/". -mantidqt.python_interfaces = Direct/DGS_Reduction.py Direct/DGSPlanner.py Direct/PyChop.py SANS/ORNL_SANS.py Reflectometry/REFL_Reduction.py Reflectometry/REFL_SF_Calculator.py Reflectometry/REFM_Reduction.py Utility/TofConverter.py Reflectometry/ISIS_Reflectometry_Old.py Diffraction/Powder_Diffraction_Reduction.py Utility/FilterEvents.py Diffraction/HFIR_Powder_Diffraction_Reduction.py Diffraction/HFIR_4Circle_Reduction.py Utility/QECoverage.py SANS/SANSDataProcessorInterface.py +mantidqt.python_interfaces = Direct/DGS_Reduction.py Direct/DGSPlanner.py Direct/PyChop.py SANS/ORNL_SANS.py Reflectometry/REFL_Reduction.py Reflectometry/REFL_SF_Calculator.py Reflectometry/REFM_Reduction.py Utility/TofConverter.py Reflectometry/ISIS_Reflectometry_Old.py Diffraction/Powder_Diffraction_Reduction.py Utility/FilterEvents.py Diffraction/HFIR_Powder_Diffraction_Reduction.py Diffraction/HFIR_4Circle_Reduction.py Utility/QECoverage.py SANS/ISIS_SANS_v2_experimental.py mantidqt.python_interfaces_directory = @MANTID_ROOT@/scripts diff --git a/scripts/SANSDataProcessorInterface.py b/scripts/ISIS_SANS_v2_experimental.py similarity index 100% rename from scripts/SANSDataProcessorInterface.py rename to scripts/ISIS_SANS_v2_experimental.py diff --git a/scripts/Interface/ui/sans_isis/CMakeLists.txt b/scripts/Interface/ui/sans_isis/CMakeLists.txt index 06f861af286657ca064796c8dcb97e1bb376a7d4..bf5f20d465b0ccb6379137c62ff284b423d9d99e 100644 --- a/scripts/Interface/ui/sans_isis/CMakeLists.txt +++ b/scripts/Interface/ui/sans_isis/CMakeLists.txt @@ -3,6 +3,7 @@ set( UI_FILES sans_data_processor_window.ui settings_diagnostic_tab.ui +masking_table.ui ) UiToPy( UI_FILES CompileUISANSDataProcessorInterface) diff --git a/scripts/Interface/ui/sans_isis/masking_table.py b/scripts/Interface/ui/sans_isis/masking_table.py new file mode 100644 index 0000000000000000000000000000000000000000..16a060f90a573b0c7982cf9c5a1356a9dc4adf40 --- /dev/null +++ b/scripts/Interface/ui/sans_isis/masking_table.py @@ -0,0 +1,93 @@ +import ui_masking_table +from PyQt4 import QtGui, QtCore +from abc import ABCMeta, abstractmethod +from six import with_metaclass + + +class MaskingTable(QtGui.QWidget, ui_masking_table.Ui_MaskingTable): + class MaskingTableListener(with_metaclass(ABCMeta, object)): + """ + Defines the elements which a presenter can listen to for the masking table + """ + @abstractmethod + def on_row_changed(self): + pass + + @abstractmethod + def on_update_rows(self): + pass + + @abstractmethod + def on_display(self): + pass + + def __init__(self, parent=None): + super(MaskingTable, self).__init__(parent) + self.setupUi(self) + + # Hook up signal and slots + self.connect_signals() + self._masking_tab_listeners = [] + + def add_listener(self, listener): + if not isinstance(listener, MaskingTable.MaskingTableListener): + raise ValueError("The listener ist not of type MaskingTableListener but rather {}".format(type(listener))) + self._masking_tab_listeners.append(listener) + + def clear_listeners(self): + self._masking_tab_listeners = [] + + def _call_masking_tab_listeners(self, target): + for listener in self._masking_tab_listeners: + target(listener) + + def on_row_changed(self): + self._call_masking_tab_listeners(lambda listener: listener.on_row_changed()) + + def on_update_rows(self): + self._call_masking_tab_listeners(lambda listener: listener.update_rows()) + + def on_display(self): + self._call_masking_tab_listeners(lambda listener: listener.on_display()) + + def connect_signals(self): + self.select_row_combo_box.currentIndexChanged.connect(self.on_row_changed) + + # ------------------------------------------------------------------------------------------------------------------ + # Actions + # ------------------------------------------------------------------------------------------------------------------ + def get_current_row(self): + value = self.select_row_combo_box.currentText() + if not value: + value = -1 + return int(value) + + def set_row(self, index): + found_index = self.select_row_combo_box.findText(str(index)) + if found_index and found_index != -1: + self.select_row_combo_box.setCurrentIndex(found_index) + + def update_rows(self, indices): + self.select_row_combo_box.blockSignals(True) + self.select_row_combo_box.clear() + for index in indices: + self.select_row_combo_box.addItem(str(index)) + self.select_row_combo_box.blockSignals(False) + + def set_table(self, table_entries): + # Remove all rows + for index in reversed(range(self.masking_table.rowCount())): + self.masking_table.removeRow(index) + + # Set the number of rows + self.masking_table.setRowCount(len(table_entries)) + + # Populate the rows + for row, table_entry in enumerate(table_entries): + entry_type = QtGui.QTableWidgetItem(table_entry.first) + entry_detector = QtGui.QTableWidgetItem(table_entry.second) + entry_detail = QtGui.QTableWidgetItem(table_entry.third) + + self.masking_table.setItem(row, 0, entry_type) + self.masking_table.setItem(row, 1, entry_detector) + self.masking_table.setItem(row, 2, entry_detail) diff --git a/scripts/Interface/ui/sans_isis/masking_table.ui b/scripts/Interface/ui/sans_isis/masking_table.ui new file mode 100644 index 0000000000000000000000000000000000000000..98b7aaa03d98c677fa03cbd4f68ccd887a8c621c --- /dev/null +++ b/scripts/Interface/ui/sans_isis/masking_table.ui @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MaskingTable</class> + <widget class="QWidget" name="MaskingTable"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>455</width> + <height>506</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Masking information</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="6"> + <widget class="QTableWidget" name="masking_table"> + <property name="columnCount"> + <number>3</number> + </property> + <column> + <property name="text"> + <string>Type</string> + </property> + </column> + <column> + <property name="text"> + <string>Detector</string> + </property> + </column> + <column> + <property name="text"> + <string>Details</string> + </property> + </column> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Select row:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="select_row_combo_box"/> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="select_row_push_button"> + <property name="text"> + <string>Update Rows</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py index eab0a3c6a2305feaf0320a0cc8471d4ca4ce2b3d..ba4449b9eb79b2b2cf9ca84fdaf58b81fdd2610f 100644 --- a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py +++ b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py @@ -1,4 +1,3 @@ -#from __future__ import (absolute_import, division, print_function) try: from mantidplot import * except ImportError: @@ -23,7 +22,6 @@ canMantidPlot = True # Globals # ---------------------------------------------------------------------------------------------------------------------- gui_logger = Logger("SANS GUI LOGGER") -BAD_POLYNOMAIL = -1 # ---------------------------------------------------------------------------------------------------------------------- @@ -163,6 +161,7 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S # Main Tab # -------------------------------------------------------------------------------------------------------------- self.data_processor_table = MantidQt.MantidWidgets.QDataProcessorWidget(white_list, alg, self) + self.data_processor_table.setForcedReProcessing(True) self._setup_main_tab() # -------------------------------------------------------------------------------------------------------------- @@ -1296,10 +1295,10 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.transmission_radius_line_edit.setValidator(positive_double_validator) self.transmission_m4_shift_line_edit.setValidator(double_validator) - #self.fit_sample_wavelength_min_line_edit.setValidator(positive_double_validator) - #self.fit_sample_wavelength_max_line_edit.setValidator(positive_double_validator) - #self.fit_can_wavelength_min_line_edit.setValidator(positive_double_validator) - #self.fit_can_wavelength_max_line_edit.setValidator(positive_double_validator) + self.fit_sample_wavelength_min_line_edit.setValidator(positive_double_validator) + self.fit_sample_wavelength_max_line_edit.setValidator(positive_double_validator) + self.fit_can_wavelength_min_line_edit.setValidator(positive_double_validator) + self.fit_can_wavelength_max_line_edit.setValidator(positive_double_validator) # -------------------------------- # Q tab diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui index f5cc757c48d3919ab9390b078b6fab34504416da..e902511c80984f0e78db58f595311deca556ba2e 100755 --- a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui +++ b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui @@ -64,7 +64,7 @@ QGroupBox::title { <item> <widget class="QStackedWidget" name="main_stacked_widget"> <property name="currentIndex"> - <number>1</number> + <number>0</number> </property> <widget class="QWidget" name="run_page"> <layout class="QVBoxLayout" name="verticalLayout_3"> @@ -83,6 +83,9 @@ QGroupBox::title { <property name="text"> <string>User File:</string> </property> + <property name="buddy"> + <cstring>masking_table</cstring> + </property> </widget> </item> <item row="2" column="3"> @@ -264,9 +267,6 @@ QGroupBox::title { <item> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <widget class="QLineEdit" name="save_line_edit"/> - </item> - <item row="1" column="0"> <widget class="QCheckBox" name="save_zero_error_free"> <property name="toolTip"> <string><html><head/><body><p>When enabled it will replace all entries with zero-valued errors with errors of magnitude 1e6. </p></body></html></string> @@ -279,14 +279,7 @@ QGroupBox::title { </property> </widget> </item> - <item row="0" column="1"> - <widget class="QPushButton" name="save_push_button"> - <property name="text"> - <string>Save</string> - </property> - </widget> - </item> - <item row="1" column="1"> + <item row="1" column="0"> <widget class="QCheckBox" name="use_optimizations_checkbox"> <property name="toolTip"> <string><html><head/><body><p>When optimizations are turned on then data is being stored in RAM and reused. The optimizations affect the raw data files and can reductions. Most of the time you will want these optimizations enabled. Disable the optimizations if you run a very long batch reduction where there might be the danger that your RAM will max out.</p></body></html></string> @@ -795,37 +788,15 @@ QGroupBox::title { </attribute> <layout class="QHBoxLayout" name="horizontalLayout_7"> <item> - <layout class="QVBoxLayout" name="mask_tab_left_layout"> - <item> - <widget class="QGroupBox" name="groupBox_5"> - <property name="title"> - <string>Masking</string> - </property> - <layout class="QGridLayout" name="gridLayout_21"> - <item row="0" column="1"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Add mask table here</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> + <widget class="MaskingTable" name="masking_table" native="true"> + <property name="minimumSize"> + <size> + <width>400</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="mask_tab_left_layout"/> + </widget> </item> <item> <layout class="QVBoxLayout" name="mask_tab_right_layout"> @@ -1934,6 +1905,12 @@ QGroupBox::title { <header>settings_diagnostic_tab.h</header> <container>1</container> </customwidget> + <customwidget> + <class>MaskingTable</class> + <extends>QWidget</extends> + <header>masking_table.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections/> diff --git a/scripts/SANS/sans/algorithm_detail/load_data.py b/scripts/SANS/sans/algorithm_detail/load_data.py index 997ada3730da322362ef6167b1dbd7e1214f0bf5..f8806ba538f992ca4a153523656b3bce87bafaf2 100644 --- a/scripts/SANS/sans/algorithm_detail/load_data.py +++ b/scripts/SANS/sans/algorithm_detail/load_data.py @@ -78,27 +78,27 @@ def get_file_and_period_information_from_data(data): file_information_factory = SANSFileInformationFactory() file_information = dict() period_information = dict() - if data.sample_scatter is not None: + if data.sample_scatter: update_file_information(file_information, file_information_factory, SANSDataType.SampleScatter, data.sample_scatter) period_information.update({SANSDataType.SampleScatter: data.sample_scatter_period}) - if data.sample_transmission is not None: + if data.sample_transmission: update_file_information(file_information, file_information_factory, SANSDataType.SampleTransmission, data.sample_transmission) period_information.update({SANSDataType.SampleTransmission: data.sample_transmission_period}) - if data.sample_direct is not None: + if data.sample_direct: update_file_information(file_information, file_information_factory, SANSDataType.SampleDirect, data.sample_direct) period_information.update({SANSDataType.SampleDirect: data.sample_direct_period}) - if data.can_scatter is not None: + if data.can_scatter: update_file_information(file_information, file_information_factory, SANSDataType.CanScatter, data.can_scatter) period_information.update({SANSDataType.CanScatter: data.can_scatter_period}) - if data.can_transmission is not None: + if data.can_transmission: update_file_information(file_information, file_information_factory, SANSDataType.CanTransmission, data.can_transmission) period_information.update({SANSDataType.CanTransmission: data.can_transmission_period}) - if data.can_direct is not None: + if data.can_direct: update_file_information(file_information, file_information_factory, SANSDataType.CanDirect, data.can_direct) period_information.update({SANSDataType.CanDirect: data.can_direct_period}) diff --git a/scripts/SANS/sans/common/file_information.py b/scripts/SANS/sans/common/file_information.py index 19725adef267b0c3a1bbe9016f34ccb5dc9c757b..04105f38a9a5af7599874bb3b6f98822e61ef9b6 100644 --- a/scripts/SANS/sans/common/file_information.py +++ b/scripts/SANS/sans/common/file_information.py @@ -997,6 +997,7 @@ class SANSFileInformationFactory(object): super(SANSFileInformationFactory, self).__init__() def create_sans_file_information(self, file_name): + full_file_name = find_sans_file(file_name) if is_isis_nexus_single_period(full_file_name) or is_isis_nexus_multi_period(full_file_name): file_information = SANSFileInformationISISNexus(full_file_name) diff --git a/scripts/SANS/sans/gui_logic/gui_common.py b/scripts/SANS/sans/gui_logic/gui_common.py index 15e0c75c71d515a6a144c5d9e871181e14cfb9fc..026913e8ea83af57ec64555fdf9ce04fe4f5e3a6 100644 --- a/scripts/SANS/sans/gui_logic/gui_common.py +++ b/scripts/SANS/sans/gui_logic/gui_common.py @@ -4,10 +4,10 @@ from collections import namedtuple # ---------------------------------------------------------------------------------------------------------------------- # Option column globals # ---------------------------------------------------------------------------------------------------------------------- -OPTIONS_INDEX = 6 +OPTIONS_INDEX = 7 OPTIONS_SEPARATOR = "," OPTIONS_EQUAL = "=" -HIDDEN_OPTIONS_INDEX = 7 +HIDDEN_OPTIONS_INDEX = 8 # ---------------------------------------------------------------------------------------------------------------------- # Other Globals diff --git a/scripts/SANS/sans/gui_logic/models/state_gui_model.py b/scripts/SANS/sans/gui_logic/models/state_gui_model.py index c81e3e8175b2a6f96da81615b18ee668d461a4b7..295bef9e5908ea1e6a3f0a8f5b65adc53cb51f4b 100644 --- a/scripts/SANS/sans/gui_logic/models/state_gui_model.py +++ b/scripts/SANS/sans/gui_logic/models/state_gui_model.py @@ -978,3 +978,14 @@ class StateGuiModel(object): @radius_limit_max.setter def radius_limit_max(self, value): self._set_radius_limit(max_value=value) + + # ------------------------------------------------------------------------------------------------------------------ + # Output name + # ------------------------------------------------------------------------------------------------------------------ + @property + def output_name(self): + return self.get_simple_element(element_id=OtherId.user_specified_output_name, default_value="") + + @output_name.setter + def output_name(self, value): + self.set_simple_element(element_id=OtherId.user_specified_output_name, value=value) diff --git a/scripts/SANS/sans/gui_logic/models/table_model.py b/scripts/SANS/sans/gui_logic/models/table_model.py index 4ddf8b65eaf0cfe9fc21811160bdb5092f386890..c3fd40f9f686dc7cc31d29623f973dacb892ec68 100644 --- a/scripts/SANS/sans/gui_logic/models/table_model.py +++ b/scripts/SANS/sans/gui_logic/models/table_model.py @@ -49,7 +49,8 @@ class TableModel(object): class TableIndexModel(object): def __init__(self, index, sample_scatter, sample_transmission, sample_direct, - can_scatter, can_transmission, can_direct, options_column_string=""): + can_scatter, can_transmission, can_direct, output_name="", + options_column_string=""): super(TableIndexModel, self).__init__() self.index = index self.sample_scatter = sample_scatter @@ -61,7 +62,7 @@ class TableIndexModel(object): self.can_direct = can_direct self.user_file = "" - self.output_name = "" + self.output_name = output_name # Options column entries self.options_column_model = OptionsColumnModel(options_column_string) diff --git a/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py b/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py index fcf5f91a7664280e06d930d5a49ce45585629a3e..b2149f349c9ab16ac98af665d0ff324784095f77 100644 --- a/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py +++ b/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py @@ -30,7 +30,12 @@ class GuiStateDirector(object): options_column_model = table_index_model.options_column_model self._apply_column_options_to_state(options_column_model, state_gui_model) - # 3. Create the rest of the state based on the builder. + # 3. Add other columns + output_name = table_index_model.output_name + if output_name: + state_gui_model.output_name = output_name + + # 4. Create the rest of the state based on the builder. user_file_state_director = StateDirectorISIS(data) settings = copy.deepcopy(state_gui_model.settings) user_file_state_director.add_state_settings(settings) diff --git a/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..6597733e98041729081d005deda808d3b528b17a --- /dev/null +++ b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py @@ -0,0 +1,383 @@ +from ui.sans_isis.masking_table import MaskingTable +from collections import namedtuple +import copy +from mantid.api import (AnalysisDataService, IEventWorkspace) +import mantidplot +from sans.common.enums import DetectorType +from sans.common.constants import EMPTY_NAME +from sans.common.general_functions import create_unmanaged_algorithm + + +masking_information = namedtuple("masking_information", "first, second, third") + + +class MaskingTablePresenter(object): + DISPLAY_WORKSPACE_NAME = "__sans_mask_display_dummy_workspace" + + class ConcreteMaskingTableListener(MaskingTable.MaskingTableListener): + def __init__(self, presenter): + super(MaskingTablePresenter.ConcreteMaskingTableListener, self).__init__() + self._presenter = presenter + + def on_row_changed(self): + self._presenter.on_row_changed() + + def on_update_rows(self): + self._presenter.on_update_rows() + + def on_display(self): + self._presenter.on_display() + + def __init__(self, parent_presenter): + super(MaskingTablePresenter, self).__init__() + self._view = None + self._parent_presenter = parent_presenter + + def on_row_changed(self): + row_index = self._view.get_current_row() + state = self.get_state(row_index) + if state: + self.display_masking_information(state) + + def on_display(self): + # TODO: This will run synchronously and block the GUI, therefore it is not enabled at the moment. Once, + # we agree on a async framework which we can use in combination with Mantid in Python, we will have to + # disable this feature. Once it is solved, it is easy to hook up. + + # Load the sample workspace + row_index = self._view.get_current_row() + state = self.get_state(row_index) + workspace_to_mask = self._load_the_workspace_to_mask(state) + + # Apply the mask + self._mask_workspace(state, workspace_to_mask) + + # Display + self._display(workspace_to_mask) + + def on_update_rows(self): + """ + Update the row selection in the combobox + """ + current_row_index = self._view.get_current_row() + valid_row_indices = self._parent_presenter.get_row_indices() + + new_row_index = -1 + if current_row_index in valid_row_indices: + new_row_index = current_row_index + elif len(valid_row_indices) > 0: + new_row_index = valid_row_indices[0] + + self._view.update_rows(valid_row_indices) + + if new_row_index != -1: + self.set_row(new_row_index) + self.on_row_changed() + + def set_row(self, index): + self._view.set_row(index) + + def set_view(self, view): + if view: + self._view = view + + # Set up row selection listener + listener = MaskingTablePresenter.ConcreteMaskingTableListener(self) + self._view.add_listener(listener) + + # Set the default gui + self._set_default_gui() + + def _set_default_gui(self): + self._view.update_rows([]) + self.display_masking_information(state=None) + + def get_state(self, index): + return self._parent_presenter.get_state_for_row(index) + + @staticmethod + def _append_single_spectrum_mask(spectrum_mask, container, detector_name, prefix): + if spectrum_mask: + for item in spectrum_mask: + detail = prefix + str(item) + container.append(masking_information(first="Spectrum", second=detector_name, third=detail)) + + @staticmethod + def _append_strip_spectrum_mask(strip_mask_start, strip_mask_stop, container, detector_name, prefix): + if strip_mask_start and strip_mask_stop: + for start, stop in zip(strip_mask_start, strip_mask_stop): + detail = prefix + str(start) + ">" + prefix + str(stop) + container.append(masking_information(first="Strip", second=detector_name, third=detail)) + + @staticmethod + def _append_block_spectrum_mask(horizontal_mask_start, horizontal_mask_stop, vertical_mask_start, + vertical_mask_stop, container, detector_name): + if horizontal_mask_start and horizontal_mask_stop and vertical_mask_start and vertical_mask_stop: + for h_start, h_stop, v_start, v_stop in zip(horizontal_mask_start, horizontal_mask_stop, + vertical_mask_start, vertical_mask_stop): + detail = "H{}>H{}+V{}>V{}".format(h_start, h_stop, v_start, v_stop) + container.append(masking_information(first="Strip", second=detector_name, third=detail)) + + @staticmethod + def _append_spectrum_block_cross_mask(horizontal_mask, vertical_mask, container, detector_name): + if horizontal_mask and vertical_mask: + for h, v in zip(horizontal_mask, vertical_mask): + detail = "H{}+V{}".format(h, v) + container.append(masking_information(first="Strip", second=detector_name, third=detail)) + + @staticmethod + def _get_spectrum_masks(mask_detector_info): + detector_name = mask_detector_info.detector_name + spectrum_masks = [] + + # ------------------------------- + # Get the vertical spectrum masks + # ------------------------------- + single_vertical_strip_mask = mask_detector_info.single_vertical_strip_mask + MaskingTablePresenter._append_single_spectrum_mask(single_vertical_strip_mask, spectrum_masks, + detector_name, "V") + + range_vertical_strip_start = mask_detector_info.range_vertical_strip_start + range_vertical_strip_stop = mask_detector_info.range_vertical_strip_stop + MaskingTablePresenter._append_strip_spectrum_mask(range_vertical_strip_start, + range_vertical_strip_stop, + spectrum_masks, detector_name, "V") + + # --------------------------------- + # Get the horizontal spectrum masks + # --------------------------------- + single_horizontal_strip_mask = mask_detector_info.single_horizontal_strip_mask + MaskingTablePresenter._append_single_spectrum_mask(single_horizontal_strip_mask, spectrum_masks, + detector_name, "H") + + range_horizontal_strip_start = mask_detector_info.range_horizontal_strip_start + range_horizontal_strip_stop = mask_detector_info.range_horizontal_strip_stop + MaskingTablePresenter._append_strip_spectrum_mask(range_horizontal_strip_start, + range_horizontal_strip_stop, + spectrum_masks, detector_name, "H") + + # --------------------------------- + # Get the block masks + # --------------------------------- + block_horizontal_start = mask_detector_info.block_horizontal_start + block_horizontal_stop = mask_detector_info.block_horizontal_stop + block_vertical_start = mask_detector_info.block_vertical_start + block_vertical_stop = mask_detector_info.block_vertical_stop + MaskingTablePresenter._append_block_spectrum_mask(block_horizontal_start, block_horizontal_stop, + block_vertical_start, block_vertical_stop, + spectrum_masks, detector_name) + + block_cross_horizontal = mask_detector_info.block_cross_horizontal + block_cross_vertical = mask_detector_info.block_cross_vertical + MaskingTablePresenter._append_spectrum_block_cross_mask(block_cross_horizontal, block_cross_vertical, + spectrum_masks, detector_name) + + # --------------------------------- + # Get spectrum masks + # --------------------------------- + single_spectra = mask_detector_info.single_spectra + MaskingTablePresenter._append_single_spectrum_mask(single_spectra, spectrum_masks, + detector_name, "S") + + spectrum_range_start = mask_detector_info.spectrum_range_start + spectrum_range_stop = mask_detector_info.spectrum_range_stop + MaskingTablePresenter._append_strip_spectrum_mask(spectrum_range_start, + spectrum_range_stop, + spectrum_masks, detector_name, "S") + + return spectrum_masks + + @staticmethod + def _get_time_masks_general(mask_info): + container = [] + bin_mask_general_start = mask_info.bin_mask_general_start + bin_mask_general_stop = mask_info.bin_mask_general_stop + if bin_mask_general_start and bin_mask_general_stop: + for start, stop in zip(bin_mask_general_start, bin_mask_general_stop): + detail = "{}-{}".format(start, stop) + container.append(masking_information(first="Time", second="", third=detail)) + return container + + @staticmethod + def _get_time_masks(mask_info): + container = [] + bin_mask_start = mask_info.bin_mask_start + bin_mask_stop = mask_info.bin_mask_stop + detector_name = mask_info.detector_name + if bin_mask_start and bin_mask_stop: + for start, stop in zip(bin_mask_start, bin_mask_stop): + detail = "{}-{}".format(start, stop) + container.append(masking_information(first="Time", second=detector_name, third=detail)) + return container + + @staticmethod + def _get_arm_mask(mask_info): + container = [] + beam_stop_arm_width = mask_info.beam_stop_arm_width + beam_stop_arm_angle = mask_info.beam_stop_arm_angle + beam_stop_arm_pos1 = mask_info.beam_stop_arm_pos1 + beam_stop_arm_pos2 = mask_info.beam_stop_arm_pos2 + if beam_stop_arm_width and beam_stop_arm_angle and beam_stop_arm_pos1 and beam_stop_arm_pos2: + detail = "LINE {}, {}, {}, {}".format(beam_stop_arm_width, beam_stop_arm_angle, + beam_stop_arm_pos1, beam_stop_arm_pos2) + container.append(masking_information(first="Arm", second="", third=detail)) + return container + + @staticmethod + def _get_phi_mask(mask_info): + container = [] + phi_min = mask_info.phi_min + phi_max = mask_info.phi_max + use_mask_phi_mirror = mask_info.use_mask_phi_mirror + if phi_min and phi_max: + if use_mask_phi_mirror: + detail = "L/PHI {} {}".format(phi_min, phi_max) + else: + detail = "L/PHI/NOMIRROR{} {}".format(phi_min, phi_max) + container.append(masking_information(first="Phi", second="", third=detail)) + return container + + @staticmethod + def _get_mask_files(mask_info): + container = [] + mask_files = mask_info.mask_files + if mask_files: + for mask_file in mask_files: + container.append(masking_information(first="Mask file", second="", third=mask_file)) + return container + + @staticmethod + def _get_radius(mask_info): + container = [] + radius_min = mask_info.radius_min + radius_max = mask_info.radius_max + + if radius_min: + detail = "infinite-cylinder, r = {}".format(radius_min) + container.append(masking_information(first="Beam stop", second="", third=detail)) + + if radius_max: + detail = "infinite-cylinder, r = {}".format(radius_max) + container.append(masking_information(first="Corners", second="", third=detail)) + return container + + def _generate_masking_information(self, state): + if state is None: + return [] + mask_info = state.mask + masks = [] + + mask_info_lab = mask_info.detectors[DetectorType.to_string(DetectorType.LAB)] + mask_info_hab = mask_info.detectors[DetectorType.to_string(DetectorType.HAB)] + + # Add the radius mask + radius_mask = self._get_radius(mask_info) + masks.extend(radius_mask) + + # Add the spectrum masks for LAB + spectrum_masks_lab = self._get_spectrum_masks(mask_info_lab) + masks.extend(spectrum_masks_lab) + + # Add the spectrum masks for HAB + spectrum_masks_hab = self._get_spectrum_masks(mask_info_hab) + masks.extend(spectrum_masks_hab) + + # Add the general time mask + time_masks_general = self._get_time_masks_general(mask_info) + masks.extend(time_masks_general) + + # Add the time masks for LAB + time_masks_lab = self._get_time_masks(mask_info_lab) + masks.extend(time_masks_lab) + + # Add the time masks for HAB + time_masks_hab = self._get_time_masks(mask_info_hab) + masks.extend(time_masks_hab) + + # Add arm mask + arm_mask = self._get_arm_mask(mask_info) + masks.extend(arm_mask) + + # Add phi mask + phi_mask = self._get_phi_mask(mask_info) + masks.extend(phi_mask) + + # Add mask files + mask_files = self._get_mask_files(mask_info) + masks.extend(mask_files) + return masks + + def get_masking_information(self, state): + table_entries = [] + if state is not None: + table_entries = self._generate_masking_information(state) + return table_entries + + def display_masking_information(self, state): + table_entries = self.get_masking_information(state) + self._view.set_table(table_entries) + + def _load_the_workspace_to_mask(self, state): + # Make a deepcopy of the state + state_copy = copy.deepcopy(state) + + # We only want to load the data for the scatter sample. Hence we set everything else to an empty string. + # This is ok since we are changing a copy of the state which is not being used for the actual data reduction. + state_copy.data.sample_transmission = "" + state_copy.data.sample_direct = "" + state_copy.data.can_scatter = "" + state_copy.data.can_transmission = "" + state_copy.data.can_direct = "" + + # If the data is multi-period data, then we select only the first period. + if state_copy.data.sample_scatter_is_multi_period and state_copy.data.sample_scatter_period == 0: + state_copy.data.sample_scatter_period = 1 + + # Load the workspace + serialized_state = state_copy.property_manager + load_name = "SANSLoad" + load_options = {"SANSState": serialized_state, + "PublishToCache": False, + "UseCached": True, + "SampleScatterWorkspace": EMPTY_NAME, + "SampleScatterMonitorWorkspace": EMPTY_NAME, + "SampleTransmissionWorkspace": EMPTY_NAME, + "SampleDirectWorkspace": EMPTY_NAME, + "CanScatterWorkspace": EMPTY_NAME, + "CanScatterMonitorWorkspace": EMPTY_NAME, + "CanTransmissionWorkspace": EMPTY_NAME, + "CanDirectWorkspace": EMPTY_NAME} + load_alg = create_unmanaged_algorithm(load_name, **load_options) + load_alg.execute() + workspace_to_mask = load_alg.getProperty("SampleScatterWorkspace").value + + # Perform an initial move on the workspace + move_name = "SANSMove" + move_options = {"SANSState": serialized_state, + "Workspace": workspace_to_mask, + "MoveType": "InitialMove"} + move_alg = create_unmanaged_algorithm(move_name, **move_options) + move_alg.execute() + + # Put the workspace onto the ADS as a hidden workspace + AnalysisDataService.addOrReplace(self.DISPLAY_WORKSPACE_NAME, workspace_to_mask) + return workspace_to_mask + + @staticmethod + def _mask_workspace(state, workspace): + serialized_state = state.property_manager + mask_name = "SANSMaskWorkspace" + mask_options = {"SANSState": serialized_state, + "Workspace": workspace} + mask_alg = create_unmanaged_algorithm(mask_name, **mask_options) + + detectors = [DetectorType.to_string(DetectorType.LAB), DetectorType.to_string(DetectorType.HAB)] + for detector in detectors: + mask_alg.setProperty("Component", detector) + mask_alg.execute() + + @staticmethod + def _display(masked_workspace): + if masked_workspace and AnalysisDataService.doesExist(masked_workspace.name()): + instrument_win = mantidplot.getInstrumentView(masked_workspace.name()) + instrument_win.show() diff --git a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py index fd99385451a925df50c6ec4dd4f50d6c2b690422..dd8f34185a6113c44e992cdab27b90b17f91e9a5 100644 --- a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py +++ b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py @@ -3,7 +3,7 @@ from __future__ import (absolute_import, division, print_function) import os import copy from mantid.kernel import Logger - +import time from mantid.api import (AlgorithmManager, AnalysisDataService, FileFinder) from mantid.kernel import (Property) from ui.sans_isis.sans_data_processor_gui import SANSDataProcessorGui @@ -11,6 +11,7 @@ from sans.gui_logic.models.state_gui_model import StateGuiModel from sans.gui_logic.models.table_model import TableModel, TableIndexModel from sans.gui_logic.presenter.gui_state_director import (GuiStateDirector) from sans.gui_logic.presenter.settings_diagnostic_presenter import (SettingsDiagnosticPresenter) +from sans.gui_logic.presenter.masking_table_presenter import (MaskingTablePresenter) from sans.gui_logic.sans_data_processor_gui_algorithm import SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME from sans.gui_logic.presenter.property_manager_service import PropertyManagerService from sans.gui_logic.gui_common import (get_reduction_mode_strings_for_gui, get_reduction_mode_strings_for_gui, @@ -71,6 +72,9 @@ class RunTabPresenter(object): # Settings diagnostic tab presenter self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(self) + # Masking table presenter + self._masking_table_presenter = MaskingTablePresenter(self) + def __del__(self): self._delete_dummy_input_workspace() @@ -119,37 +123,40 @@ class RunTabPresenter(object): # Set appropriate view for the state diagnostic tab presenter self._settings_diagnostic_tab_presenter.set_view(self._view.settings_diagnostic_tab) + # Set appropriate view for the masking table presenter + self._masking_table_presenter.set_view(self._view.masking_table) + def on_user_file_load(self): """ Loads the user file. Populates the models and the view. """ - # try: - # 1. Get the user file path from the view - user_file_path = self._view.get_user_file_path() + try: + # 1. Get the user file path from the view + user_file_path = self._view.get_user_file_path() - if not user_file_path: - return + if not user_file_path: + return - # 2. Get the full file path - user_file_path = FileFinder.getFullPath(user_file_path) - if not os.path.exists(user_file_path): - raise RuntimeError("The user path {} does not exist. Make sure a valid user file path" - " has been specified.".format(user_file_path)) + # 2. Get the full file path + user_file_path = FileFinder.getFullPath(user_file_path) + if not os.path.exists(user_file_path): + raise RuntimeError("The user path {} does not exist. Make sure a valid user file path" + " has been specified.".format(user_file_path)) - # Clear out the current view - self._view.reset_all_fields_to_default() + # Clear out the current view + self._view.reset_all_fields_to_default() - # 3. Read and parse the user file - user_file_reader = UserFileReader(user_file_path) - user_file_items = user_file_reader.read_user_file() + # 3. Read and parse the user file + user_file_reader = UserFileReader(user_file_path) + user_file_items = user_file_reader.read_user_file() - # 4. Populate the model - self._state_model = StateGuiModel(user_file_items) + # 4. Populate the model + self._state_model = StateGuiModel(user_file_items) - # 5. Update the views. - self._update_view_from_state_model() - # except Exception as e: - # sans_logger.error("Loading of the user file failed. See here for more details: {}".format(str(e))) + # 5. Update the views. + self._update_view_from_state_model() + except Exception as e: + sans_logger.error("Loading of the user file failed. See here for more details: {}".format(str(e))) def on_batch_file_load(self): """ @@ -180,6 +187,10 @@ class RunTabPresenter(object): # 5. Populate the selected instrument and the correct detector selection self._setup_instrument_specific_settings() + # 6. Perform calls on child presenters + self._masking_table_presenter.on_update_rows() + self._settings_diagnostic_tab_presenter.on_update_rows() + except RuntimeError as e: sans_logger.error("Loading of the batch file failed. See here for more details: {}".format(str(e))) @@ -309,6 +320,8 @@ class RunTabPresenter(object): """ Gathers the state information and performs a reduction """ + start_time_state_generation = time.time() + # 1. Update the state model state_model_with_view_update = self._get_state_model_with_view_update() @@ -317,6 +330,10 @@ class RunTabPresenter(object): # 3. Go through each row and construct a state object states = self._create_states(state_model_with_view_update, table_model) + + stop_time_state_generation = time.time() + time_taken = stop_time_state_generation - start_time_state_generation + sans_logger.debug("The generation of all states took {}s".format(time_taken)) return states def get_row_indices(self): @@ -671,6 +688,7 @@ class RunTabPresenter(object): can_scatter = self._view.get_cell(row=row, column=3, convert_to=str) can_transmission = self._view.get_cell(row=row, column=4, convert_to=str) can_direct = self._view.get_cell(row=row, column=5, convert_to=str) + output_name = self._view.get_cell(row=row, column=6, convert_to=str) # Get the options string # We don't have to add the hidden column here, since it only contains information for the SANS @@ -678,7 +696,7 @@ class RunTabPresenter(object): options_string = self._get_options(row) table_index_model = TableIndexModel(row, sample_scatter, sample_transmission, sample_direct, - can_scatter, can_transmission, can_direct, + can_scatter, can_transmission, can_direct, output_name=output_name, options_column_string=options_string) table_model.add_table_entry(row, table_index_model) return table_model diff --git a/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py b/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py index 93d69c559afb83efbe9e224b178d672f3b3110ab..865ee1af0da5e50c00906eeba8bb9d2d54e1418a 100644 --- a/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py +++ b/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py @@ -97,6 +97,13 @@ def create_properties(): default=False, prefix='', property_type=bool), + algorithm_list_entry(column_name="OutputName", + algorithm_property="OutputName", + description='An optional custom output workspace name.', + show_value=False, + default='', + prefix='', + property_type=str), algorithm_list_entry(column_name="", algorithm_property="RowIndex", description='The row index (which is automatically populated by the GUI)', diff --git a/scripts/test/SANS/gui_logic/CMakeLists.txt b/scripts/test/SANS/gui_logic/CMakeLists.txt index c3ba818cc6b7a2b4d5aa267bb6dedc67e672468b..29555556194c187b15694e70b2fc7a851397b397 100644 --- a/scripts/test/SANS/gui_logic/CMakeLists.txt +++ b/scripts/test/SANS/gui_logic/CMakeLists.txt @@ -6,6 +6,7 @@ set ( TEST_PY_FILES gui_state_director_test.py gui_common_test.py main_presenter_test.py + masking_table_presenter_test.py property_manager_service_test.py run_tab_presenter_test.py state_gui_model_test.py diff --git a/scripts/test/SANS/gui_logic/masking_table_presenter_test.py b/scripts/test/SANS/gui_logic/masking_table_presenter_test.py new file mode 100644 index 0000000000000000000000000000000000000000..d7e2aa7e33a5818eb4ddb8107fa95b8a2d2f1344 --- /dev/null +++ b/scripts/test/SANS/gui_logic/masking_table_presenter_test.py @@ -0,0 +1,83 @@ +from __future__ import (absolute_import, division, print_function) + +import mantid +import unittest +from sans.gui_logic.presenter.settings_diagnostic_presenter import SettingsDiagnosticPresenter + + +# ------------ +# Test Helper +# ------------ +class MockState(object): + dummy_state = "dummy_state" + + def __init__(self): + super(MockState, self).__init__() + + @property + def property_manager(self): + return self.dummy_state + + +class MockParentPresenter(object): + def __init__(self): + super(MockParentPresenter, self).__init__() + + def get_row_indices(self): + # We assume that row 2 is empty + return [0, 1, 3] + + def get_state_for_row(self, row_index): + return MockState() if row_index == 3 else "" + + +class MockSettingsDiagnosticTabView(object): + def __init__(self): + super(MockSettingsDiagnosticTabView, self).__init__() + self.number_of_view_updates = 0 + self.number_of_row_updates = 0 + + def add_listener(self, listener): + pass + + def update_rows(self, rows): + _ = rows + self.number_of_row_updates += 1 + + def set_tree(self, state): + _ = state + self.number_of_view_updates += 1 + + @staticmethod + def get_current_row(): + return 3 + + +class SettingsDiagnosticPresenterTest(unittest.TestCase): + def test_that_can_get_state_for_index(self): + parent_presenter = MockParentPresenter() + presenter = SettingsDiagnosticPresenter(parent_presenter) + state = presenter.get_state(3) + self.assertTrue(isinstance(state, MockState)) + + def test_that_updates_tree_view_when_row_selection_changes(self): + parent_presenter = MockParentPresenter() + view = MockSettingsDiagnosticTabView() + presenter = SettingsDiagnosticPresenter(parent_presenter) + presenter.set_view(view) + self.assertTrue(view.number_of_view_updates == 1) + presenter.on_row_changed() + self.assertTrue(view.number_of_view_updates == 2) + + def test_that_updates_rows_when_triggered(self): + parent_presenter = MockParentPresenter() + view = MockSettingsDiagnosticTabView() + presenter = SettingsDiagnosticPresenter(parent_presenter) + presenter.set_view(view) + self.assertTrue(view.number_of_row_updates == 1) + presenter.on_update_rows() + self.assertTrue(view.number_of_row_updates == 2) + + +if __name__ == '__main__': + unittest.main()