コード例 #1
0
class OptimizationFluenceParameterWidget(OptimizationParameterWidget):
    """
    Class that handles the fluence optimization parameters' ui.
    """

    # Fluence specific properties
    upper_limits = bnd.bind("fluenceDoubleSpinBox")
    dis_c = bnd.bind("disCSpinBox")
    dis_m = bnd.bind("disMSpinBox")

    @property
    def lower_limits(self):
        return 0.0

    @property
    def sol_size(self):
        return 1

    @property
    def optimization_type(self) -> OptimizationType:
        return OptimizationType.FLUENCE

    def __init__(self, **kwargs):
        """Initialize the widget.

        Args:
            kwargs: property values to be shown in the widget.
        """
        ui_file = gutils.get_ui_dir() / "ui_optimization_fluence_params.ui"
        self.fluenceDoubleSpinBox = ScientificSpinBox(10e12)
        super().__init__(ui_file, **kwargs)
        self.fluence_form_layout.addRow("Upper limit",
                                        self.fluenceDoubleSpinBox)
コード例 #2
0
class PercentageRow(QtWidgets.QWidget,
                    bnd.PropertyBindingWidget,
                    metaclass=QtABCMeta):
    """PercentageRow is used to display percentage and area for each
    RecoilElement in the PercentageWidget.
    """
    percentage = bnd.bind("percentageLabel",
                          fget=label_to_percentage,
                          fset=percentage_to_label)
    area = bnd.bind("areaLabel", fget=label_to_area, fset=area_to_label)
    selected = bnd.bind("selectedCheckbox")

    def __init__(self, label_text, color="red", **kwargs):
        """Initializes a PercentageRow.

        Args:
            label_text: text to be shown in the main label
            color: color of the Circle that is shown next to label
            kwargs: percentage and area.
        """
        super().__init__()
        self.setMinimumHeight(20)
        layout = QtWidgets.QHBoxLayout()
        layout.setAlignment(Qt.AlignBottom)

        text_label = QtWidgets.QLabel(label_text)
        text_label.setMaximumWidth(100)
        text_label.setMinimumWidth(100)

        if platform.system() == "Linux":
            circle = Circle(color, None)
        else:
            circle = Circle(color, (1, 4, 4, 4))

        circle.setMinimumWidth(25)
        circle.setMaximumWidth(25)

        self.percentageLabel = QtWidgets.QLabel()
        self.percentageLabel.setMinimumWidth(80)
        self.percentageLabel.setMaximumWidth(80)

        self.areaLabel = QtWidgets.QLabel()
        self.areaLabel.setMinimumWidth(60)
        self.areaLabel.setMaximumWidth(60)

        self.selectedCheckbox = QtWidgets.QCheckBox()
        self.selectedCheckbox.setToolTip(
            f"Deselect to ignore the element from percentage and area "
            f"calculations.")
        self.selectedCheckbox.setChecked(True)

        layout.addWidget(text_label)
        layout.addWidget(circle)
        layout.addWidget(self.percentageLabel)
        layout.addWidget(self.areaLabel)
        layout.addWidget(self.selectedCheckbox)

        self.set_properties(**kwargs)

        self.setLayout(layout)
コード例 #3
0
class DepthProfileIgnoreElements(QtWidgets.QDialog):
    """
    Dialog for ignoring elements in a depth profile.
    """

    included_in_graph = bnd.bind("tree_elements")
    included_in_ratio = bnd.bind("tree_ratio")

    @property
    def ignored_from_graph(self):
        try:
            return self._get_ignored(set(self.included_in_graph))
        except AttributeError:
            return set()

    @property
    def ignored_from_ratio(self):
        try:
            return self._get_ignored(set(self.included_in_ratio))
        except AttributeError:
            return set()

    def _get_ignored(self, included):
        return {elem for elem in self._elements if elem not in included}

    def __init__(self, elements: List[Element], ignored_graph: Set[Element],
                 ignored_ratio: Set[Element]):
        """Init the dialog.
        
        Args:
            elements: A list of elements in Depth Profile.
            ignored_graph: A list of elements ignored previously for the graph.
            ignored_ratio: A list of elements ignored previously for ratio
                calculation.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_depth_profile_ignored.ui", self)

        self._elements = sorted(set(elements))
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)

        # Fill the trees
        gutils.fill_tree(self.tree_elements.invisibleRootItem(),
                         self._elements)
        gutils.fill_tree(self.tree_ratio.invisibleRootItem(), self._elements)

        self.included_in_graph = set(elem for elem in self._elements
                                     if elem not in ignored_graph)
        self.included_in_ratio = set(elem for elem in self._elements
                                     if elem not in ignored_ratio)
コード例 #4
0
class OptimizationParameterWidget(QtWidgets.QWidget,
                                  PropertyBindingWidget,
                                  abc.ABC,
                                  metaclass=QtABCMeta):
    """Abstract base class for recoil and fluence optimization parameter
    widgets.
    """
    # Common properties
    gen = bnd.bind("generationSpinBox")
    pop_size = bnd.bind("populationSpinBox")
    number_of_processes = bnd.bind("processesSpinBox")
    cross_p = bnd.bind("crossoverProbDoubleSpinBox")
    mut_p = bnd.bind("mutationProbDoubleSpinBox")
    stop_percent = bnd.bind("percentDoubleSpinBox")
    check_time = bnd.bind("timeSpinBox")
    check_max = bnd.bind("maxTimeEdit")
    check_min = bnd.bind("minTimeEdit")
    skip_simulation = bnd.bind("skip_sim_chk_box")

    @abc.abstractmethod
    def optimization_type(self) -> OptimizationType:
        pass

    def __init__(self, ui_file, **kwargs):
        """Initializes a optimization parameter widget.

        Args:
            ui_file: relative path to a ui_file
            kwargs: values to show in the widget
        """
        super().__init__()
        uic.loadUi(ui_file, self)

        locale = QLocale.c()
        self.crossoverProbDoubleSpinBox.setLocale(locale)
        self.mutationProbDoubleSpinBox.setLocale(locale)
        self.percentDoubleSpinBox.setLocale(locale)

        self.skip_sim_chk_box.stateChanged.connect(self.enable_sim_params)
        self.set_properties(**kwargs)

    def enable_sim_params(self, *_):
        """Either enables or disables simulation parameters depending on the
        value of skip_simulation parameter.

        Args:
            *_: not used
        """
        self.simGroupBox.setEnabled(not self.skip_simulation)
コード例 #5
0
class TofeGraphSettingsWidget(QtWidgets.QDialog):
    """Graph settings dialog for the ToF-E histogram graph.
    """
    color_scheme = bnd.bind("colorbox")

    def __init__(self, parent):
        """Inits ToF-E graph histogram graph settings dialog.
        
        Args:
            parent: MatplotlibHistogramWidget which settings are being changed.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_tofe_graph_settings.ui", self)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.parent = parent

        gutils.set_min_max_handlers(
            self.spin_range_x_min, self.spin_range_x_max
        )
        gutils.set_min_max_handlers(
            self.spin_range_y_min, self.spin_range_y_max
        )

        self.parent.show_yourself(self)

        # Connect and show
        self.OKButton.clicked.connect(self.accept_settings)
        self.cancelButton.clicked.connect(self.close)

        self.exec_()

    def accept_settings(self):
        """Accept changed settings and save them.
        """
        self.parent.compression_x = self.bin_x.value()
        self.parent.compression_y = self.bin_y.value()
        self.parent.invert_X = \
            self.invert_x.checkState() == QtCore.Qt.Checked
        self.parent.invert_Y = \
            self.invert_y.checkState() == QtCore.Qt.Checked
        self.parent.show_axis_ticks = \
            self.axes_ticks.checkState() == QtCore.Qt.Checked
        self.parent.transpose_axes = \
            self.transposeAxesCheckBox.checkState() == QtCore.Qt.Checked
        self.parent.color_scheme = self.color_scheme
        if self.radio_range_auto.isChecked():
            self.parent.axes_range_mode = 0
        elif self.radio_range_manual.isChecked():
            self.parent.axes_range_mode = 1
        x_range_min = self.spin_range_x_min.value()
        x_range_max = self.spin_range_x_max.value()
        y_range_min = self.spin_range_y_min.value()
        y_range_max = self.spin_range_y_max.value()

        self.parent.axes_range = [(x_range_min, x_range_max),
                                  (y_range_min, y_range_max)]
        self.parent.on_draw()
        self.close()
コード例 #6
0
class FoilWidget(QtWidgets.QWidget):
    """Class for creating a foil widget for detector settings.
    """
    foil_deletion = pyqtSignal(QtWidgets.QWidget)

    # Distance in nanometers from previous foil or Target if this is the first
    # foil
    distance_from_previous = bnd.bind("distanceDoubleSpinBox")
    # Distance from Target in nanometers
    cumulative_distance = bnd.bind("distanceLabel")

    name = bnd.bind("foilButton")

    def __init__(self, foil):
        """
        Initializes the foil widget.

        Args:
            foil: foil object
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_foil_widget.ui", self)

        locale = QLocale.c()
        self.distanceDoubleSpinBox.setLocale(locale)
        self.deleteButton.clicked.connect(self._delete_foil)

        self.name = foil.name
        self.distance_from_previous = 0
        self.cumulative_distance = foil.distance

    def _delete_foil(self):
        """
        Delete a foil.
        """
        confirm_box = QtWidgets.QMessageBox()
        confirm_box.setIcon(QtWidgets.QMessageBox.Warning)
        yes_button = confirm_box.addButton(QtWidgets.QMessageBox.Yes)
        confirm_box.addButton(QtWidgets.QMessageBox.Cancel)
        confirm_box.setText("Are you sure you want to delete the foil?")
        confirm_box.setWindowTitle("Confirm")

        confirm_box.exec()

        if confirm_box.clickedButton() == yes_button:
            self.foil_deletion.emit(self)
コード例 #7
0
ファイル: test_binding.py プロジェクト: aleekaup2/potku
        class Foo(bnd.PropertyTrackingWidget):
            foo = bnd.bind("_foo", getattr, setattr, track_change=True)

            def __init__(self):
                self.foo = 1

            def get_original_property_values(self):
                return {"_foo": 2}
コード例 #8
0
ファイル: test_binding.py プロジェクト: aleekaup2/potku
        class Foo(bnd.PropertyTrackingWidget):
            bar = bnd.bind("_bar", getattr, setattr, track_change=True)

            def __init__(self):
                self._bar = None
                self._orig = {}

            def get_original_property_values(self):
                return self._orig
コード例 #9
0
ファイル: test_binding.py プロジェクト: aleekaup2/potku
        class Foo(bnd.PropertyTrackingWidget):
            @property
            def foo(self):
                return self._foo

            @foo.setter
            def foo(self, value):
                self._foo = value

            bar = bnd.bind("_bar", getattr, setattr, track_change=True)
            baz = bnd.bind("_baz", getattr, setattr, track_change=False)

            def __init__(self):
                self.orig_props = {}
                self.set_properties(foo=1, bar=2, baz=3)

            def get_original_property_values(self):
                return self.orig_props
コード例 #10
0
class OptimizationRecoilParameterWidget(OptimizationParameterWidget):
    """
    Class that handles the recoil optimization parameters' ui.
    """

    # Recoil specific properties
    upper_limits = bnd.multi_bind(
        ("upperXDoubleSpinBox", "upperYDoubleSpinBox"))
    lower_limits = bnd.multi_bind(
        ("lowerXDoubleSpinBox", "lowerYDoubleSpinBox"))

    # sol_size values are unique (5, 7, 9 or 11) so they can be used in
    # two-way binding
    sol_size = bnd.bind("recoilTypeComboBox",
                        fget=sol_size_from_combobox,
                        fset=sol_size_to_combobox)
    recoil_type = bnd.bind("recoilTypeComboBox",
                           fget=recoil_from_combobox,
                           twoway=False)

    @property
    def optimization_type(self) -> OptimizationType:
        return OptimizationType.RECOIL

    def __init__(self, **kwargs):
        """Initialize the widget.

        Args:
            kwargs: property values to be shown in the widget.
        """
        ui_file = gutils.get_ui_dir() / "ui_optimization_recoil_params.ui"
        super().__init__(ui_file, **kwargs)
        self.radio_buttons()
        locale = QLocale.c()
        self.upperXDoubleSpinBox.setLocale(locale)
        self.lowerXDoubleSpinBox.setLocale(locale)
        self.upperYDoubleSpinBox.setLocale(locale)
        self.lowerYDoubleSpinBox.setLocale(locale)
コード例 #11
0
class DepthProfileDialog(QtWidgets.QDialog):
    """
    Dialog for making a depth profile.
    """
    # TODO replace these global variables with PropertySavingWidget
    checked_cuts = {}
    x_unit = DepthProfileUnit.ATOMS_PER_SQUARE_CM
    line_zero = False
    line_scale = False
    systerr = 0.0

    status_msg = bnd.bind("label_status")
    used_cuts = bnd.bind("treeWidget")
    cross_sections = bnd.bind("label_cross")
    tof_slope = bnd.bind("label_calibslope")
    tof_offset = bnd.bind("label_caliboffset")
    depth_stop = bnd.bind("label_depthstop")
    depth_steps = bnd.bind("label_depthnumber")
    depth_bin = bnd.bind("label_depthbin")
    depth_scale = bnd.bind("label_depthscale")
    used_efficiency_files = bnd.bind("label_efficiency_files")

    systematic_error = bnd.bind("spin_systerr")
    show_scale_line = bnd.bind("check_scaleline")
    show_zero_line = bnd.bind("check_0line")
    reference_density = bnd.bind("sbox_reference_density")
    x_axis_units = bnd.bind("group_x_axis_units")

    def __init__(self,
                 parent: BaseTab,
                 measurement: Measurement,
                 global_settings: GlobalSettings,
                 statusbar: Optional[QtWidgets.QStatusBar] = None):
        """Inits depth profile dialog.
        
        Args:
            parent: a MeasurementTabWidget.
            measurement: a Measurement object
            global_settings: a GlobalSettings object
            statusbar: a QStatusBar object
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_depth_profile_params.ui", self)

        self.parent = parent
        self.measurement = measurement
        self.statusbar = statusbar

        # Connect buttons
        self.OKButton.clicked.connect(self._accept_params)
        self.cancelButton.clicked.connect(self.close)

        locale = QLocale.c()
        self.spin_systerr.setLocale(locale)
        self.sbox_reference_density.setLocale(locale)

        m_name = self.measurement.name
        if m_name not in DepthProfileDialog.checked_cuts:
            DepthProfileDialog.checked_cuts[m_name] = set()

        gutils.fill_cuts_treewidget(self.measurement,
                                    self.treeWidget.invisibleRootItem(),
                                    use_elemloss=True)
        self.used_cuts = DepthProfileDialog.checked_cuts[m_name]

        gutils.set_btn_group_data(self.group_x_axis_units, DepthProfileUnit)
        self.x_axis_units = DepthProfileDialog.x_unit
        if self.x_axis_units == DepthProfileUnit.NM:
            self._show_reference_density()
        else:
            self._hide_reference_density()

        self.radioButtonNm.clicked.connect(self._show_reference_density)
        self.radioButtonAtPerCm2.clicked.connect(self._hide_reference_density)

        self.systematic_error = DepthProfileDialog.systerr
        self.show_scale_line = DepthProfileDialog.line_scale
        self.show_zero_line = DepthProfileDialog.line_zero

        self.cross_sections = global_settings.get_cross_sections()

        self._show_measurement_settings()
        self._show_efficiency_files()
        self.exec_()

    @gutils.disable_widget
    def _accept_params(self, *_):
        """Accept given parameters.

        Args:
            *_: unused event args
        """
        self.status_msg = ""
        sbh = StatusBarHandler(self.statusbar)
        sbh.reporter.report(10)

        try:
            output_dir = self.measurement.get_depth_profile_dir()

            # Get the filepaths of the selected items
            used_cuts = self.used_cuts
            DepthProfileDialog.checked_cuts[self.measurement.name] = set(
                used_cuts)
            # TODO could take care of RBS selection here
            elements = [
                Element.from_string(fp.name.split(".")[1]) for fp in used_cuts
            ]

            x_unit = self.x_axis_units

            DepthProfileDialog.x_unit = x_unit
            DepthProfileDialog.line_zero = self.show_zero_line
            DepthProfileDialog.line_scale = self.show_scale_line
            DepthProfileDialog.systerr = self.systematic_error

            sbh.reporter.report(20)

            # If items are selected, proceed to generating the depth profile
            if used_cuts:
                self.status_msg = "Please wait. Creating depth profile."
                if self.parent.depth_profile_widget:
                    self.parent.del_widget(self.parent.depth_profile_widget)

                # If reference density changed, update value to measurement
                if x_unit == DepthProfileUnit.NM:
                    _, _, _, profile, measurement = \
                        self.measurement.get_used_settings()
                    if profile.reference_density != self.reference_density:
                        profile.reference_density = self.reference_density
                        measurement.to_file()

                self.parent.depth_profile_widget = DepthProfileWidget(
                    self.parent,
                    output_dir,
                    used_cuts,
                    elements,
                    x_unit,
                    DepthProfileDialog.line_zero,
                    DepthProfileDialog.line_scale,
                    DepthProfileDialog.systerr,
                    progress=sbh.reporter.get_sub_reporter(
                        lambda x: 30 + 0.6 * x))

                sbh.reporter.report(90)

                icon = self.parent.icon_manager.get_icon(
                    "depth_profile_icon_2_16.png")
                self.parent.add_widget(self.parent.depth_profile_widget,
                                       icon=icon)
                self.close()
            else:
                self.status_msg = "Please select .cut file[s] to create " \
                                  "depth profiles."
        except Exception as e:
            error_log = f"Exception occurred when trying to create depth " \
                        f"profiles: {e}"
            logging.getLogger(self.measurement.name).error(error_log)
        finally:
            sbh.reporter.report(100)

    def _show_reference_density(self):
        """
        Add a filed for modifying the reference density.
        """
        self.label_reference_density.setVisible(True)
        self.sbox_reference_density.setVisible(True)

    def _hide_reference_density(self):
        """
        Remove reference density form dialog if it is there.
        """
        self.label_reference_density.setVisible(False)
        self.sbox_reference_density.setVisible(False)

    def _show_efficiency_files(self):
        """Update efficiency files to UI which are used.
        """
        detector, *_ = self.measurement.get_used_settings()
        self.used_efficiency_files = df.get_efficiency_text(
            self.treeWidget, detector)

    def _show_measurement_settings(self):
        """Show some important setting values in the depth profile parameter
        dialog for the user.
        """
        detector, _, _, profile, _ = self.measurement.get_used_settings()

        self.tof_slope = detector.tof_slope
        self.tof_offset = detector.tof_offset
        self.depth_stop = profile.depth_step_for_stopping
        self.depth_steps = profile.number_of_depth_steps
        self.depth_bin = profile.depth_step_for_output
        self.depth_scale = f"{profile.depth_for_concentration_from} - " \
                           f"{profile.depth_for_concentration_to}"
        self.reference_density = profile.reference_density
コード例 #12
0
ファイル: settings.py プロジェクト: aleekaup2/potku
class SimulationSettingsWidget(QtWidgets.QWidget,
                               PropertyTrackingWidget,
                               PropertySavingWidget,
                               metaclass=QtABCMeta):
    """Class for creating a simulation settings tab.
    """
    # TODO name, desc should perhaps not be tracked
    name = bnd.bind("nameLineEdit", track_change=True)
    description = bnd.bind("descriptionPlainTextEdit", track_change=True)
    simulation_type = bnd.bind("typeOfSimulationComboBox", track_change=True)
    simulation_mode = bnd.bind("modeComboBox", track_change=True)
    number_of_ions = bnd.bind("numberOfIonsSpinBox")
    number_of_ions_in_presimu = bnd.bind("numberOfPreIonsSpinBox")
    number_of_scaling_ions = bnd.bind("numberOfScalingIonsSpinBox",
                                      track_change=True)
    number_of_recoils = bnd.bind("numberOfRecoilsSpinBox", track_change=True)
    minimum_scattering_angle = bnd.bind("minimumScatterAngleDoubleSpinBox",
                                        track_change=True)
    minimum_main_scattering_angle = bnd.bind(
        "minimumMainScatterAngleDoubleSpinBox", track_change=True)
    minimum_energy_of_ions = bnd.bind("minimumEnergyDoubleSpinBox",
                                      track_change=True)

    # Seed and modification time are not tracked for changes
    seed_number = bnd.bind("seedSpinBox")
    modification_time = bnd.bind("dateLabel",
                                 fget=bnd.unix_time_from_label,
                                 fset=bnd.unix_time_to_label)

    settings_updated = pyqtSignal()

    def __init__(self,
                 element_simulation: ElementSimulation,
                 preset_folder=None):
        """
        Initializes the widget.

        Args:
            element_simulation: Element simulation object.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_request_simulation_settings.ui",
                   self)

        # By default, disable the widget, so caller has to enable it. Without
        # this, name and description fields would always be enabled when the
        # widget loads.
        self.setEnabled(False)
        self.element_simulation = element_simulation
        self.set_spinbox_maximums()
        gutils.fill_combobox(self.modeComboBox, SimulationMode)
        gutils.fill_combobox(self.typeOfSimulationComboBox, SimulationType)

        self.fields_are_valid = False
        iv.set_input_field_red(self.nameLineEdit)
        self.nameLineEdit.textChanged.connect(
            lambda: iv.check_text(self.nameLineEdit, qwidget=self))
        self.nameLineEdit.textEdited.connect(
            lambda: iv.sanitize_file_name(self.nameLineEdit))
        self.nameLineEdit.setEnabled(False)

        locale = QLocale.c()
        self.minimumScatterAngleDoubleSpinBox.setLocale(locale)
        self.minimumMainScatterAngleDoubleSpinBox.setLocale(locale)
        self.minimumEnergyDoubleSpinBox.setLocale(locale)

        self.__original_property_values = {}

        self.set_properties(
            name=self.element_simulation.name,
            description=self.element_simulation.description,
            modification_time=self.element_simulation.modification_time,
            **self.element_simulation.get_settings())

        if preset_folder is not None:
            self.preset_widget = PresetWidget.add_preset_widget(
                preset_folder / "simulation",
                "sim",
                lambda w: self.layout().insertWidget(0, w),
                save_callback=self.save_properties_to_file,
                load_callback=self.load_properties_from_file)
        else:
            self.preset_widget = None

    def get_property_file_path(self) -> Path:
        raise NotImplementedError

    def save_on_close(self) -> bool:
        return False

    def save_properties_to_file(self, file_path: Path):
        def err_func(err: Exception):
            if self.preset_widget is not None:
                self.preset_widget.set_status_msg(
                    f"Failed to save preset: {err}")

        self._save_json_file(file_path,
                             self.get_properties(),
                             True,
                             error_func=err_func)
        if self.preset_widget is not None:
            self.preset_widget.load_files(selected=file_path)

    def load_properties_from_file(self, file_path: Path):
        # TODO create a base class for settings widgets to get rid of this
        #   copy-paste code

        def err_func(err: Exception):
            if self.preset_widget is not None:
                self.preset_widget.set_status_msg(
                    f"Failed to load preset: {err}")

        bnd.PropertySavingWidget.load_properties_from_file(self,
                                                           file_path,
                                                           error_func=err_func)

    def get_original_property_values(self):
        """Returns a dictionary of original property values.
        """
        return self.__original_property_values

    def setEnabled(self, b):
        """Either enables or disables widgets input fields.
        """
        super().setEnabled(b)
        try:
            # setEnabled is called when ui file is being loaded and these
            # attributes do not yet exist, so we have to catch the exception.
            self.formLayout.setEnabled(b)
            self.generalParametersGroupBox.setEnabled(b)
            self.physicalParametersGroupBox.setEnabled(b)
        except AttributeError:
            pass

    def set_spinbox_maximums(self,
                             int_max=2147483647,
                             float_max=1000000000000000013287555072.00):
        """Set maximum values to spinbox components.
        """
        self.numberOfIonsSpinBox.setMaximum(int_max)
        self.numberOfPreIonsSpinBox.setMaximum(int_max)
        self.seedSpinBox.setMaximum(int_max)
        self.numberOfRecoilsSpinBox.setMaximum(int_max)
        self.numberOfScalingIonsSpinBox.setMaximum(int_max)
        self.minimumScatterAngleDoubleSpinBox.setMaximum(float_max)
        self.minimumMainScatterAngleDoubleSpinBox.setMaximum(float_max)
        self.minimumEnergyDoubleSpinBox.setMaximum(float_max)

    def update_settings(self):
        """
        Update simulation settings.
        """
        params = self.get_properties()
        self.element_simulation.name = params.pop("name")
        self.element_simulation.description = params.pop("description")
        params.pop("modification_time")

        if self.simulation_type != self.element_simulation.simulation_type:
            if self.simulation_type == SimulationType.ERD:
                new_type = "rec"
                old_type = ".sct"
            else:
                new_type = "sct"
                old_type = ".rec"
            for recoil in self.element_simulation.recoil_elements:
                recoil.type = new_type
                try:
                    path_to_rec = Path(self.element_simulation.directory,
                                       f"{recoil.get_full_name()}{old_type}")
                    os.remove(path_to_rec)
                except OSError:
                    pass
                recoil.to_file(self.element_simulation.directory)

        self.element_simulation.set_settings(**params)
        self.settings_updated.emit()
コード例 #13
0
class SimulationControlsWidget(QtWidgets.QWidget, GUIObserver):
    """Class for creating simulation controls widget for the element simulation.
    """
    recoil_name = bnd.bind("controls_group_box")
    process_count = bnd.bind("processes_spinbox")
    finished_processes = bnd.bind("finished_processes_label",
                                  fget=_process_count_from_label,
                                  fset=_process_count_to_label)
    observed_atoms = bnd.bind("observed_atom_count_label")
    simulation_state = bnd.bind("state_label")
    mcerd_error = bnd.bind("mcerd_error_lbl")
    ion_settings_error = bnd.bind("ion_settings_label")

    # TODO these styles could use some brush up...
    PRESIM_PROGRESS_STYLE = """
        QProgressBar::chunk:horizontal {
            background: #b8112a;
        }
    """
    SIM_PROGRESS_STYLE = """
        QProgressBar::chunk:horizontal {
            background: #0ec95c;
        }
    """

    def __init__(self,
                 element_simulation: ElementSimulation,
                 recoil_dist_widget,
                 recoil_name_changed=None,
                 settings_updated=None,
                 ion_division=IonDivision.BOTH,
                 min_presim_ions=0,
                 min_sim_ions=0):
        """
        Initializes a SimulationControlsWidget.

        Args:
             element_simulation: An ElementSimulation class object.
             recoil_dist_widget: RecoilAtomDistributionWidget.
             recoil_name_changed: signal that indicates that a recoil name
                has changed.
            ion_division: ion division mode
        """
        super().__init__()
        GUIObserver.__init__(self)
        uic.loadUi(gutils.get_ui_dir() / "ui_simulation_controls.ui", self)

        self.element_simulation = element_simulation
        self.element_simulation.subscribe(self)
        self.recoil_dist_widget = recoil_dist_widget
        self.progress_bars = {}

        self.recoil_name = \
            self.element_simulation.get_main_recoil().get_full_name()
        self.show_status(self.element_simulation.get_current_status())
        self.finished_processes = 0, self.process_count

        self.run_button.clicked.connect(self.start_simulation)
        self.run_button.setIcon(icons.get_reinhardt_icon("player_play.svg"))
        self.stop_button.clicked.connect(self.stop_simulation)
        self.stop_button.setIcon(icons.get_reinhardt_icon("player_stop.svg"))
        self.enable_buttons()

        self.mcerd_error_lbl.hide()
        self.ion_settings_label.hide()

        self.__unsub = None

        self._ion_division = ion_division
        self._min_presim_ions = min_presim_ions
        self._min_sim_ions = min_sim_ions
        self.show_ion_settings_label()

        self.processes_spinbox.valueChanged.connect(
            self.show_ion_settings_label)
        self._recoil_name_changed = recoil_name_changed
        if self._recoil_name_changed is not None:
            self._recoil_name_changed.connect(self._set_name)

        self._settings_updated = settings_updated
        if self._settings_updated is not None:
            self._settings_updated.connect(self.settings_update_handler)
            self._settings_updated[GlobalSettings].connect(
                self.settings_update_handler)

    def closeEvent(self, event):
        """Disconnects self from recoil_name_changed signal and closes the
        widget.
        """
        # FIXME not being called. This is a problem as there may be memory
        #  leaks.
        try:
            self._recoil_name_changed.disconnect(self._set_name)
        except (AttributeError, TypeError):
            pass
        try:
            self.settings_updated.disconnect(self.settings_update_handler)
        except (AttributeError, TypeError):
            pass
        super().closeEvent(event)

    def _set_name(self, _, recoil_elem):
        """Sets the name shown in group box title to the name of the
        given recoil element if the recoil element is the same as the
        main recoil.
        """
        if recoil_elem is self.element_simulation.get_main_recoil():
            self.recoil_name = recoil_elem.get_full_name()

    def enable_buttons(self, starting=False):
        """Switches the states of run and stop button depending on the state
        of the ElementSimulation object.
        """
        if self.element_simulation.is_optimization_running():
            start_enabled, stop_enabled = False, False
        else:
            start_enabled = not (
                self.element_simulation.is_simulation_running() and starting)
            stop_enabled = not (
                start_enabled
                or self.element_simulation.is_optimization_running())
        self.run_button.setEnabled(start_enabled)
        self.stop_button.setEnabled(stop_enabled)
        self.processes_spinbox.setEnabled(start_enabled)

    def start_simulation(self):
        """Calls ElementSimulation's start method.
        """
        # Ask the user if they want to write old simulation results over (if
        # they exist), or continue
        status = self.element_simulation.get_current_status()

        if status[ElementSimulation.STATE] == SimulationState.DONE:
            reply = QtWidgets.QMessageBox.question(
                self, "Confirmation",
                "Do you want to continue this simulation?\n\n"
                "If you do, old simulation results will be preserved.\n"
                "Otherwise they will be deleted.", QtWidgets.QMessageBox.Yes
                | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel,
                QtWidgets.QMessageBox.Cancel)
            if reply == QtWidgets.QMessageBox.Cancel:
                return  # If clicked Cancel don't start simulation
            elif reply == QtWidgets.QMessageBox.No:
                use_old_erd_files = False
            else:
                use_old_erd_files = True
        elif status[ElementSimulation.STATE] == SimulationState.NOTRUN:
            use_old_erd_files = False
        else:
            self.mcerd_error = "Simulation currently running. Cannot start a " \
                               "new one."
            self.mcerd_error_lbl.show()
            return

        self.mcerd_error_lbl.hide()

        # Lock full edit
        self.element_simulation.lock_edit()
        if self.recoil_dist_widget.current_element_simulation is \
           self.element_simulation:
            self.recoil_dist_widget.full_edit_on = False
            self.recoil_dist_widget.update_plot()

        self.finished_processes = 0, self.process_count
        self.remove_progress_bars()

        observable = self.element_simulation.start(
            self.process_count,
            use_old_erd_files=use_old_erd_files,
            ion_division=self._ion_division)
        if observable is not None:
            self.__unsub = observable.pipe(
                ops.scan(lambda acc, x: {
                    **x, "started":
                    x[MCERD.IS_RUNNING] and not acc["started"]
                },
                         seed={"started": False})).subscribe(self)
        else:
            self.mcerd_error = "Could not start simulation. Check that there " \
                               "is no other simulation running for this " \
                               "recoil element"
            self.mcerd_error_lbl.show()

    def show_status(self, status):
        """Updates the status of simulation in the GUI

        Args:
            status: status of the ElementSimulation object
        """
        self.observed_atoms = status[ElementSimulation.ATOMS]
        self.simulation_state = status[ElementSimulation.STATE]

    def settings_update_handler(self,
                                settings: Optional[GlobalSettings] = None):
        """Updates SimulationControlsWidget when a setting has been updated.
        """
        if settings is not None:
            self._ion_division = settings.get_ion_division()
            self._min_presim_ions = settings.get_min_presim_ions()
            self._min_sim_ions = settings.get_min_simulation_ions()
        self.show_ion_settings_label()

    def show_ion_settings_label(self):
        """Shows a warning label if ion counts are below the user defined
        treshold.
        """
        settings, _, _ = self.element_simulation.get_mcerd_params()
        presim_ions, sim_ions = self._ion_division.get_ion_counts(
            settings["number_of_ions_in_presimu"], settings["number_of_ions"],
            self.process_count)
        txt = ""
        if self._min_presim_ions > presim_ions:
            txt += "Not enough pre-simulation ions. "
        if self._min_sim_ions > sim_ions:
            txt += "Not enough simulation ions."
        if txt:
            self.ion_settings_error = txt
            self.ion_settings_label.show()
        else:
            self.ion_settings_label.hide()

    def stop_simulation(self):
        """ Calls ElementSimulation's stop method.
        """
        self.element_simulation.stop()

    def on_next_handler(self, status):
        """Callback function that receives status from an
        ElementSimulation

        Args:
            status: status update sent by ElementSimulation or observable stream
        """
        if status[MCERD.MSG] == MCERD.PRESIM_FINISHED:
            style = SimulationControlsWidget.SIM_PROGRESS_STYLE
        else:
            style = None
        self.update_progress_bar(status[MCERD.SEED],
                                 status[MCERD.PERCENTAGE],
                                 stylesheet=style)

        self.finished_processes = (status[ElementSimulation.FINISHED],
                                   status[ElementSimulation.TOTAL])

        if status["started"]:
            self.enable_buttons(starting=True)

        self.show_status(status)

    def on_error_handler(self, err):
        """Called when observable (either ElementSimulation or the rx stream
        reports an error.
        """
        self.mcerd_error = err
        self.mcerd_error_lbl.show()

        self.enable_buttons()
        self.show_status(self.element_simulation.get_current_status())
        if self.__unsub is not None:
            self.__unsub.dispose()

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(object)
    def on_completed_handler(self, status=None):
        """This method is called when the ElementSimulation has run all of
        its simulation processes.

        GUI is updated to show the status and button states are switched
        accordingly.
        """
        if status is None:
            self.show_status(self.element_simulation.get_current_status())
            if self.__unsub is not None:
                self.__unsub.dispose()
        else:
            self.show_status(status)
        for process_bar in self.progress_bars.values():
            # TODO this only affects the number, adjust the color too
            process_bar.setEnabled(False)
        self.enable_buttons()

    def remove_progress_bars(self):
        """Removes all progress bars and seed labels.
        """
        self.progress_bars = {}
        for i in reversed(range(self.process_layout.count())):
            self.process_layout.itemAt(i).widget().deleteLater()

    def update_progress_bar(self, seed: int, value: int, stylesheet=None):
        """Updates or adds a progress bar for a simulation process that uses
        the given seed.

        Args:
            seed: seed of the simulation process
            value: value to be shown in the progress bar.
            stylesheet: stylesheet given to to the progress bar.
        """
        if seed not in self.progress_bars:
            if stylesheet is None:
                stylesheet = SimulationControlsWidget.PRESIM_PROGRESS_STYLE
            progress_bar = QtWidgets.QProgressBar()
            progress_bar.setStyleSheet(stylesheet)

            # Align the percentage display to the right side of the
            # progress bar.
            progress_bar.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

            # Make sure it fills the horizontal space by setting size policy
            # to expanding.
            progress_bar.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                       QtWidgets.QSizePolicy.Fixed)

            self.progress_bars[seed] = progress_bar
            self.process_layout.addRow(QtWidgets.QLabel(str(seed)),
                                       progress_bar)
        else:
            progress_bar = self.progress_bars[seed]
            if stylesheet is not None:
                progress_bar.setStyleSheet(stylesheet)
        progress_bar.setValue(value)
コード例 #14
0
class SimulationSettingsDialog(QtWidgets.QDialog):
    """
    Dialog class for handling the simulation parameter input.
    """
    use_request_settings = bnd.bind("defaultSettingsCheckBox")

    def __init__(self, tab, simulation: Simulation, icon_manager):
        """
        Initializes the dialog.

        Args:
            tab: A SimulationTabWidget.
            simulation: A Simulation object whose parameters are handled.
            icon_manager: An icon manager.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_specific_settings.ui", self)

        self.tab = tab
        self.simulation = simulation
        self.icon_manager = icon_manager

        self.setWindowTitle("Simulation Settings")
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        screen_geometry = QtWidgets.QDesktopWidget.availableGeometry(
            QtWidgets.QApplication.desktop())
        self.resize(int(self.geometry().width() * 1.2),
                    int(screen_geometry.size().height() * 0.8))
        self.defaultSettingsCheckBox.stateChanged.connect(
            self._change_used_settings)
        self.OKButton.clicked.connect(self._save_settings_and_close)
        self.applyButton.clicked.connect(self._update_parameters)
        self.cancelButton.clicked.connect(self.close)

        preset_folder = gutils.get_preset_dir(
            self.simulation.request.global_settings)
        # Add measurement settings view to the settings view
        self.measurement_settings_widget = MeasurementSettingsWidget(
            self.simulation, preset_folder=preset_folder)
        self.tabs.addTab(self.measurement_settings_widget, "Measurement")

        self.measurement_settings_widget.beam_selection_ok.connect(
            lambda b: self.OKButton.setEnabled(b))

        # Add detector settings view to the settings view
        detector_object = self.simulation.detector
        if detector_object is None:
            detector_object = self.simulation.request.default_detector

        self.detector_settings_widget = DetectorSettingsWidget(
            detector_object, self.simulation.request, self.icon_manager)

        # 2 is calibration tab that is not needed
        calib_tab_widget = self.detector_settings_widget.tabs.widget(2)
        self.detector_settings_widget.tabs.removeTab(2)
        calib_tab_widget.deleteLater()

        self.tabs.addTab(self.detector_settings_widget, "Detector")

        self.use_request_settings = self.simulation.use_request_settings

        # TODO
        # self.measurement_settings_widget.nameLineEdit.setText(
        #     self.simulation.measurement_setting_file_name)
        # self.measurement_settings_widget.descriptionPlainTextEdit \
        #     .setPlainText(
        #         self.simulation.measurement_setting_file_description)
        # self.measurement_settings_widget.dateLabel.setText(time.strftime(
        #     "%c %z %Z", time.localtime(self.simulation.modification_time)))

        self.tabs.currentChanged.connect(lambda: df.check_for_red(self))

        self.exec()

    def _change_used_settings(self):
        """Set specific settings enabled or disabled based on the "Use
        request settings" checkbox.
        """
        check_box = self.sender()
        if check_box.isChecked():
            self.tabs.setEnabled(False)
        else:
            self.tabs.setEnabled(True)

    def _remove_extra_files(self):
        gf.remove_matching_files(self.simulation.directory,
                                 exts={".measurement", ".target"})
        gf.remove_matching_files(self.simulation.directory / "Detector",
                                 exts={".detector"})

    def use_request_settings_toggled(self) -> bool:
        """Check if "use request settings" has been toggled."""
        return self.use_request_settings != self.simulation.use_request_settings

    def values_changed(self) -> bool:
        """Check if measurement or detector settings have changed."""
        if self.measurement_settings_widget.are_values_changed():
            return True
        if self.detector_settings_widget.values_changed():
            return True
        return False

    def _update_parameters(self):
        """
         Update Simulation's Run, Detector and Target objects. If simulation
         specific parameters are in use, save them into a file.
        """
        if self.measurement_settings_widget.isotopeComboBox.currentIndex() \
                == -1:
            QtWidgets.QMessageBox.critical(
                self, "Warning", "No isotope selected.\n\n"
                "Please select an isotope for the beam element.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return False

        if not self.simulation.measurement_setting_file_name:
            self.simulation.measurement_setting_file_name = \
                self.simulation.name

        if not self.tabs.currentWidget().fields_are_valid:
            QtWidgets.QMessageBox.critical(
                self, "Warning",
                "Some of the setting values have not been set.\n"
                "Please input values in fields indicated in red.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return False

        if self.use_request_settings_toggled() \
                or (not self.use_request_settings and self.values_changed()):
            # User has switched from simulation settings to request settings,
            # or vice versa. Confirm if the user wants to delete old simulations
            # OR
            # If values that require rerunning simulations, prompt user
            # delete previous or currently running simulation results (if those
            # exists)
            if not df.delete_element_simulations(
                    self, self.simulation, tab=self.tab,
                    msg="simulation settings"):
                return False

        # Copy request settings without checking their validity. They
        # have been checked once in request settings anyway.
        if self.use_request_settings:
            self.simulation.use_request_settings = True

            # Remove simulation-specific efficiency files
            if self.simulation.detector is not \
                    self.simulation.request.default_detector:
                self.simulation.detector.remove_efficiency_files()

            self.simulation.clone_request_settings()

            self._remove_extra_files()
            self.simulation.to_file()
            return True

        try:
            # Update simulation settings
            self.simulation.use_request_settings = False

            # Set Detector object to settings widget
            self.detector_settings_widget.obj = self.simulation.detector

            # Update settings
            self.measurement_settings_widget.update_settings()
            self.detector_settings_widget.update_settings()

            self._remove_extra_files()
            self.simulation.to_file()
            return True

        except TypeError:
            QtWidgets.QMessageBox.question(
                self, "Warning",
                "Some of the setting values have not been set.\n"
                "Please input setting values to save them.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)

        return False

    def _save_settings_and_close(self):
        """Saves settings and closes the dialog if __update_parameters returns
        True.
        """
        if self._update_parameters():
            self.tab.check_default_settings_clicked()
            self.close()
コード例 #15
0
ファイル: preset_widget.py プロジェクト: aleekaup2/potku
class PresetWidget(QWidget,
                   bnd.PropertyBindingWidget,
                   metaclass=gutils.QtABCMeta):

    PRESET_SUFFIX = ".preset"
    MAX_COUNT = 10
    NONE_TEXT = "<None>"
    preset = bnd.bind("preset_combobox")
    status_msg = bnd.bind("status_label")

    save_file = pyqtSignal(Path)
    load_file = pyqtSignal(Path)

    def __init__(self, folder: Path, prefix: str, enable_load_btn=False):
        """Initializes a new PresetWidget.

        Args:
            folder: folder where preset files are stored
            prefix: default prefix for preset files
            enable_load_btn: whether 'Load preset' file is shown or not.
                If not, load_file signal is fired when the combobox
                index changes.
        """
        QWidget.__init__(self)
        uic.loadUi(gutils.get_ui_dir() / "ui_preset_widget.ui", self)

        self._folder = folder
        self._prefix = prefix

        self.save_btn: QPushButton
        self.load_btn: QPushButton
        self.preset_combobox: QComboBox

        self.save_btn.clicked.connect(self._emit_save_file)

        self._load_btn_enabled = enable_load_btn
        self.load_btn.setVisible(self._load_btn_enabled)
        self.load_btn.setEnabled(self._load_btn_enabled)
        if self._load_btn_enabled:
            self.load_btn.clicked.connect(
                lambda: self._emit_file_to_load(self.preset))

        self.status_label.setVisible(False)

        self.preset_combobox: QComboBox
        self.preset_combobox.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.preset_combobox.currentIndexChanged.connect(self._index_changed)

        self._action_rename = QAction(self.preset_combobox)
        self._action_rename.setText("Rename")
        self._action_rename.triggered.connect(self._rename_file)

        self._action_remove = QAction(self.preset_combobox)
        self._action_remove.setText("Remove")
        self._action_remove.triggered.connect(self._remove_file)

        self.preset_combobox.addAction(self._action_rename)
        self.preset_combobox.addAction(self._action_remove)

        self.preset_combobox.installEventFilter(self)

        self.load_files()
        self._activate_actions(self.preset)

    def _index_changed(self):
        """Event handler for index changes in the combobox. Actions are
        disabled if the selected item is 'None'. If 'Load preset' button
        is not enabled, this will also emit a load_file signal.
        '"""
        self.set_status_msg("")
        preset = self.preset
        self._activate_actions(preset)
        if not self._load_btn_enabled:
            self._emit_file_to_load(preset)

    def _activate_actions(self, preset: Optional[Path]):
        """Activates or deactives actions depending on whether the given
        preset is None or not.
        """
        self._action_remove.setEnabled(preset is not None)
        self._action_rename.setEnabled(preset is not None)

    def _emit_file_to_load(self, preset: Optional[Path]):
        """Emits a load file signal if the given preset is not 'None'.
        """
        if preset is not None:
            self.load_file.emit(preset)

    def _emit_save_file(self):
        """Emits save_file signal if new file name can be generated.
        """
        next_file = self.get_next_available_file(
            starting_index=self.preset_combobox.count() - 1)
        if next_file is None:
            self.set_status_msg("Could not generate a name for preset.")
        else:
            self.save_file.emit(next_file)

    def load_files(self, max_count=MAX_COUNT, selected: Optional[Path] = None):
        """Loads preset files to combobox.

        Args:
            max_count: maximum number of files to load
            selected: path to a file that will be selected after loading
        """
        def text_func(preset_path: Optional[Path]):
            if preset_path is None:
                return PresetWidget.NONE_TEXT
            return preset_path.stem

        preset_files = PresetWidget.get_preset_files(self._folder,
                                                     max_count,
                                                     keep=selected)
        if not self._load_btn_enabled:
            # If load button is not used, add 'None' as the first element in
            # the combobox
            preset_files = [None, *preset_files]

        gutils.fill_combobox(self.preset_combobox,
                             preset_files,
                             text_func=text_func,
                             block_signals=True)
        self.preset = selected

    def _rename_file(self):
        """Activates edit if preset is selected.
        """
        self.preset_combobox: QComboBox
        self.preset_combobox.setEditable(self.preset is not None)

    def eventFilter(self, source: QObject, event: QEvent) -> bool:
        """Filters combobox events. If combobox is currently editable,
        capture FocusOut event and try to rename selected file.
        """
        self.preset_combobox: QComboBox
        if source is self.preset_combobox and isinstance(event, QFocusEvent):
            if self.preset_combobox.isEditable():
                cur_txt = self.preset_combobox.currentText()
                self.preset_combobox.setEditable(False)
                try:
                    cur_preset = self.preset
                    new_file = Path(self._folder,
                                    f"{cur_txt}{PresetWidget.PRESET_SUFFIX}")
                    if new_file != cur_preset and self.is_valid_preset(
                            self._folder, new_file):
                        cur_preset.rename(new_file)
                        self.load_files(selected=new_file)
                except OSError as e:
                    self.set_status_msg(f"Failed to rename preset: {e}")
        return super().eventFilter(source, event)

    def _remove_file(self):
        """Removes file with a confirmation.
        """
        file = self.preset
        if file is None:
            return
        reply = QMessageBox.question(self, "Delete preset",
                                     "Delete selected preset?",
                                     QMessageBox.Yes | QMessageBox.Cancel,
                                     QMessageBox.Yes)

        if reply == QMessageBox.Yes:
            try:
                file.unlink()
            except OSError as e:
                self.set_status_msg(f"Failed to remove preset: {e}")
            self.load_files()
            self._activate_actions(self.preset)

    def set_status_msg(self, msg: Any):
        """Sets the status message and shows or hides the label.
        """
        self.status_msg = msg
        self.status_label.setVisible(bool(msg))

    @classmethod
    def add_preset_widget(cls, folder: Path, prefix, add_func: Callable,
                          save_callback: Optional[Callable] = None,
                          load_callback: Optional[Callable] = None) -> \
            "PresetWidget":
        """Creates a PresetWidget, adds it to a parent widget by calling the
        add_func and connects save and load callbacks if they are provided.
        Returns the created widget.
        """
        widget = cls(folder, prefix)
        add_func(widget)
        if save_callback is not None:
            widget.save_file.connect(save_callback)
        if load_callback is not None:
            widget.load_file.connect(load_callback)
        return widget

    def get_next_available_file(self, starting_index=0, max_iterations=100) \
            -> Optional[Path]:
        """Returns the next available file name or None, if no available
        file name was found within maximum number of iterations.
        """
        def file_name_generator():
            for i in range(starting_index, max_iterations):
                fname = f"{self._prefix}-{i + 1:03}{PresetWidget.PRESET_SUFFIX}"
                yield self._folder / fname

        try:
            return fp.find_available_file_path(file_name_generator())
        except ValueError:
            return None

    @staticmethod
    def get_preset_files(folder: Path,
                         max_count: int = MAX_COUNT,
                         keep: Optional[Path] = None) -> List[Path]:
        """Returns a sorted list of .preset files from the given folder.

        Args:
            folder: folder path
            max_count: maximum number of files to return
            keep: file that is guaranteed to be in the list as long as it is
                valid .preset file
        """
        if PresetWidget.is_valid_preset(folder, keep) and keep.is_file():
            files = [keep]
        else:
            files = []
        try:
            with os.scandir(folder) as scdir:
                for entry in scdir:
                    if len(files) >= max_count:
                        break
                    path = Path(entry.path)
                    if PresetWidget.is_valid_preset(folder, path) and \
                            path != keep and path.is_file():
                        files.append(path)
        except OSError:
            pass
        return sorted(files)

    @staticmethod
    def is_valid_preset(folder: Path, file: Optional[Path]) -> bool:
        """Checks if the given file is a valid .preset file.
        """
        # TODO might want to add checks for characters that are forbidden
        #   on one platform but allowed on another to ensure the portability
        #   of presets.
        if file is None:
            return False
        if not file.stem:
            return False
        if file.suffix != PresetWidget.PRESET_SUFFIX:
            return False
        return file.resolve().parent == folder.resolve()
コード例 #16
0
ファイル: element_losses.py プロジェクト: aleekaup2/potku
class ElementLossesDialog(QtWidgets.QDialog):
    """Class to handle element losses dialogs.
    """
    checked_cuts = {}
    reference_cut = {}
    split_count = 10
    y_scale = 1

    status_msg = bnd.bind("label_status")
    used_reference_cut = bnd.bind("referenceCut")
    used_cuts = bnd.bind("targetCutTree")

    def __init__(self, parent, measurement: Measurement, statusbar:
                 Optional[QtWidgets.QStatusBar] = None):
        """Inits element losses class.
        
         Args:
            parent: A MeasurementTabWidget.
            
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_element_losses_params.ui", self)
        self.parent = parent
        self.measurement = measurement
        self.statusbar = statusbar
        self.cuts = []

        self.OKButton.clicked.connect(self.__accept_params)
        self.cancelButton.clicked.connect(self.close)
        # self.referenceCut.currentIndexChanged.connect(self.__load_targets)

        # TODO: Reads cut files twice. Requires Refactor.
        # Reference cuts
        m_name = self.measurement.name
        if m_name not in ElementLossesDialog.reference_cut:
            ElementLossesDialog.reference_cut[m_name] = None
        cuts, _ = self.measurement.get_cut_files()
        gutils.fill_combobox(
            self.referenceCut, cuts, text_func=lambda fp: fp.name)
        self.used_reference_cut = ElementLossesDialog.reference_cut[m_name]

        # Cuts and element losses
        if m_name not in ElementLossesDialog.checked_cuts:
            ElementLossesDialog.checked_cuts[m_name] = set()
        gutils.fill_cuts_treewidget(
            self.measurement, self.targetCutTree.invisibleRootItem(),
            use_elemloss=True)
        self.used_cuts = ElementLossesDialog.checked_cuts[m_name]

        self.partitionCount.setValue(ElementLossesDialog.split_count)
        self.radioButton_0max.setChecked(ElementLossesDialog.y_scale == 0)
        self.radioButton_minmax.setChecked(ElementLossesDialog.y_scale == 1)

        self.exec_()

    @gutils.disable_widget
    def __accept_params(self, *_):
        """Called when OK button is pressed. Creates a elementlosses widget and
        adds it to the parent (mdiArea).

        Args:
            *_: unused event args
        """
        self.status_msg = ""
        sbh = StatusBarHandler(self.statusbar)

        y_axis_0_scale = self.radioButton_0max.isChecked()
        reference_cut = self.used_reference_cut
        split_count = self.partitionCount.value()
        m_name = self.measurement.name
        used_cuts = self.used_cuts
        ElementLossesDialog.checked_cuts[m_name] = set(used_cuts)

        if y_axis_0_scale:
            y_scale = 0
        else:
            y_scale = 1

        ElementLossesDialog.reference_cut[m_name] = \
            self.referenceCut.currentText()
        ElementLossesDialog.split_count = split_count
        ElementLossesDialog.y_scale = y_scale

        sbh.reporter.report(25)

        if used_cuts:
            if self.parent.elemental_losses_widget:
                self.parent.del_widget(self.parent.elemental_losses_widget)

            self.parent.elemental_losses_widget = ElementLossesWidget(
                self.parent, self.measurement, reference_cut, used_cuts,
                split_count, y_scale, statusbar=self.statusbar,
                progress=sbh.reporter.get_sub_reporter(
                    lambda x: 25 + 0.70 * x
                ))
            icon = self.parent.icon_manager \
                .get_icon("elemental_losses_icon_16.png")
            self.parent.add_widget(self.parent.elemental_losses_widget,
                                   icon=icon)

            msg = f"Created Element Losses. Splits: {split_count} " \
                  f"Reference cut: {reference_cut} " \
                  f"List of cuts: {used_cuts}"
            self.measurement.log(msg)

            log_info = "Elemental Losses split counts:\n"

            split_counts = self.parent.elemental_losses_widget.split_counts
            splitinfo = "\n".join(
                ["{0}: {1}".format(
                    key, ", ".join(str(v) for v in split_counts[key]))
                    for key in split_counts])
            self.measurement.log(log_info + splitinfo)

            sbh.reporter.report(100)
            self.close()
        else:
            self.status_msg = "Please select .cut file[s] to create element " \
                              "losses."
        sbh.reporter.report(100)
コード例 #17
0
class OptimizationParameterWidget(QtWidgets.QWidget,
                                  PropertyBindingWidget,
                                  abc.ABC,
                                  metaclass=QtABCMeta):
    """Abstract base class for recoil and fluence optimization parameter
    widgets.
    """
    # Common properties
    gen = bnd.bind("generationSpinBox")
    pop_size = bnd.bind("populationSpinBox")
    number_of_processes = bnd.bind("processesSpinBox")
    cross_p = bnd.bind("crossoverProbDoubleSpinBox")
    mut_p = bnd.bind("mutationProbDoubleSpinBox")
    stop_percent = bnd.bind("percentDoubleSpinBox")
    check_time = bnd.bind("timeSpinBox")
    check_max = bnd.bind("maxTimeEdit")
    check_min = bnd.bind("minTimeEdit")
    skip_simulation = bnd.bind("skip_sim_chk_box")

    @abc.abstractmethod
    def optimization_type(self) -> OptimizationType:
        pass

    def __init__(self, ui_file, **kwargs):
        """Initializes a optimization parameter widget.

        Args:
            ui_file: relative path to a ui_file
            kwargs: values to show in the widget
        """
        super().__init__()
        uic.loadUi(ui_file, self)

        locale = QLocale.c()
        self.crossoverProbDoubleSpinBox.setLocale(locale)
        self.mutationProbDoubleSpinBox.setLocale(locale)
        self.percentDoubleSpinBox.setLocale(locale)

        self.skip_sim_chk_box.stateChanged.connect(self.enable_sim_params)
        self.set_properties(**kwargs)

    def enable_sim_params(self, *_):
        """Either enables or disables simulation parameters depending on the
        value of skip_simulation parameter.

        Args:
            *_: not used
        """
        self.simGroupBox.setEnabled(not self.skip_simulation)

    def radio_buttons(self):
        """Radio buttons for optimization parameters Sum and Area
        """
        self.optimize_by_area = True
        self.radios = QtWidgets.QButtonGroup(self)
        self.radios.buttonToggled[QtWidgets.QAbstractButton,
                                  bool].connect(self.isChecked)
        self.radios.addButton(self.areaRadioButton)
        self.radios.addButton(self.sumRadioButton)

    def isChecked(self, button, checked):
        """Choose whether to optimize by Sum or Area
        """
        if checked and button.text() == 'Sum':
            self.optimize_by_area = False
        else:
            self.optimize_by_area = True
コード例 #18
0
ファイル: settings.py プロジェクト: aleekaup2/potku
    def __init__(self, tab, measurement: Measurement, icon_manager):
        """
        Initializes the dialog.

        Args:
            measurement: A Measurement object whose parameters are handled.
            icon_manager: An icon manager.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_specific_settings.ui", self)
        self.warning_text = bnd.bind('warning_text')

        self.tab = tab
        self.measurement = measurement
        self.icon_manager = icon_manager
        self.setWindowTitle("Measurement Settings")
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        screen_geometry = QtWidgets.QDesktopWidget.availableGeometry(
            QtWidgets.QApplication.desktop())
        self.resize(int(self.geometry().width() * 1.2),
                    int(screen_geometry.size().height() * 0.8))
        self.defaultSettingsCheckBox.stateChanged.connect(
            self._change_used_settings)
        self.OKButton.clicked.connect(self._save_settings_and_close)
        self.applyButton.clicked.connect(self._update_parameters)
        self.cancelButton.clicked.connect(self.close)

        preset_folder = gutils.get_preset_dir(
            self.measurement.request.global_settings)
        # Add measurement settings view to the settings view
        self.measurement_settings_widget = MeasurementSettingsWidget(
            self.measurement, preset_folder=preset_folder)
        self.tabs.addTab(self.measurement_settings_widget, "Measurement")

        self.measurement_settings_widget.beam_selection_ok.connect(
            self.OKButton.setEnabled)

        # Add detector settings view to the settings view
        self.detector_settings_widget = DetectorSettingsWidget(
            self.measurement.detector, self.measurement.request,
            self.icon_manager, self.measurement_settings_widget.tmp_run)

        self.tabs.addTab(self.detector_settings_widget, "Detector")

        self.use_request_settings = self.measurement.use_request_settings

        # TODO these should be set in the widget, not here
        self.measurement_settings_widget.nameLineEdit.setText(
            self.measurement.measurement_setting_file_name)
        self.measurement_settings_widget.descriptionPlainTextEdit.setPlainText(
            self.measurement.measurement_setting_file_description)
        self.measurement_settings_widget.dateLabel.setText(
            time.strftime("%c %z %Z",
                          time.localtime(self.measurement.modification_time)))

        # Add profile settings view to the settings view
        self.profile_settings_widget = ProfileSettingsWidget(
            self.measurement, preset_folder=preset_folder)
        self.tabs.addTab(self.profile_settings_widget, "Profile")

        self.tabs.currentChanged.connect(lambda: df.check_for_red(self))

        self.exec()
コード例 #19
0
class DepthProfileDialog(QtWidgets.QDialog):
    """
    Dialog for making a depth profile.
    """
    # TODO replace these global variables with PropertySavingWidget
    checked_cuts = {}
    x_unit = DepthProfileUnit.ATOMS_PER_SQUARE_CM
    line_zero = False
    line_scale = False
    used_eff = False
    systerr = 0.0

    status_msg = bnd.bind("label_status")
    used_cuts = bnd.bind("treeWidget")
    cross_sections = bnd.bind("label_cross")
    tof_slope = bnd.bind("label_calibslope")
    tof_offset = bnd.bind("label_caliboffset")
    depth_stop = bnd.bind("label_depthstop")
    depth_steps = bnd.bind("label_depthnumber")
    depth_bin = bnd.bind("label_depthbin")
    depth_scale = bnd.bind("label_depthscale")
    used_efficiency_files = bnd.bind("label_efficiency_files")
    warning = bnd.bind("label_warning_text")

    systematic_error = bnd.bind("spin_systerr")
    show_scale_line = bnd.bind("check_scaleline")
    show_used_eff = bnd.bind("show_eff")
    show_zero_line = bnd.bind("check_0line")
    reference_density = bnd.bind("sbox_reference_density")
    x_axis_units = bnd.bind("group_x_axis_units")

    def __init__(self,
                 parent: BaseTab,
                 measurement: Measurement,
                 global_settings: GlobalSettings,
                 statusbar: Optional[QtWidgets.QStatusBar] = None):
        """Inits depth profile dialog.
        
        Args:
            parent: a MeasurementTabWidget.
            measurement: a Measurement object
            global_settings: a GlobalSettings object
            statusbar: a QStatusBar object
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_depth_profile_params.ui", self)

        # Basic stuff
        self.parent = parent
        self.measurement = measurement
        self.statusbar = statusbar

        # Connect buttons
        self.OKButton.clicked.connect(self._accept_params)
        self.cancelButton.clicked.connect(self.close)

        locale = QLocale.c()
        self.spin_systerr.setLocale(locale)
        self.sbox_reference_density.setLocale(locale)

        m_name = self.measurement.name
        if m_name not in DepthProfileDialog.checked_cuts:
            DepthProfileDialog.checked_cuts[m_name] = set()

        gutils.fill_cuts_treewidget(self.measurement,
                                    self.treeWidget.invisibleRootItem(),
                                    use_elemloss=True)
        self.used_cuts = DepthProfileDialog.checked_cuts[m_name]

        self._update_label()
        self.treeWidget.itemClicked.connect(self._update_label)

        gutils.set_btn_group_data(self.group_x_axis_units, DepthProfileUnit)
        self.x_axis_units = DepthProfileDialog.x_unit
        if self.x_axis_units == DepthProfileUnit.NM:
            self._show_reference_density()
        else:
            self._hide_reference_density()

        self.radioButtonNm.clicked.connect(self._show_reference_density)
        self.radioButtonAtPerCm2.clicked.connect(self._hide_reference_density)

        self.systematic_error = DepthProfileDialog.systerr

        # Checkboxes
        self.systematic_error = DepthProfileDialog.systerr
        self.show_scale_line = DepthProfileDialog.line_scale
        self.show_zero_line = DepthProfileDialog.line_zero
        self.show_used_eff = DepthProfileDialog.used_eff

        self.cross_sections = global_settings.get_cross_sections()

        self._show_measurement_settings()
        self._show_efficiency_files()
        # Does not work correctly if self is replaced with DepthProfileDialog
        self.eff_files_str = self.used_efficiency_files

        self.exec_()

    def _update_label(self):
        if len(self.used_cuts) <= 1:
            self.label_warning_text.setText('')
            return
        else:
            cuts = []
            for cut in self.used_cuts:
                cuts.append(cut.suffixes[0])

            indices = []
            for c in cuts:
                indices.append(
                    [i for i, sublist in enumerate(cuts) if sublist == c])

            indices = [
                list(sublist) for sublist in set(
                    tuple(sublist) for sublist in indices)
            ]
            indices_length = [len(x) for x in indices]

            if any(length > 1 for length in indices_length):
                files = []
                for index in indices:
                    if len(index) < 2:
                        continue
                    files.append(
                        (self.used_cuts[index[0]].suffixes[0].replace('.',
                                                                      '')))
                warning_message = "Multiple .cut-files selected for " \
                                  "following element(s): {} \nCheck the " \
                                  "elemental losses if you are not sure " \
                                  "what you are doing. "
                if len(files) > 1:  # If there are multiple elements
                    files = ' and '.join(files)
                    warning_message = warning_message.format(files)
                else:  # If theres is only one element
                    warning_message = warning_message.format(files[0])
                self.label_warning_text.setText(warning_message)
                self.label_warning_text.setStyleSheet("color: red")
                return
            self.label_warning_text.setText('')
            return

    @gutils.disable_widget
    def _accept_params(self, *_):
        """Accept given parameters.

        Args:
            *_: unused event args
        """
        self.status_msg = ""
        sbh = StatusBarHandler(self.statusbar)
        sbh.reporter.report(10)

        try:
            output_dir = self.measurement.get_depth_profile_dir()

            # Get the filepaths of the selected items
            used_cuts = self.used_cuts
            DepthProfileDialog.checked_cuts[self.measurement.name] = set(
                used_cuts)
            # TODO could take care of RBS selection here
            elements = [
                Element.from_string(fp.name.split(".")[1]) for fp in used_cuts
            ]

            x_unit = self.x_axis_units

            DepthProfileDialog.x_unit = x_unit
            DepthProfileDialog.line_zero = self.show_zero_line
            DepthProfileDialog.line_scale = self.show_scale_line
            DepthProfileDialog.systerr = self.systematic_error
            DepthProfileDialog.used_eff = self.show_used_eff

            DepthProfileDialog.eff_files_str = self.eff_files_str

            sbh.reporter.report(20)

            # If items are selected, proceed to generating the depth profile
            if used_cuts:
                self.status_msg = "Please wait. Creating depth profile."
                if self.parent.depth_profile_widget:
                    self.parent.del_widget(self.parent.depth_profile_widget)

                # If reference density changed, update value to measurement
                if x_unit == DepthProfileUnit.NM:
                    _, _, _, profile, measurement = \
                        self.measurement.get_used_settings()
                    if profile.reference_density != self.reference_density:
                        profile.reference_density = self.reference_density
                        measurement.to_file()

                self.parent.depth_profile_widget = DepthProfileWidget(
                    self.parent,
                    output_dir,
                    used_cuts,
                    elements,
                    x_unit,
                    DepthProfileDialog.line_zero,
                    DepthProfileDialog.used_eff,
                    DepthProfileDialog.line_scale,
                    DepthProfileDialog.systerr,
                    DepthProfileDialog.eff_files_str,
                    progress=sbh.reporter.get_sub_reporter(
                        lambda x: 30 + 0.6 * x))

                sbh.reporter.report(90)

                icon = self.parent.icon_manager.get_icon(
                    "depth_profile_icon_2_16.png")
                self.parent.add_widget(self.parent.depth_profile_widget,
                                       icon=icon)
                self.close()
            else:
                self.status_msg = "Please select .cut file[s] to create " \
                                  "depth profiles."
        except Exception as e:
            error_log = f"Exception occurred when trying to create depth " \
                        f"profiles: {e}"
            self.measurement.log_error(error_log)
        finally:
            sbh.reporter.report(100)

    def _show_reference_density(self):
        """
        Add a filed for modifying the reference density.
        """
        self.label_reference_density.setVisible(True)
        self.sbox_reference_density.setVisible(True)

    def _hide_reference_density(self):
        """
        Remove reference density form dialog if it is there.
        """
        self.label_reference_density.setVisible(False)
        self.sbox_reference_density.setVisible(False)

    def _show_efficiency_files(self):
        """Update efficiency files to UI which are used.
        """
        detector, *_ = self.measurement.get_used_settings()
        self.used_efficiency_files = df.get_efficiency_text(
            self.treeWidget, detector)

    def _show_measurement_settings(self):
        """Show some important setting values in the depth profile parameter
        dialog for the user.
        """
        detector, _, _, profile, _ = self.measurement.get_used_settings()

        self.tof_slope = detector.tof_slope
        self.tof_offset = detector.tof_offset
        self.depth_stop = profile.depth_step_for_stopping
        self.depth_steps = profile.number_of_depth_steps
        self.depth_bin = profile.depth_step_for_output
        self.depth_scale = f"{profile.depth_for_concentration_from} - " \
                           f"{profile.depth_for_concentration_to}"
        self.reference_density = profile.reference_density
コード例 #20
0
class CalibrationDialog(QtWidgets.QDialog):
    """A dialog for the time of flight calibration
    """
    bin_width = bnd.bind("binWidthSpinBox")
    selected_cut_file = bnd.bind("cutFilesTreeWidget",
                                 fget=bnd.get_selected_tree_item,
                                 fset=bnd.set_selected_tree_item)
    POINTS_OBJECT_FILENAME = 'points.pkl'

    def __init__(self,
                 measurements: List[Measurement],
                 detector: Detector,
                 run: Run,
                 parent_settings_widget=None):
        """Inits the calibration dialog class.
        
        Args:
            measurements: A string list representing measurements files.
            detector: A Detector class object.
            run: Run object.
            parent_settings_widget: A widget this dialog was opened from.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_calibration_dialog.ui", self)

        self.measurements = measurements
        self.run = run
        self.detector = detector

        self.parent_settings_widget = parent_settings_widget
        self.tof_calibration = TOFCalibration()

        # Go through all the measurements and their cut files and list them.
        for measurement in self.measurements:
            item = QtWidgets.QTreeWidgetItem([measurement.name])
            cuts, _ = measurement.get_cut_files()
            gutils.fill_tree(item,
                             cuts,
                             data_func=lambda fp: CutFile(cut_file_path=fp),
                             text_func=lambda fp: fp.name)
            self.cutFilesTreeWidget.addTopLevelItem(item)
            item.setExpanded(True)
        # Resize columns to fit the content nicely
        for column in range(0, self.cutFilesTreeWidget.columnCount()):
            self.cutFilesTreeWidget.resizeColumnToContents(column)

        self.curveFittingWidget = CalibrationCurveFittingWidget(
            self, self.selected_cut_file, self.tof_calibration, self.detector,
            self.bin_width, 0, self.run)

        old_params = None
        # Get old parameters from the parent dialog
        if parent_settings_widget is not None:
            try:
                f1 = self.parent_settings_widget.tof_offset
                f2 = self.parent_settings_widget.tof_slope
                old_params = f1, f2
            except ValueError as e:
                print(f"Can't get old calibration parameters from the "
                      f"settings dialog: {e}.")

        self.linearFittingWidget = CalibrationLinearFittingWidget(
            self, self.tof_calibration, old_params)

        self.fittingResultsLayout.addWidget(self.curveFittingWidget)
        self.calibrationResultsLayout.addWidget(self.linearFittingWidget)

        # Set up connections
        self.cutFilesTreeWidget.itemSelectionChanged.connect(
            self.__update_curve_fit)
        self.pointsTreeWidget.itemClicked.connect(self.__set_state_for_point)
        self.acceptPointButton.clicked.connect(self.__accept_point)
        self.binWidthSpinBox.valueChanged.connect(self.__update_curve_fit)
        self.binWidthSpinBox.setKeyboardTracking(False)
        self.acceptCalibrationButton.clicked.connect(self.accept_calibration)
        self.cancelButton.clicked.connect(self.close)
        self.removePointButton.clicked.connect(self.remove_selected_points)
        self.tofChannelLineEdit.editingFinished.connect(
            lambda: self.set_calibration_point(
                float(self.tofChannelLineEdit.text())))

        # Set the validator for lineEdit so user can't give invalid values
        double_validator = QtGui.QDoubleValidator()
        self.tofChannelLineEdit.setValidator(double_validator)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.label_remove_timeout)

        try:
            self.__load_object(self.POINTS_OBJECT_FILENAME)
            for p in self.tof_calibration.tof_points:
                self.__accept_points(p)
        except OSError:
            pass

        self.exec_()

    def showEvent(self, _):
        """Called after dialog is shown. Size is adjusted so that all elements
        fit nicely on screen.
        """
        self.adjustSize()

    def remove_selected_points(self):
        """Remove selected items from point tree widget
        """
        removed_something = False
        root = self.pointsTreeWidget.invisibleRootItem()
        for item in self.pointsTreeWidget.selectedItems():
            if item and hasattr(item, "point"):
                removed_something = True
                self.tof_calibration.remove_point(item.point)
            (item.parent() or root).removeChild(item)
        if removed_something:
            self.__change_selected_points()
            self.__save_object(self.POINTS_OBJECT_FILENAME)

    def set_calibration_point(self, tof):
        """Set Cut file front edge estimation to specific value.
        
        Args:
            tof: Float representing front edge of linear fit estimation.
        """
        self.curveFittingWidget.matplotlib.set_calibration_point_externally(
            tof)

    def set_calibration_parameters_to_parent(self):
        """Set calibration parameters to parent dialog's calibration parameters 
        fields.
        """
        if self.parent_settings_widget is not None:
            self.parent_settings_widget.tof_slope = float(
                self.slopeLineEdit.text())
            self.parent_settings_widget.tof_offset = float(
                self.offsetLineEdit.text())
            return True
        return False

    def accept_calibration(self):
        """Accept calibration (parameters).
        """
        calib_ok = "Calibration parameters accepted.\nYou can now close the " \
                   "window."
        calib_no = "Couldn't set parameters to\nthe settings dialog."
        calib_inv = "Invalid calibration parameters."
        results = self.tof_calibration.get_fit_parameters()
        if results[0] and results[1]:
            if self.set_calibration_parameters_to_parent():
                self.acceptCalibrationLabel.setText(calib_ok)
            else:
                self.acceptCalibrationLabel.setText(calib_no)
        else:
            self.acceptCalibrationLabel.setText(calib_inv)

    def __update_curve_fit(self):
        """Redraws everything in the curve fitting graph. Updates the bin width
        too.
        """
        self.__change_accept_point_label("")
        if self.selected_cut_file is not None:
            self.curveFittingWidget.matplotlib.change_bin_width(self.bin_width)
            try:
                self.curveFittingWidget.matplotlib.change_cut(
                    self.selected_cut_file)
            except ValueError as e:
                QtWidgets.QMessageBox.critical(self, "Warning", str(e))
                # TODO: Clear plot

    def __set_state_for_point(self, tree_item):
        """Sets if the tof calibration point is drawn to the linear fit graph
        
        Args:
            tree_item: QtWidgets.QTreeWidgetItem
        """
        if tree_item and hasattr(tree_item, "point"):
            tree_item.point.point_used = tree_item.checkState(0)
            self.__change_selected_points()
            self.__enable_accept_calibration_button()

    def __accept_points(self, point):
        """ Called when 'accept point' button is clicked.

        Adds the calibration point to the point set for linear fitting and
        updates the treewidget of points.
        """
        self.__add_point_to_tree(point)
        self.__change_selected_points()
        self.__enable_accept_calibration_button()
        self.__change_accept_point_label("Point accepted.")

    def __accept_point(self):
        """ Called when 'accept point' button is clicked.
        
        Adds the calibration point to the point set for linear fitting and
        updates the treewidget of points.
        """
        point = self.curveFittingWidget.matplotlib.tof_calibration_point
        if point and not self.tof_calibration.point_exists(point):
            self.tof_calibration.add_point(point)
            self.__add_point_to_tree(point)
            self.__change_selected_points()
            self.__enable_accept_calibration_button()
            self.__change_accept_point_label("Point accepted.")
            self.__save_object(self.POINTS_OBJECT_FILENAME)
        else:
            self.__change_accept_point_label("Point already exists.")

    def __load_object(self, filename):
        file_to_open = self.detector.path.parent / filename
        with open(file_to_open, 'rb') as output:
            self.tof_calibration.tof_points = pickle.load(output)

    def __save_object(self, filename):
        file_to_open = self.detector.path.parent / filename
        with open(file_to_open, 'wb') as output:
            pickle.dump(self.tof_calibration.tof_points, output,
                        pickle.HIGHEST_PROTOCOL)

    def __change_accept_point_label(self, text):
        """Sets text for the 'acceptPointLabel' label 
        and starts timer.
        
        Args:
            text: String to be set to the label.
        """
        self.acceptPointLabel.setText(text)
        self.timer.start(1500)

    def label_remove_timeout(self):
        """Timeout event method to remove label text.
        """
        self.acceptPointLabel.setText("")
        self.timer.stop()

    def __enable_accept_calibration_button(self):
        """Let press accept calibration only if there are parameters available.
        """
        if self.tof_calibration.slope or self.tof_calibration.offset:
            self.acceptCalibrationButton.setEnabled(True)
        else:
            self.acceptCalibrationButton.setEnabled(False)

    def __add_point_to_tree(self, tof_calibration_point):
        """Adds a ToF Calibration point to the pointsTreeWidget and sets the 
        QTreeWidgetItem's attribute 'point' as the given TOFCalibrationPoint. 
        """
        item = QtWidgets.QTreeWidgetItem([tof_calibration_point.get_name()])
        item.point = tof_calibration_point
        item.setCheckState(0, QtCore.Qt.Checked)
        self.pointsTreeWidget.addTopLevelItem(item)

    def __change_selected_points(self):
        """Redraws the linear fitting graph.
        """
        self.linearFittingWidget.matplotlib.on_draw()  # Redraw
コード例 #21
0
class OptimizationDialog(QtWidgets.QDialog,
                         PropertySavingWidget,
                         metaclass=QtABCMeta):
    """User may either optimize fluence or recoil atom distribution.
    Optimization is done by comparing simulated spectrum to measured spectrum.
    """
    ch: float = bnd.bind("histogramTicksDoubleSpinBox")
    use_efficiency: bool = bnd.bind("eff_file_check_box")
    selected_cut_file: Path = bnd.bind("measurementTreeWidget",
                                       fget=bnd.get_selected_tree_item,
                                       fset=bnd.set_selected_tree_item)
    selected_element_simulation: ElementSimulation = bnd.bind(
        "simulationTreeWidget",
        fget=bnd.get_selected_tree_item,
        fset=bnd.set_selected_tree_item)
    auto_adjust_x: bool = bnd.bind("auto_adjust_x_box")

    @property
    def fluence_parameters(self) -> Dict[str, Any]:
        return self.fluence_widget.get_properties()

    @fluence_parameters.setter
    def fluence_parameters(self, value: Dict[str, Any]):
        self.fluence_widget.set_properties(**value)

    @property
    def recoil_parameters(self) -> Dict[str, Any]:
        return self.recoil_widget.get_properties()

    @recoil_parameters.setter
    def recoil_parameters(self, value: Dict[str, Any]):
        self.recoil_widget.set_properties(**value)

    def __init__(self, simulation: Simulation, parent):
        """Initializes an OptimizationDialog that displays various optimization
        parameters.

        Args:
            simulation: a Simulation object
            parent: a SimulationTabWidget
        """
        super().__init__()
        self.simulation = simulation
        self.tab = parent
        self.current_mode = OptimizationType.RECOIL

        uic.loadUi(gutils.get_ui_dir() / "ui_optimization_params.ui", self)

        self.recoil_widget = OptimizationRecoilParameterWidget()
        self.fluence_widget = OptimizationFluenceParameterWidget()

        self.load_properties_from_file()

        locale = QLocale.c()
        self.histogramTicksDoubleSpinBox.setLocale(locale)

        self.pushButton_OK.setEnabled(False)

        self.pushButton_Cancel.clicked.connect(self.close)
        self.pushButton_OK.clicked.connect(self.start_optimization)

        self.radios = QtWidgets.QButtonGroup(self)
        self.radios.buttonToggled[QtWidgets.QAbstractButton,
                                  bool].connect(self.choose_optimization_mode)
        self.parametersLayout.addWidget(self.recoil_widget)
        self.parametersLayout.addWidget(self.fluence_widget)
        self.fluence_widget.hide()

        self.radios.addButton(self.fluenceRadioButton)
        self.radios.addButton(self.recoilRadioButton)

        gutils.fill_tree(self.simulationTreeWidget.invisibleRootItem(),
                         simulation.element_simulations,
                         text_func=lambda elem_sim: elem_sim.get_full_name())

        self.simulationTreeWidget.itemSelectionChanged.connect(
            self._enable_ok_button)
        self.simulationTreeWidget.itemSelectionChanged.connect(self._adjust_x)
        self.auto_adjust_x_box.clicked.connect(self._adjust_x)

        self._fill_measurement_widget()

        self.measurementTreeWidget.itemSelectionChanged.connect(
            self._enable_ok_button)

        self.eff_file_check_box.clicked.connect(self._enable_efficiency_label)
        self._update_efficiency_label()

        self.exec_()

    def closeEvent(self, event):
        """Overrides the QDialogs closeEvent. Saves current parameters to
        file so they shown next time the dialog is opened.
        """
        params = self.get_properties()
        # Remove non-serializable values
        params.pop("selected_element_simulation")
        params.pop("selected_cut_file")
        self.save_properties_to_file(values=params)
        QtWidgets.QDialog.closeEvent(self, event)

    def _update_efficiency_label(self):
        """Updates the text of efficiency label.
        """
        self.efficiency_label.setText(
            df.get_multi_efficiency_text(
                self.measurementTreeWidget,
                self.simulation.sample.get_measurements(),
                data_func=lambda tpl: tpl[0]))

    def _enable_efficiency_label(self):
        """Enables or disables efficiency label.
        """
        self.efficiency_label.setEnabled(self.use_efficiency)

    def _fill_measurement_widget(self):
        """Add calculated tof_list files to tof_list_tree_widget by
        measurement under the same sample.
        """
        for sample in self.simulation.request.samples.samples:
            for measurement in sample.measurements.measurements.values():
                if self.simulation.sample is measurement.sample:
                    root = QtWidgets.QTreeWidgetItem()
                    root.setText(0, measurement.name)
                    self.measurementTreeWidget.addTopLevelItem(root)
                    cuts, elem_losses = measurement.get_cut_files()
                    gutils.fill_tree(root,
                                     cuts,
                                     data_func=lambda c: (c, measurement),
                                     text_func=lambda c: c.name)
                    loss_node = QtWidgets.QTreeWidgetItem(["Element losses"])
                    gutils.fill_tree(loss_node,
                                     elem_losses,
                                     data_func=lambda c: (c, measurement),
                                     text_func=lambda c: c.name)
                    root.addChild(loss_node)
                    root.setExpanded(True)

    def get_property_file_path(self) -> Path:
        """Returns absolute path to the file that is used for saving and
        loading parameters.
        """
        return Path(self.simulation.directory, ".parameters",
                    ".optimization_parameters")

    def _enable_ok_button(self, *_):
        """Enables OK button if both ElementSimulation and cut file have been
        selected.
        """
        self.pushButton_OK.setEnabled(
            self.selected_cut_file is not None
            and self.selected_element_simulation is not None)

    def _adjust_x(self):
        """Adjusts the upper limit value on x axis based on the distribution
        length of the main recoil of currently selected ElementSimulation.
        """
        if not self.auto_adjust_x:
            return
        elem_sim = self.selected_element_simulation
        if elem_sim is None:
            return

        _, max_x = elem_sim.get_main_recoil().get_range()
        _, prev_y = self.recoil_widget.upper_limits
        self.recoil_widget.upper_limits = max_x, prev_y

    def choose_optimization_mode(self, button, checked):
        """Choose whether to optimize recoils or fluence. Show correct widget.
        """
        if checked:
            if button.text() == "Recoil":
                self.current_mode = OptimizationType.RECOIL
                self.fluence_widget.hide()
                self.recoil_widget.show()
            else:
                self.recoil_widget.hide()
                self.fluence_widget.show()
                self.current_mode = OptimizationType.FLUENCE

    def start_optimization(self):
        """Find necessary cut file and make energy spectrum with it, and start
        optimization with given parameters.
        """
        elem_sim = self.selected_element_simulation
        cut, measurement = self.selected_cut_file
        # Delete previous results widget if it exists
        if self.tab.optimization_result_widget:
            self.tab.del_widget(self.tab.optimization_result_widget)
            self.tab.optimization_result_widget = None

            # Delete previous energy spectra if there are any
            df.delete_optim_espe(self, elem_sim)

        self.close()

        if self.current_mode == OptimizationType.RECOIL:
            params = self.recoil_widget.get_properties()
        else:
            params = self.fluence_widget.get_properties()

        # TODO move following code to the result widget
        nsgaii = Nsgaii(element_simulation=elem_sim,
                        measurement=measurement,
                        cut_file=cut,
                        ch=self.ch,
                        **params,
                        use_efficiency=self.use_efficiency)

        # Optimization running thread
        ct = CancellationToken()
        optimization_thread = threading.Thread(
            target=nsgaii.start_optimization,
            kwargs={"cancellation_token": ct})

        # Create necessary results widget
        result_widget = self.tab.add_optimization_results_widget(
            elem_sim, cut.name, self.current_mode, ct=ct)

        elem_sim.optimization_widget = result_widget
        nsgaii.subscribe(result_widget)

        optimization_thread.daemon = True
        optimization_thread.start()
コード例 #22
0
class PercentageWidget(QtWidgets.QWidget):
    """
    Class for a widget that calculates the percentages for given recoils and
    intervals.
    """
    interval_type = bnd.bind("comboBox")
    status_msg = bnd.bind("label_status")

    def __init__(self,
                 recoil_elements: List[RecoilElement],
                 icon_manager,
                 distribution_changed=None,
                 interval_changed=None,
                 get_limits=None):
        """
        Initialize the widget.

        Args:
            recoil_elements: List of recoil elements.
            icon_manager: Icon manager.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_percentage_widget.ui", self)

        # Stores the PercentageRow objects for each recoil
        self._percentage_rows = {recoil: None for recoil in recoil_elements}

        self.icon_manager = icon_manager
        self.setWindowTitle("Percentages")

        gutils.fill_combobox(self.comboBox, IntervalType)
        self.comboBox.currentIndexChanged.connect(
            self.__show_percents_and_areas)

        self.icon_manager.set_icon(self.absRelButton, "depth_profile_rel.svg")
        self.__relative_values = True
        self.absRelButton.setToolTip(
            "Toggle between relative and absolute values.")
        self.absRelButton.clicked.connect(self.__show_abs_or_rel_values)

        self.__dist_changed_sig = distribution_changed
        self.__interval_changed_sig = interval_changed

        self.get_limits = get_limits
        if self.get_limits is not None:
            if distribution_changed is not None:
                distribution_changed.connect(self._dist_changed)

            if interval_changed is not None:
                interval_changed.connect(self._limits_changed)

        self.__show_percents_and_areas()

    def closeEvent(self, event):
        """Overrides QWidget's closeEvent. Disconnects slots from signals.
        """
        try:
            self.__dist_changed_sig.disconnect(self._dist_changed)
        except (TypeError, AttributeError):
            # Signal was either already disconnected or None
            pass
        try:
            self.__interval_changed_sig.disconnect(self._limits_changed)
        except (TypeError, AttributeError):
            # Signal was either already disconnected or None
            pass
        event.accept()

    def row_selected(self, recoil: RecoilElement) -> bool:
        """Checks if the given recoil has a row that is selected.
        """
        row = self._percentage_rows.get(recoil, None)
        # Row is None when it has not been created yet.
        # Row will be created after area has been calculated
        # for the first time so we can return True
        return row is None or row.selected

    def _calculate_areas_and_percentages(self,
                                         rounding=2) -> Tuple[Dict, Dict]:
        """Calculate areas and percents for recoil elements within the given
        interval.

        Args:
            rounding: rounding accuracy of percentage calculation
        Return:
            areas and percentages as a dict
        """
        if self.get_limits is not None:
            limits = self.get_limits()
        else:
            return None, None

        interval_type = self.interval_type

        if interval_type is IntervalType.NO_LIMITS:

            def get_range(_):
                return None, None

        elif interval_type is IntervalType.COMMON:

            def get_range(_):
                try:
                    start, end = limits["common"]
                except (ValueError, KeyError):
                    start, end = None, None
                return start, end
        else:

            def get_range(rec: RecoilElement):
                try:
                    start, end = limits[rec]
                except (ValueError, KeyError):
                    start, end = None, None
                return start, end

        areas = {}
        percentages = {}
        for recoil in self._percentage_rows:
            if not self.row_selected(recoil):
                area = 0
            else:
                s, e = get_range(recoil)
                area = recoil.calculate_area(s, e)

            if not self.__relative_values:
                # TODO label text needs to reformatted when using absolute
                #  values. See previous version of Potku for reference.
                area *= recoil.reference_density

            areas[recoil] = area

        for recoil, percentage in zip(
                areas,
                mf.calculate_percentages(areas.values(), rounding=rounding)):
            percentages[recoil] = percentage

        return areas, percentages

    def __show_abs_or_rel_values(self):
        """Show recoil area in absolute or relative format.
        """
        if self.__relative_values:
            self.icon_manager.set_icon(self.absRelButton,
                                       "depth_profile_abs.svg")
        else:
            self.icon_manager.set_icon(self.absRelButton,
                                       "depth_profile_rel.svg")

        self.__relative_values = not self.__relative_values
        self.__show_percents_and_areas()

    def __show_percents_and_areas(self):
        """Show the percentages of the recoil elements.
        """
        areas, percentages = self._calculate_areas_and_percentages()
        if areas is None or percentages is None:
            return

        for row_idx, recoil in enumerate(sorted(percentages)):
            try:
                self._percentage_rows[recoil].percentage = \
                    percentages[recoil]
                self._percentage_rows[recoil].area = \
                    areas[recoil]
            except (KeyError, AttributeError):
                row = PercentageRow(recoil.get_full_name(),
                                    recoil.color,
                                    percentage=percentages[recoil],
                                    area=areas[recoil])
                self._percentage_rows[recoil] = row
                row.selectedCheckbox.stateChanged.connect(
                    self.__show_percents_and_areas)
                self.gridLayout.addWidget(row, row_idx, 0)

    def _dist_changed(self, recoil, _):
        """Callback that is executed when RecoilElement distribution is changed.
        Checks that the recoil is being displayed in the Widget and then calls
        _show_percents_and_areas.

        Args:
            recoil: RecoilElement which distribution has changed.
            _: unused ElementSimulation object.
        """
        if recoil in self._percentage_rows:
            self.__show_percents_and_areas()

    def _limits_changed(self):
        """Updates the common_interval with given x values and calls
        __show_percents_and_areas.
        """
        self.__show_percents_and_areas()
コード例 #23
0
class MeasurementSettingsWidget(QtWidgets.QWidget,
                                bnd.PropertyTrackingWidget,
                                bnd.PropertySavingWidget,
                                metaclass=QtABCMeta):
    """Class for creating a measurement settings tab.
    """
    # Signal that indicates whether the beam selection was ok or not (i.e. can
    # the selected element be used in measurements or simulations)
    beam_selection_ok = pyqtSignal(bool)

    measurement_setting_file_name = bnd.bind("nameLineEdit")
    measurement_setting_file_description = bnd.bind("descriptionPlainTextEdit")

    beam_ion = bnd.multi_bind(
        ["beamIonButton", "isotopeComboBox"], fget=element_from_gui,
        fset=element_to_gui, track_change=True
    )
    beam_energy = bnd.bind("energyDoubleSpinBox", track_change=True)
    beam_energy_distribution = bnd.bind(
        "energyDistDoubleSpinBox", track_change=True)
    beam_charge = bnd.bind("beamChargeSpinBox")
    beam_spot_size = bnd.multi_bind(
        ["spotSizeXdoubleSpinBox", "spotSizeYdoubleSpinBox"], track_change=True)
    beam_divergence = bnd.bind("divergenceDoubleSpinBox", track_change=True)
    beam_profile = bnd.bind("profileComboBox", track_change=True)

    run_fluence = bnd.bind("fluenceDoubleSpinBox")
    run_current = bnd.bind("currentDoubleSpinBox")
    run_time = bnd.bind("timeDoubleSpinBox")
    run_charge = bnd.bind("runChargeDoubleSpinBox")

    target_theta = bnd.bind("targetThetaDoubleSpinBox", track_change=True)
    detector_theta = bnd.bind("detectorThetaDoubleSpinBox", track_change=True)

    def __init__(self, obj: Union[Measurement, Simulation], preset_folder=None):
        """Initializes the widget.

        Args:
            obj: object that uses these settings, either a Measurement or a
                Simulation.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_measurement_settings_tab.ui", self)
        self.fluenceDoubleSpinBox = ScientificSpinBox()
        image = gf.get_root_dir() / "images" / "measurement_setup_angles.png"
        pixmap = QtGui.QPixmap(str(image))
        self.picture.setScaledContents(True)
        self.picture.setPixmap(pixmap)

        self.obj = obj
        self.__original_property_values = {}

        locale = QLocale.c()

        self.energyDoubleSpinBox.setLocale(locale)
        self.energyDistDoubleSpinBox.setLocale(locale)
        self.spotSizeXdoubleSpinBox.setLocale(locale)
        self.spotSizeYdoubleSpinBox.setLocale(locale)
        self.divergenceDoubleSpinBox.setLocale(locale)
        self.currentDoubleSpinBox.setLocale(locale)
        self.timeDoubleSpinBox.setLocale(locale)
        self.runChargeDoubleSpinBox.setLocale(locale)

        self.targetThetaDoubleSpinBox.setLocale(locale)
        self.detectorThetaDoubleSpinBox.setLocale(locale)
        self.detectorFiiDoubleSpinBox.setLocale(locale)
        self.targetFiiDoubleSpinBox.setLocale(locale)

        # Fii angles are currently not used so disable their spin boxes
        self.detectorFiiDoubleSpinBox.setEnabled(False)
        self.targetFiiDoubleSpinBox.setEnabled(False)
        gutils.fill_combobox(self.profileComboBox, Profile)

        # Copy of measurement's/simulation's run or default run
        # TODO should default run also be copied?
        if not self.obj.run:
            self.tmp_run = self.obj.request.default_run
        else:
            self.tmp_run = copy.deepcopy(self.obj.run)

        self.isotopeInfoLabel.setVisible(False)

        self.beamIonButton.clicked.connect(self.change_element)

        self.fields_are_valid = False
        iv.set_input_field_red(self.nameLineEdit)
        self.nameLineEdit.textChanged.connect(
            lambda: iv.check_text(self.nameLineEdit, qwidget=self))
        self.nameLineEdit.textEdited.connect(self.__validate)
        self.nameLineEdit.setEnabled(False)

        self.run_form_layout: QtWidgets.QFormLayout
        self.run_form_layout.insertRow(3, "Fluence", self.fluenceDoubleSpinBox)
        self.fluenceDoubleSpinBox.scientificLineEdit.setContextMenuPolicy(
            Qt.ActionsContextMenu)
        self.actionMultiply = QtWidgets.QAction(
            self.fluenceDoubleSpinBox.scientificLineEdit)
        self.actionMultiply.triggered.connect(self.__multiply_fluence)
        self.fluenceDoubleSpinBox.scientificLineEdit.addAction(
            self.actionMultiply)

        self.actionUndo = QtWidgets.QAction(
            self.fluenceDoubleSpinBox.scientificLineEdit)
        self.actionUndo.setText("Undo multiply")
        self.actionUndo.triggered.connect(self.__undo_fluence)

        self.actionUndo.setEnabled(bool(self.tmp_run.previous_fluence))
        self.fluenceDoubleSpinBox.scientificLineEdit.addAction(self.actionUndo)

        self.clipboard = QGuiApplication.clipboard()
        self._ratio = None
        self.clipboard.changed.connect(self.__update_multiply_action)
        self.__update_multiply_action()

        self.energyDoubleSpinBox.setToolTip("Energy set in MeV with.")

        if preset_folder is not None:
            self.preset_widget = PresetWidget.add_preset_widget(
                preset_folder / "measurement", "mea",
                lambda w: self.layout().insertWidget(0, w),
                save_callback=self.save_properties_to_file,
                load_callback=self.load_properties_from_file
            )
        else:
            self.preset_widget = None

        self.show_settings()

    def get_property_file_path(self) -> Path:
        raise NotImplementedError

    def save_on_close(self) -> bool:
        return False

    def save_properties_to_file(self, file_path: Path):
        def err_func(err: Exception):
            if self.preset_widget is not None:
                self.preset_widget.set_status_msg(
                    f"Failed to save preset: {err}")

        values = self.get_properties()
        values["beam_ion"] = str(values["beam_ion"])
        self._save_json_file(
            file_path, values, True, error_func=err_func)
        if self.preset_widget is not None:
            self.preset_widget.load_files(selected=file_path)

    def load_properties_from_file(self, file_path: Path):
        # TODO create a base class for settings widgets to get rid of this
        #   copy-paste code
        def err_func(err: Exception):
            if self.preset_widget is not None:
                self.preset_widget.set_status_msg(
                    f"Failed to load preset: {err}")
        values = self._load_json_file(file_path, error_func=err_func)
        if not values:
            return
        try:
            values["beam_ion"] = Element.from_string(values["beam_ion"])
            self.set_properties(**values)
        except KeyError as e:
            err_func(f"file contained no {e} key.")
        except ValueError as e:
            err_func(e)

    def get_original_property_values(self):
        """Returns the values of the properties when they were first set.
        """
        return self.__original_property_values

    def show_settings(self):
        """Show measurement settings.
        """
        self.measurement_setting_file_name = \
            self.obj.measurement_setting_file_name
        self.measurement_setting_file_description = \
            self.obj.measurement_setting_file_description
        self.dateLabel.setText(time.strftime("%c %z %Z", time.localtime(
            self.obj.modification_time)))

        run_params = {
            f"run_{key}": value
            for key, value in self.tmp_run.get_settings().items()
        }
        bean_params = {
            f"beam_{key}": value
            for key, value in self.tmp_run.beam.get_settings().items()
        }
        self.set_properties(**run_params, **bean_params)

        self.detector_theta = self.obj.detector.detector_theta
        self.target_theta = self.obj.target.target_theta

    def check_angles(self):
        """
        Check that detector angle is bigger than target angle.
        This is a must for measurement. Simulation can handle target angles
        greater than the detector angle.

        Return:
            Whether it is ok to use current angle settings.
        """
        if self.target_theta > self.detector_theta:
            reply = QtWidgets.QMessageBox.question(
                self, "Warning",
                "Measurement cannot use a target angle that is "
                "bigger than the detector angle (for simulation "
                "this is possible).\n\n"
                "Do you want to use these settings anyway?",
                QtWidgets.QMessageBox.Ok |
                QtWidgets.QMessageBox.Cancel,
                QtWidgets.QMessageBox.Cancel)
            if reply == QtWidgets.QMessageBox.Cancel:
                return False
        return True

    def get_run_and_beam_parameters(self):
        """Returns run and beam related properties as separate dictionaries with
        prefixes removed from each key.
        """
        run_params, beam_params = {}, {}
        for k, v in self.get_properties().items():
            if k.startswith("run_"):
                run_params[k[4:]] = v
            elif k.startswith("beam_"):
                beam_params[k[5:]] = v
        return run_params, beam_params

    def update_settings(self):
        """Update measurement settings.
        """
        isotope_index = self.isotopeComboBox.currentIndex()
        if isotope_index != -1:
            self.obj.measurement_setting_file_name = \
                self.measurement_setting_file_name
            self.obj.measurement_setting_file_description = \
                self.measurement_setting_file_description

            run_params, beam_params = self.get_run_and_beam_parameters()
            self.obj.run.set_settings(**run_params)
            self.obj.run.beam.set_settings(**beam_params)
            self.obj.run.previous_fluence = self.tmp_run.previous_fluence
            self.obj.detector.detector_theta = self.detector_theta
            self.obj.target.target_theta = self.target_theta
        else:
            QtWidgets.QMessageBox.critical(
                self, "Warning",
                "No isotope selected.\n\n"
                "Please select an isotope for the beam element.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)

    def other_values_changed(self):
        """
        Check whether measurement values that don't require running a
        simulation again have been changed.

        Return:
             True or False.
        """
        # TODO make it possible to group TrackingProperties (for example into
        #  'critical' and 'noncritical' properties)
        if self.obj.measurement_setting_file_name != \
                self.measurement_setting_file_name:
            return True
        if self.obj.measurement_setting_file_description != \
                self.measurement_setting_file_description:
            return True
        if self.obj.run.beam.charge != self.beam_charge:
            return True
        if self.obj.run.current != self.run_current:
            return True
        if self.obj.run.time != self.run_time:
            return True
        if self.obj.run.charge != self.run_charge:
            return True
        if self.obj.run.fluence != self.run_fluence:
            return True
        return False

    def save_to_tmp_run(self):
        """
        Save run and beam parameters to tmp_run object.
        """
        isotope_index = self.isotopeComboBox.currentIndex()
        # TODO: Show a message box, don't just quietly do nothing
        if isotope_index != -1:
            run_params, beam_params = self.get_run_and_beam_parameters()
            self.tmp_run.set_settings(**run_params)
            self.tmp_run.beam.set_settings(**beam_params)
        else:
            QtWidgets.QMessageBox.critical(
                self, "Warning",
                "No isotope selected.\n\n"
                "Please select an isotope for the beam element.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)

    def __validate(self):
        """Validate the measurement settings file name.
        """
        text = self.measurement_setting_file_name
        regex = "^[A-Za-z0-9-ÖöÄäÅå]*"
        valid_text = iv.validate_text_input(text, regex)

        self.measurement_setting_file_name = valid_text

    def __multiply_fluence(self):
        """Multiply fluence with clipboard's value.
        """
        try:
            new_fluence = round(self._ratio * self.run_fluence, 2)
            self.tmp_run.previous_fluence.append(self.run_fluence)
            self.run_fluence = new_fluence
            self.actionUndo.setEnabled(True)
        except ValueError:
            pass

    def __undo_fluence(self):
        """Undo latest change to fluence.
        """
        try:
            old_value = self.tmp_run.previous_fluence.pop()
            self.run_fluence = old_value
        except IndexError:
            pass

        # Enable undo if there are still previous values
        self.actionUndo.setEnabled(bool(self.tmp_run.previous_fluence))

    def __update_multiply_action(self):
        """Update the value with which the multiplication is done.
        """
        try:
            self._ratio = float(self.clipboard.text())
        except ValueError:
            if self._ratio is None:
                self._ratio = 1.0
        self.actionMultiply.setText(f"Multiply with value in clipboard\n"
                                    f"({self._ratio})")

    def change_element(self):
        """Opens element selection dialog and loads selected element's isotopes
        to the combobox.
        """
        dialog = ElementSelectionDialog()
        if dialog.element:
            self.beamIonButton.setText(dialog.element)
            # TODO use IsotopeSelectionWidget
            gutils.load_isotopes(dialog.element, self.isotopeComboBox)

            # Check if no isotopes
            if self.isotopeComboBox.count() == 0:
                self.isotopeInfoLabel.setVisible(True)
                self.fields_are_valid = False
                iv.set_input_field_red(self.isotopeComboBox)
                self.beam_selection_ok.emit(False)
            else:
                self.isotopeInfoLabel.setVisible(False)
                iv.check_text(self.nameLineEdit, qwidget=self)
                self.isotopeComboBox.setStyleSheet(
                    "background-color: %s" % "None")
                self.beam_selection_ok.emit(True)
        else:
            self.beam_selection_ok.emit(False)
コード例 #24
0
class EnergySpectrumParamsDialog(QtWidgets.QDialog):
    """An EnergySpectrumParamsDialog.
    """

    checked_cuts = {}
    bin_width = 0.025

    use_efficiency = bnd.bind("use_eff_checkbox")
    status_msg = bnd.bind("label_status")
    measurement_cuts = bnd.bind("treeWidget")
    used_bin_width = bnd.bind("histogramTicksDoubleSpinBox")

    external_files = bnd.bind("external_tree_widget")
    tof_list_files = bnd.bind("tof_list_tree_widget")
    used_recoil = bnd.bind("treeWidget")

    def __init__(self,
                 parent: BaseTab,
                 spectrum_type: str = _MESU,
                 element_simulation: Optional[ElementSimulation] = None,
                 simulation: Optional[Simulation] = None,
                 measurement: Optional[Measurement] = None,
                 recoil_widget=None,
                 statusbar: Optional[QtWidgets.QStatusBar] = None,
                 spectra_changed=None):
        """Inits energy spectrum dialog.
        
        Args:
            parent: A TabWidget.
            spectrum_type: Whether spectrum is for measurement of simulation.
            element_simulation: ElementSimulation object.
            recoil_widget: RecoilElement widget.
            statusbar: QStatusBar
            spectra_changed: pyQtSignal that is emitted when recoil atom
                distribution is changed.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_energy_spectrum_params.ui", self)

        self.parent = parent
        if spectrum_type == EnergySpectrumWidget.MEASUREMENT:
            if measurement is None:
                raise ValueError(
                    f"Must provide a Measurement when spectrum type is "
                    f"{spectrum_type}")
        elif spectrum_type is EnergySpectrumWidget.SIMULATION:
            if simulation is None:
                raise ValueError(
                    f"Must provide a Simulation when spectrum type is "
                    f"{spectrum_type}")
            if element_simulation is None:
                raise ValueError(
                    f"Must provide an ElementSimulation when spectrum is "
                    f"{spectrum_type}")
        else:
            raise ValueError(f"Unexpected spectrum type: {spectrum_type}")

        self.spectrum_type = spectrum_type
        self.measurement = measurement
        self.simulation = simulation
        self.element_simulation = element_simulation
        self.statusbar = statusbar
        self.result_files = []

        self.use_eff_checkbox.stateChanged.connect(
            lambda *_: self.label_efficiency_files.setEnabled(self.
                                                              use_efficiency))
        self.use_efficiency = True

        locale = QLocale.c()
        self.histogramTicksDoubleSpinBox.setLocale(locale)

        # Connect buttons
        self.pushButton_Cancel.clicked.connect(self.close)

        self.external_tree_widget = QtWidgets.QTreeWidget()

        if self.spectrum_type == EnergySpectrumWidget.MEASUREMENT:
            EnergySpectrumParamsDialog.bin_width = \
                self.measurement.profile.channel_width
            self.pushButton_OK.clicked.connect(
                lambda: self.__accept_params(spectra_changed=spectra_changed))

            m_name = self.measurement.name
            if m_name not in EnergySpectrumParamsDialog.checked_cuts:
                EnergySpectrumParamsDialog.checked_cuts[m_name] = set()

            gutils.fill_cuts_treewidget(self.measurement,
                                        self.treeWidget.invisibleRootItem(),
                                        use_elemloss=True)
            self.measurement_cuts = \
                EnergySpectrumParamsDialog.checked_cuts[m_name]

            self.importPushButton.setVisible(False)
        else:
            EnergySpectrumParamsDialog.bin_width = \
                self.element_simulation.channel_width

            self._set_simulation_files(recoil_widget)
            self._set_measurement_files()
            self._set_external_files()

            # Change the bin width label text
            self.histogramTicksLabel.setText(
                "Simulation and measurement histogram bin width:")

            self.pushButton_OK.clicked.connect(
                self.__calculate_selected_spectra)
            self.importPushButton.clicked.connect(self.__import_external_file)

        self.used_bin_width = EnergySpectrumParamsDialog.bin_width
        # FIXME .eff files not shown in sim mode
        self.__update_eff_files()
        self.exec_()

    def showEvent(self, event):
        """Adjust size after dialog has been shown.
        """
        self.adjustSize()
        super().showEvent(event)

    def _set_simulation_files(self, recoil_widget):
        """Sets up the simulation files in a QTreeWidget.
        """
        header_item = QtWidgets.QTreeWidgetItem()
        header_item.setText(0, "Simulated element (observed atoms)")
        self.treeWidget.setHeaderItem(header_item)

        for elem_sim in self.simulation.element_simulations:
            root = QtWidgets.QTreeWidgetItem(
                [f"{elem_sim.get_full_name()} ({elem_sim.get_atom_count()})"])
            gutils.fill_tree(root,
                             elem_sim.recoil_elements,
                             data_func=lambda rec: (elem_sim, rec, None),
                             text_func=lambda rec: rec.get_full_name())
            if elem_sim.is_optimization_finished():
                gutils.fill_tree(root,
                                 elem_sim.optimization_recoils,
                                 data_func=lambda rec:
                                 (elem_sim, rec, OptimizationType.RECOIL),
                                 text_func=lambda rec: rec.get_full_name())
            self.treeWidget.addTopLevelItem(root)
            root.setExpanded(True)

        self.used_recoil = {(recoil_widget.element_simulation,
                             recoil_widget.recoil_element, None)}

    def _set_measurement_files(self):
        """Sets up the .cut file list.
        """
        self.tof_list_tree_widget = QtWidgets.QTreeWidget()
        self.tof_list_tree_widget.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        header = QtWidgets.QTreeWidgetItem()
        header.setText(0, "Pre-calculated elements")
        self.gridLayout_2.addWidget(self.tof_list_tree_widget, 0, 1)
        self.tof_list_tree_widget.setHeaderItem(header)

        # Add calculated tof_list files to tof_list_tree_widget by
        # measurement under the same sample.
        for measurement in self.simulation.sample.get_measurements():
            root = QtWidgets.QTreeWidgetItem([measurement.name])

            cuts, elem_loss = measurement.get_cut_files()
            gutils.fill_tree(root,
                             cuts,
                             data_func=lambda c: (c, measurement),
                             text_func=lambda c: c.name)

            self.tof_list_tree_widget.addTopLevelItem(root)

            elem_loss_root = QtWidgets.QTreeWidgetItem(["Element losses"])
            gutils.fill_tree(elem_loss_root,
                             elem_loss,
                             data_func=lambda c: (c, measurement),
                             text_func=lambda c: c.name)
            root.addChild(elem_loss_root)
            root.setExpanded(True)

        self.tof_list_files = {}

    def _set_external_files(self):
        """Sets up the external file QTreeWidget.
        """
        # Add a view for adding external files to draw
        self.external_tree_widget.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        header = QtWidgets.QTreeWidgetItem()
        header.setText(0, "External files")
        self.gridLayout_2.addWidget(self.external_tree_widget, 0, 2)
        self.external_tree_widget.setHeaderItem(header)

        gutils.fill_tree(self.external_tree_widget.invisibleRootItem(),
                         self.simulation.request.get_imported_files(),
                         text_func=lambda fp: fp.name)

        self.external_files = {}

    def get_selected_measurements(self):
        """Returns a dictionary that contains selected measurements,
        cut files belonging to each measurement, and the corresponding
        result file.
        """
        mesus = self.tof_list_files
        used_measurements = {}
        # TODO result file is probably not needed here
        for c, m in mesus:
            used_measurements.setdefault(m, []).append({
                "cut_file":
                c,
                "result_file":
                Path(m.get_energy_spectra_dir(), f"{c.stem}.no_foil.hist")
            })
        return used_measurements

    def get_selected_simulations(self):
        """Returns a dictionary that contains selected simulations and list
        of recoil elements and corresponding result files.
        """
        used_simulations = {}
        for elem_sim, rec, optim in self.used_recoil:
            # TODO optim type may not be necessary
            used_simulations.setdefault(elem_sim, []).append({
                "recoil_element":
                rec,
                "optimization_type":
                optim
            })
        return used_simulations

    @gutils.disable_widget
    def __calculate_selected_spectra(self, *_):
        """Calculate selected spectra.
        """
        EnergySpectrumParamsDialog.bin_width = self.used_bin_width

        sbh = StatusBarHandler(self.statusbar)

        # Get all
        used_simulations = self.get_selected_simulations()
        used_measurements = self.get_selected_measurements()
        used_externals = self.external_files

        sbh.reporter.report(33)

        # Calculate espes for simulations
        for elem_sim, lst in used_simulations.items():
            for d in lst:
                _, espe_file = elem_sim.calculate_espe(**d,
                                                       write_to_file=True,
                                                       ch=self.bin_width)
                self.result_files.append(espe_file)

        sbh.reporter.report(66)

        # Calculate espes for measurements. 'no_foil' parameter is used to
        # make the results comparable with simulation espes. Basically
        # this increases the calculated energy values, shifting the espe
        # histograms to the right on the x axis.
        for mesu, lst in used_measurements.items():
            self.result_files.extend(d["result_file"] for d in lst)
            # TODO use the return values instead of reading the files further
            #   down the execution path
            EnergySpectrum.calculate_measured_spectra(
                mesu, [d["cut_file"] for d in lst],
                self.bin_width,
                use_efficiency=self.use_efficiency,
                no_foil=True)

        # Add external files
        self.result_files.extend(used_externals)

        sbh.reporter.report(100)

        msg = f"Created Energy Spectrum. " \
              f"Bin width: {self.bin_width} " \
              f"Used files: {', '.join(str(f) for f in self.result_files)}"

        self.simulation.log(msg)
        self.close()

    @gutils.disable_widget
    def __accept_params(self, spectra_changed=None):
        """Accept given parameters and cut files.
        """
        self.status_msg = ""
        width = self.used_bin_width
        m_name = self.measurement.name
        selected_cuts = self.measurement_cuts
        EnergySpectrumParamsDialog.checked_cuts[m_name] = set(
            self.measurement_cuts)
        EnergySpectrumParamsDialog.bin_width = width

        if selected_cuts:
            self.status_msg = "Please wait. Creating energy spectrum."
            if self.parent.energy_spectrum_widget:
                self.parent.del_widget(self.parent.energy_spectrum_widget)
            self.parent.energy_spectrum_widget = EnergySpectrumWidget(
                self.parent,
                spectrum_type=self.spectrum_type,
                use_cuts=selected_cuts,
                bin_width=width,
                use_efficiency=self.use_efficiency,
                statusbar=self.statusbar,
                spectra_changed=spectra_changed)

            # Check that matplotlib attribute exists after creation of energy
            # spectrum widget.
            # If it doesn't exists, that means that the widget hasn't been
            # initialized properly and the program should show an error dialog.
            if hasattr(self.parent.energy_spectrum_widget,
                       "matplotlib_layout"):
                icon = self.parent.icon_manager.get_icon(
                    "energy_spectrum_icon_16.png")
                self.parent.add_widget(self.parent.energy_spectrum_widget,
                                       icon=icon)

                cuts = ", ".join(str(cut) for cut in selected_cuts)
                msg = f"Created Energy Spectrum. " \
                      f"Bin width: {width}. " \
                      f"Cut files: {cuts}"
                self.measurement.log(msg)
                log_info = "Energy Spectrum graph points:\n"
                data = self.parent.energy_spectrum_widget.energy_spectrum_data
                splitinfo = "\n".join([
                    "{0}: {1}".format(
                        key,
                        ", ".join("({0};{1})".format(round(v[0], 2), v[1])
                                  for v in data[key])) for key in data.keys()
                ])
                self.measurement.log(log_info + splitinfo)
            else:
                QtWidgets.QMessageBox.critical(
                    self, "Error",
                    "An error occurred while trying to create energy spectrum.",
                    QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            self.close()
        else:
            self.status_msg = "Please select .cut file[s] to create energy " \
                              "spectra."

    def __import_external_file(self):
        """
        Import an external file that matches the format of hist and simu files.
        """
        QtWidgets.QMessageBox.information(
            self, "Notice",
            "The external file needs to have the following format:\n\n"
            "energy count\nenergy count\nenergy count\n...\n\n"
            "to match the simulation and measurement energy spectra files.",
            QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
        file_path = fdialogs.open_file_dialog(
            self, self.element_simulation.request.directory,
            "Select a file to import", "")
        if file_path is None:
            return

        name = file_path.name

        new_file_name = \
            self.element_simulation.request.get_imported_files_folder() / name

        if new_file_name.exists():
            QtWidgets.QMessageBox.critical(
                self, "Error", "A file with that name already exists.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return

        shutil.copyfile(file_path, new_file_name)

        item = QtWidgets.QTreeWidgetItem()
        item.setText(0, new_file_name.name)
        item.setData(0, QtCore.Qt.UserRole, new_file_name)
        item.setCheckState(0, QtCore.Qt.Checked)
        self.external_tree_widget.addTopLevelItem(item)

    def __update_eff_files(self):
        """Update efficiency files to UI which are used.
        """
        if self.spectrum_type == _SIMU:
            # Simulation energy spectrum can contain cut files from multiple
            # Measurements which each can have different Detector an thus
            # different efficiency files
            label_txt = df.get_multi_efficiency_text(
                self.tof_list_tree_widget,
                self.simulation.sample.get_measurements(),
                data_func=lambda tpl: tpl[0])
        else:
            detector = self.measurement.get_detector_or_default()
            label_txt = df.get_efficiency_text(self.treeWidget, detector)

        self.label_efficiency_files.setText(label_txt)
コード例 #25
0
class RecoilInfoDialog(QtWidgets.QDialog,
                       bnd.PropertyBindingWidget,
                       metaclass=gutils.QtABCMeta):
    """Dialog for editing the name, description and reference density
    of a recoil element.
    """
    # TODO possibly track name changes
    name = bnd.bind("nameLineEdit")
    description = bnd.bind("descriptionLineEdit")
    reference_density = bnd.bind("scientific_spinbox")

    @property
    def color(self):
        return self.tmp_color.name()

    def __init__(self, recoil_element: RecoilElement, colormap,
                 element_simulation: ElementSimulation):
        """Inits a recoil info dialog.

        Args:
            recoil_element: A RecoilElement object.
            colormap: Colormap for elements.
            element_simulation: Element simulation that has the recoil element.
        """
        super().__init__()
        self.recoil_element = recoil_element
        self.element_simulation = element_simulation

        self.tmp_color = QColor(self.recoil_element.color)
        self.colormap = colormap

        value = self.recoil_element.reference_density
        self.scientific_spinbox = ScientificSpinBox(value=value,
                                                    minimum=0.01,
                                                    maximum=9.99e23)

        uic.loadUi(gutils.get_ui_dir() / "ui_recoil_info_dialog.ui", self)

        self.okPushButton.clicked.connect(self.__accept_settings)
        self.cancelPushButton.clicked.connect(self.close)
        self.colorPushButton.clicked.connect(self.__change_color)

        self.fields_are_valid = True
        iv.set_input_field_red(self.nameLineEdit)
        self.nameLineEdit.textChanged.connect(
            lambda: iv.check_text(self.nameLineEdit, qwidget=self))
        self.nameLineEdit.textEdited.connect(self.__validate)

        self.name = recoil_element.name
        self.description = recoil_element.description
        self.formLayout.insertRow(
            4, QtWidgets.QLabel(r"Reference density [at./cm<sup>3</sup>]:"),
            self.scientific_spinbox)
        self.formLayout.removeRow(self.widget)

        self.description = recoil_element.description
        self.isOk = False

        self.dateLabel.setText(
            time.strftime(
                "%c %z %Z",
                time.localtime(self.recoil_element.modification_time)))

        title = f"Recoil element: " \
                f"{self.recoil_element.element.get_prefix()}"

        self.infoGroupBox.setTitle(title)

        self.__set_color_button_color(self.recoil_element.element.symbol)

        self.exec_()

    def __density_valid(self):
        """
        Check if density value is valid and in limits.

        Return:
            True or False.
        """
        try:
            self.scientific_spinbox.get_value()
            return True
        except TypeError:
            return False

    def __accept_settings(self):
        """Function for accepting the current settings and closing the dialog
        window.
        """
        if not self.fields_are_valid or not self.__density_valid():
            QtWidgets.QMessageBox.critical(
                self, "Warning", "Some of the setting values are invalid.\n"
                "Please input values in fields indicated in red.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return

        if self.name != self.recoil_element.name:
            # Check that the new name is not already in use
            if self.name in (
                    r.name for r in  # has_recoil
                    self.element_simulation.recoil_elements):
                QtWidgets.QMessageBox.critical(
                    self, "Warning",
                    "Name of the recoil element is already in use. Please use "
                    "a different name", QtWidgets.QMessageBox.Ok,
                    QtWidgets.QMessageBox.Ok)
                return

        # If current recoil is used in a running simulation
        if self.recoil_element is \
                self.element_simulation.get_main_recoil():
            if (self.element_simulation.is_simulation_running() or
                    self.element_simulation.is_optimization_running()) and \
                    self.name != self.recoil_element.name:
                reply = QtWidgets.QMessageBox.question(
                    self, "Recoil used in simulation",
                    "This recoil is used in a simulation that is "
                    "currently running.\nIf you change the name of "
                    "the recoil, the running simulation will be "
                    "stopped.\n\n"
                    "Do you want to save changes anyway?",
                    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
                    | QtWidgets.QMessageBox.Cancel,
                    QtWidgets.QMessageBox.Cancel)
                if reply == QtWidgets.QMessageBox.No or reply == \
                        QtWidgets.QMessageBox.Cancel:
                    return
                else:
                    self.element_simulation.stop()

        self.isOk = True
        self.close()

    def __change_color(self):
        """
        Change the color of the recoil element.
        """
        dialog = QtWidgets.QColorDialog(self)
        color = dialog.getColor(self.tmp_color)
        if color.isValid():
            self.tmp_color = color
            self.__change_color_button_color(
                self.recoil_element.element.symbol)

    def __change_color_button_color(self, element: str):
        """
        Change color button's color.

        Args:
            element: String representing element name.
        """
        df.set_btn_color(self.colorPushButton, self.tmp_color, self.colormap,
                         element)

    def __set_color_button_color(self, element):
        """Set default color of element to color button.

        Args:
            element: String representing element.
        """
        self.colorPushButton.setEnabled(True)
        self.tmp_color = QColor(self.recoil_element.color)
        self.__change_color_button_color(element)

    def __validate(self):
        """
        Validate the recoil name.
        """
        text = self.name
        regex = "^[A-Za-z0-9-ÖöÄäÅå]*"
        valid_text = iv.validate_text_input(text, regex)

        self.name = valid_text
コード例 #26
0
class PointCoordinatesWidget(QtWidgets.QWidget):
    """
    Class for handling point coordinates spin boxes.
    """
    x_coord = bnd.bind("x_coordinate_box")
    y_coord = bnd.bind("y_coordinate_box")

    coord_changed = pyqtSignal()

    def __init__(self, parent, optimize=False, full_edit_changed=None):
        """Initializes the widget.

        Args:
            parent: RecoilAtomDistributionWidget.
        """
        super().__init__()

        self.parent = parent

        vertical_layout = QtWidgets.QVBoxLayout()
        vertical_layout.setContentsMargins(0, 0, 0, 0)
        horizontal_layout_x = QtWidgets.QHBoxLayout()
        horizontal_layout_x.setContentsMargins(0, 0, 0, 0)

        horizontal_layout_y = QtWidgets.QHBoxLayout()
        horizontal_layout_y.setContentsMargins(0, 0, 0, 0)

        # Point x coordinate spinbox
        self.x_coordinate_box = QtWidgets.QDoubleSpinBox(self)

        # Set decimal pointer to .
        self.x_coordinate_box.setLocale(self.parent.locale)
        self.x_coordinate_box.setToolTip("X coordinate of selected point")
        self.x_coordinate_box.setSingleStep(0.1)
        self.x_coordinate_box.setDecimals(2)
        self.x_coordinate_box.setMinimum(0)
        self.x_coordinate_box.setMaximum(1000000000000)
        self.x_coordinate_box.setMaximumWidth(62)
        self.x_coordinate_box.setKeyboardTracking(False)
        if not optimize:
            self.x_coordinate_box.valueChanged.connect(
                self.parent.set_selected_point_x)
            self.x_coordinate_box.setContextMenuPolicy(Qt.ActionsContextMenu)
            self.actionXMultiply = QtWidgets.QAction(self)
            self.actionXMultiply.setText("Multiply coordinate...")
            self.actionXMultiply.triggered.connect(
                lambda: self.__multiply_coordinate(self.x_coordinate_box))
            self.x_coordinate_box.addAction(self.actionXMultiply)
            self.set_x_enabled(False)

        # X label
        label_x = QtWidgets.QLabel("x:")

        # Point y coordinate spinbox
        self.y_coordinate_box = QtWidgets.QDoubleSpinBox(self)
        # Set decimal pointer to .
        self.y_coordinate_box.setLocale(self.parent.locale)
        self.y_coordinate_box.setToolTip("Y coordinate of selected point")
        self.y_coordinate_box.setSingleStep(0.1)
        self.y_coordinate_box.setDecimals(4)
        self.y_coordinate_box.setMaximum(1000000000000)
        self.y_coordinate_box.setMaximumWidth(62)
        self.y_coord = 1.0
        self.set_y_min()
        self.y_coordinate_box.setKeyboardTracking(False)
        if not optimize:
            self.y_coordinate_box.valueChanged.connect(
                self.parent.set_selected_point_y)
            self.y_coordinate_box.setContextMenuPolicy(Qt.ActionsContextMenu)
            self.actionYMultiply = QtWidgets.QAction(self)
            self.actionYMultiply.setText("Multiply coordinate...")
            self.actionYMultiply.triggered.connect(
                lambda: self.__multiply_coordinate(self.y_coordinate_box))
            self.y_coordinate_box.addAction(self.actionYMultiply)

            self.y_coordinate_box.editingFinished.connect(
                self.coord_changed.emit)
            self.x_coordinate_box.editingFinished.connect(
                self.coord_changed.emit)

        self.set_y_enabled(False)

        # Y label
        label_y = QtWidgets.QLabel("y:")

        if platform.system() == "Darwin" or platform.system() == "Linux":
            self.x_coordinate_box.setMinimumWidth(70)
            self.y_coordinate_box.setMinimumWidth(70)

        horizontal_layout_x.addWidget(label_x)
        horizontal_layout_x.addWidget(self.x_coordinate_box)

        horizontal_layout_y.addWidget(label_y)
        horizontal_layout_y.addWidget(self.y_coordinate_box)

        vertical_layout.addLayout(horizontal_layout_x)
        vertical_layout.addLayout(horizontal_layout_y)

        self.setLayout(vertical_layout)

        self._full_edit_changed = full_edit_changed
        if full_edit_changed is not None:
            full_edit_changed.connect(self.set_y_min)

    def closeEvent(self, event):
        """Disconnects full edit signal when closing.
        """
        try:
            self._full_edit_changed.disconnect(self.set_y_min)
        except AttributeError:
            pass
        super().closeEvent(event)

    def set_y_min(self, point: Optional[Point] = None):
        """Sets the minimum value of y spinbox depending on whether full edit
        is on or not.
        """
        try:
            if self.parent.full_edit_on:
                minimum = 0.0
            else:
                minimum = min(
                    self.parent.get_minimum_concentration(), self.y_coord,
                    point.get_y() if point is not None else self.y_coord)
            self.y_coordinate_box.setMinimum(minimum)
        except AttributeError:
            self.y_coordinate_box.setMinimum(0.0)

    def set_x_enabled(self, b: bool):
        """Enables or disables x coordinate spinbox.
        """
        self.x_coordinate_box.setEnabled(b)

    def set_y_enabled(self, b: bool):
        """Enables or disables y coordinate spinbox.
        """
        self.y_coordinate_box.setEnabled(b)

    def __multiply_coordinate(self, spinbox: QtWidgets.QDoubleSpinBox):
        """Multiply the spinbox's value with the value in clipboard.

        Args:
            spinbox: Spinbox whose value is multiplied.
        """
        dialog = MultiplyCoordinateDialog(self.parent.ratio_str)
        if dialog.used_multiplier:
            multiplier = dialog.used_multiplier
            # Make backlog entry
            self.parent.current_recoil_element.save_current_points(
                self.parent.full_edit_on)

            if spinbox is self.x_coordinate_box:
                for point in reversed(self.parent.selected_points):
                    if point.get_y() == 0.0:
                        if self.parent.editing_restricted():
                            continue
                    coord = point.get_x()
                    new_coord = round(multiplier * coord, 3)
                    if new_coord > self.parent.target_thickness:
                        new_coord = self.parent.target_thickness
                    self.parent.set_selected_point_x(new_coord, point)

            else:
                for point in reversed(self.parent.selected_points):
                    if point.get_y() == 0.0:
                        if self.parent.editing_restricted():
                            continue
                    coord = point.get_y()
                    new_coord = round(multiplier * coord, 3)
                    self.parent.set_selected_point_y(new_coord, point)
コード例 #27
0
class LayerPropertiesDialog(QtWidgets.QDialog, bnd.PropertyTrackingWidget,
                            metaclass=gutils.QtABCMeta):
    """Dialog for adding a new layer or editing an existing one.
    """
    name = bnd.bind("nameEdit", track_change=True)
    thickness = bnd.bind("thicknessEdit", track_change=True)
    density = bnd.bind("densityEdit", track_change=True)

    def __init__(self, tab, layer=None, modify=False, simulation=None,
                 first_layer=False):
        """Inits a layer dialog.

        Args:
            tab: A SimulationTabWidget
            layer: Layer object to be modified. None if creating a new layer.
            modify: If dialog is used to modify a layer.
            simulation: A Simulation object.
            first_layer: Whether the dialog is used to add the first layer.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_layer_dialog.ui", self)

        self.tab = tab
        self.layer = layer
        self.ok_pressed = False
        self.simulation = simulation
        self.amount_mismatch = True

        self.fields_are_valid = True
        iv.set_input_field_red(self.nameEdit)
        self.nameEdit.textChanged.connect(
            lambda: iv.check_text(self.nameEdit, self))
        self.nameEdit.textEdited.connect(
            lambda: iv.sanitize_file_name(self.nameEdit))

        # Connect buttons to events
        self.addElementButton.clicked.connect(self.__add_element_layout)
        self.okButton.clicked.connect(self.__save_layer)
        self.cancelButton.clicked.connect(self.close)

        self.thicknessEdit.setMinimum(0.01)
        self.densityEdit.setMinimum(0.01)

        self.__original_properties = {}

        if self.layer:
            self.__show_layer_info()
        else:
            self.__add_element_layout()

        if first_layer:
            self.groupBox_2.hide()

        self.__close = True

        self.thicknessEdit.setLocale(QLocale.c())
        self.densityEdit.setLocale(QLocale.c())

        if modify:
            self.groupBox_2.hide()

        self.placement_under = True

        if platform.system() == "Darwin":
            self.setMinimumWidth(450)

        if platform.system() == "Linux":
            self.setMinimumWidth(470)

        self.exec_()

    def get_original_property_values(self):
        """Returns the original values of the properties.
        """
        return self.__original_properties

    def __save_layer(self):
        """Function for adding a new layer with given settings.
        """
        self.__check_if_settings_ok()
        self.__accept_settings()
        if self.__close:
            self.close()

    def __show_layer_info(self):
        """
        Show information of the current layer.
        """
        self.set_properties(name=self.layer.name,
                            thickness=self.layer.thickness,
                            density=self.layer.density)

        for elem in self.layer.elements:
            self.__add_element_layout(elem)

    def get_element_widgets(self):
        """Returns all ElementLayout child objects that the widget has."""
        return [
            child
            for child in self.scrollAreaWidgetContents.layout().children()
            if isinstance(child, ElementLayout)
        ]

    def __check_if_settings_ok(self):
        """Check that all the settings are okay.

        Return:
             True if the settings are okay and false if some required fields
             are empty.
        """
        settings_ok = True
        help_sum = 0
        elem_widgets = self.get_element_widgets()

        # Check if 'scrollArea' is empty (no elements).
        if not elem_widgets:
            iv.set_input_field_red(self.scrollArea)
            settings_ok = False

        # Check that the element specific settings are okay.
        for widget in elem_widgets:
            elem = widget.get_selected_element()
            if elem is None:
                settings_ok = False
            else:
                help_sum += elem.amount

        if isclose(help_sum, 1.0, rel_tol=1e-6) or isclose(help_sum, 100.0, rel_tol=1e-6):
            for widget in elem_widgets:
                iv.set_input_field_white(widget.amount_spinbox)
            self.amount_mismatch = False
        else:
            for widget in elem_widgets:
                iv.set_input_field_red(widget.amount_spinbox)
            settings_ok = False
            self.amount_mismatch = True
        self.fields_are_valid = settings_ok

    def elements_changed(self):
        """
        Check if elements have been changed in the layer.
        """
        return not all(e1 == e2 for e1, e2 in itertools.zip_longest(
            self.find_elements(), self.layer.elements
        ))

    def find_elements(self):
        """
        Find all the layer's element from the dialog.
        """
        return [
            child.get_selected_element()
            for child in self.get_element_widgets()
        ]

    def __accept_settings(self):
        """Function for accepting the current settings and closing the dialog
        window.
        """
        if not self.fields_are_valid:
            if self.amount_mismatch:
                hint = "(Hint: element amounts need to sum up to either 1 or " \
                       "100.)"
            else:
                hint = ""
            QtWidgets.QMessageBox.critical(self, "Warning",
                                           "Some of the parameter values have"
                                           " not been set.\n\n" +
                                           "Please input values in fields "
                                           "indicated in red.\n" + hint,
                                           QtWidgets.QMessageBox.Ok,
                                           QtWidgets.QMessageBox.Ok)
            self.__close = False
            return

        if self.layer and not (self.are_values_changed() or
                               self.elements_changed()):
            self.__close = True
            self.fields_are_valid = True
            self.ok_pressed = False  # No update needed
            return

        if self.simulation is not None:
            if not df.delete_element_simulations(self,
                                                 self.simulation,
                                                 tab=self.tab,
                                                 msg="target"):
                self.__close = False
                return

        elements = self.find_elements()

        if self.layer:
            self.layer.name = self.name
            self.layer.elements = elements
            self.layer.thickness = self.thickness
            self.layer.density = self.density
        else:
            self.layer = Layer(self.name, elements, self.thickness,
                               self.density)
        if self.comboBox.currentText().startswith("Under"):
            self.placement_under = True
        else:
            self.placement_under = False
        self.ok_pressed = True
        self.__close = True

    def __missing_information_message(self, empty_fields):
        """Show the user a message about missing information.

        Args:
            empty_fields: Input fields that are empty.
        """
        fields = ""
        for field in empty_fields:
            fields += "  • " + field + "\n"
        QtWidgets.QMessageBox.critical(
            self.parent(),
            "Required information missing",
            "The following fields are still empty:\n\n" + fields +
            "\nFill out the required information in order to continue.",
            QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)

    def __add_element_layout(self, element=None):
        """Add element widget into view.
        """
        el = ElementLayout(self.scrollAreaWidgetContents, element, self)
        el.selection_changed.connect(self.__check_if_settings_ok)
        self.scrollArea.setStyleSheet("")
        self.scrollAreaWidgetContents.layout().addLayout(el)
コード例 #28
0
class DetectorSettingsWidget(QtWidgets.QWidget,
                             bnd.PropertyTrackingWidget,
                             metaclass=gutils.QtABCMeta):
    """Class for creating a detector settings tab.
    """
    # Key that is used to store the folder of the most recently added eff file
    EFF_FILE_FOLDER_KEY = "efficiency_folder"

    efficiency_files = bnd.bind("efficiencyListWidget")

    name = bnd.bind("nameLineEdit")
    modification_time = bnd.bind("dateLabel",
                                 fget=bnd.unix_time_from_label,
                                 fset=bnd.unix_time_to_label)
    description = bnd.bind("descriptionLineEdit")
    detector_type = bnd.bind("typeComboBox", track_change=True)
    angle_slope = bnd.bind("angleSlopeLineEdit", track_change=True)
    angle_offset = bnd.bind("angleOffsetLineEdit", track_change=True)
    tof_slope = bnd.bind("scientific_tof_slope", track_change=True)
    tof_offset = bnd.bind("scientific_tof_offset", track_change=True)
    timeres = bnd.bind("timeResSpinBox", track_change=True)
    virtual_size = bnd.multi_bind(
        ("virtualSizeXSpinBox", "virtualSizeYSpinBox"), track_change=True)

    def __init__(self,
                 obj: Detector,
                 request: Request,
                 icon_manager,
                 run=None):
        """Initializes a DetectorSettingsWidget object.

        Args:
              obj: a Detector object.
              request: Which request it belongs to.
              icon_manager: IconManager object.
              run: Run object. None if detector is default detector.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_request_detector_settings.ui",
                   self)

        self.obj = obj
        self.request = request
        self.icon_manager = icon_manager
        self.run = run
        self.__original_properties = {}

        # Temporary foils list which holds all the information given in the
        # foil dialog
        # If user presses ok or apply, these values will be saved into
        # request's default detector
        self.tmp_foil_info = []

        # List of foil indexes that are timing foils
        self.tof_foils = []

        # Add foil widgets and foil objects
        self.detector_structure_widgets = []
        self.foils_layout = self._add_default_foils(self.obj)
        self.detectorScrollAreaContents.layout().addLayout(self.foils_layout)
        self.newFoilButton.clicked.connect(
            lambda: self._add_new_foil(self.foils_layout))

        self.addEfficiencyButton.clicked.connect(self.__add_efficiency)
        self.removeEfficiencyButton.clicked.connect(self.__remove_efficiency)
        self.plotEfficiencyButton.clicked.connect(self.__plot_efficiency)

        self.efficiencyListWidget.itemSelectionChanged.connect(
            self._enable_remove_btn)
        self._enable_remove_btn()

        # Calibration settings
        # TODO: Require saving affected cuts if beam setting has been changed
        self.executeCalibrationButton.clicked.connect(
            self.__open_calibration_dialog)
        self.executeCalibrationButton.setEnabled(
            not self.request.samples.measurements.is_empty())

        gutils.fill_combobox(self.typeComboBox, DetectorType)

        self.fields_are_valid = False
        iv.set_input_field_red(self.nameLineEdit)
        self.nameLineEdit.textChanged.connect(
            lambda: iv.check_text(self.nameLineEdit, qwidget=self))
        self.nameLineEdit.textEdited.connect(
            lambda: iv.sanitize_file_name(self.nameLineEdit))
        self.nameLineEdit.setEnabled(False)

        locale = QLocale.c()
        self.timeResSpinBox.setLocale(locale)
        self.virtualSizeXSpinBox.setLocale(locale)
        self.virtualSizeYSpinBox.setLocale(locale)
        self.angleSlopeLineEdit.setLocale(locale)
        self.angleOffsetLineEdit.setLocale(locale)

        # Create scientific spinboxes for tof slope and tof offset
        self.formLayout_2.removeRow(self.slopeLineEdit)
        self.formLayout_2.removeRow(self.offsetLineEdit)

        self.scientific_tof_slope = ScientificSpinBox(minimum=-math.inf,
                                                      maximum=math.inf)
        self.scientific_tof_offset = ScientificSpinBox(minimum=-math.inf,
                                                       maximum=math.inf)

        self.formLayout_2.insertRow(0, "ToF slope [s/channel]:",
                                    self.scientific_tof_slope)
        self.formLayout_2.insertRow(1, "ToF offset[s]:",
                                    self.scientific_tof_offset)

        if platform.system() == "Darwin":
            self.scientific_tof_offset.scientificLineEdit.setFixedWidth(170)
            self.scientific_tof_slope.scientificLineEdit.setFixedWidth(170)

        # Save as and load
        self.saveButton.clicked.connect(self.__save_file)
        self.loadButton.clicked.connect(self.__load_file)

        self.show_settings()

    def get_original_property_values(self):
        """Returns the original values of the widget's properties.
        """
        return self.__original_properties

    def __load_file(self):
        """Load settings from file.
        """
        file = fdialogs.open_file_dialog(self, self.request.default_folder,
                                         "Select detector file",
                                         "Detector File (*.detector)")
        if file is None:
            return

        temp_detector = Detector.from_file(file, self.request, False)
        self.obj.set_settings(**temp_detector.get_settings())

        self.tmp_foil_info = []
        self.tof_foils = []
        self.detector_structure_widgets = []
        # Remove old widgets
        for i in range(self.detectorScrollAreaContents.layout().count()):
            layout_item = self.detectorScrollAreaContents.layout().itemAt(i)
            if layout_item == self.foils_layout:
                self.detectorScrollAreaContents.layout().removeItem(
                    layout_item)
                for j in reversed(range(layout_item.count())):
                    layout_item.itemAt(j).widget().deleteLater()

        # Add foil widgets and foil objects
        self.foils_layout = self._add_default_foils(temp_detector)
        self.detectorScrollAreaContents.layout().addLayout(self.foils_layout)

        self.show_settings()

    def __save_file(self):
        """Opens file dialog and sets and saves the settings to a file.
        """
        file = fdialogs.save_file_dialog(self, self.request.default_folder,
                                         "Save detector file",
                                         "Detector File (*.detector)")
        if file is None:
            return

        file = file.with_suffix(".detector")
        if not self.some_values_changed():
            self.obj.to_file(file)
        else:
            # Make temp detector, modify it according to widget values,
            # and write it to file.
            temp_detector = copy.deepcopy(self.obj)
            original_obj = self.obj
            self.obj = temp_detector
            self.update_settings()
            self.obj.to_file(file)
            self.obj = original_obj

    def show_settings(self):
        """Show Detector settings.
        """
        # Detector settings
        self.set_properties(**self.obj.get_settings())
        self.efficiency_files = self.obj.get_efficiency_files()

        # Detector foils
        self.tmp_foil_info = copy.deepcopy(self.obj.foils)
        self.calculate_distance()

        # Tof foils
        self.tof_foils = copy.deepcopy(self.obj.tof_foils)

    def update_settings(self):
        """Update detector settings.
        """
        self.obj.set_settings(**self.get_properties())
        # Detector foils
        self.calculate_distance()
        self.obj.foils = self.tmp_foil_info
        # Tof foils
        self.obj.tof_foils = self.tof_foils

    def some_values_changed(self):
        """Check if any detector settings values have been changed.

        Return:
            True or False.
        """
        if self.values_changed():
            return True
        if self.other_values_changed():
            return True
        return False

    def values_changed(self):
        """Check if detector settings values that require rerunning of the
        simulation have been changed.

        Return:
            True or False.
        """
        if self.are_values_changed():
            return True

        # TODO refactor foils
        # Detector foils
        self.calculate_distance()
        if self.foils_changed():
            return True
        # Tof foils
        if self.obj.tof_foils != self.tof_foils:
            return True

        return False

    def other_values_changed(self):
        """Check if detector settings values that don't require rerunning
        simulations have been changed.

        Return:
             True or False.
        """
        if self.obj.name != self.name:
            return True
        if self.obj.description != self.description:
            return True

        return False

    def foils_changed(self):
        """Check if detector foils have been changed.

        Return:
            True or False.
        """
        if len(self.obj.foils) != len(self.tmp_foil_info):
            return True
        for i in range(len(self.obj.foils)):
            foil = self.obj.foils[i]
            tmp_foil = self.tmp_foil_info[i]
            if type(foil) != type(tmp_foil):
                return True
            if foil.name != tmp_foil.name:
                return True
            if foil.distance != tmp_foil.distance:
                return True
            if foil.transmission != tmp_foil.transmission:
                return True
            # Check layers
            if self.layers_changed(foil, tmp_foil):
                return True
            if type(foil) is CircularFoil:
                if foil.diameter != tmp_foil.diameter:
                    return True
            else:
                if foil.size != tmp_foil.size:
                    return True
        return False

    def layers_changed(self, foil1, foil2):
        """Check if foil1 has different layers than foil2.

        Args:
            foil1: Foil object.
            foil2: Foil object.

        Return:
            True or False.
        """
        if len(foil1.layers) != len(foil2.layers):
            return True
        for i in range(len(foil1.layers)):
            layer1 = foil1.layers[i]
            layer2 = foil2.layers[i]
            if layer1.name != layer2.name:
                return True
            if layer1.thickness != layer2.thickness:
                return True
            if layer1.density != layer2.density:
                return True
            if layer1.start_depth != layer2.start_depth:
                return True
            # Check layer elements
            if self.layer_elements_changed(layer1, layer2):
                return True
        return False

    @staticmethod
    def layer_elements_changed(layer1, layer2):
        """Check if layer1 elements are different than layer2 elements.

        Args:
            layer1: Layer object.
            layer2: Layer object.

        Return:
            True or False.
        """
        if len(layer1.elements) != len(layer2.elements):
            return True
        for i in range(len(layer1.elements)):
            elem1 = layer1.elements[i]
            elem2 = layer2.elements[i]
            if elem1 != elem2:
                return True
        return False

    def _add_new_foil(self, layout, new_foil=None):
        """Add a new foil into detector.
        Args:
            layout: Layout into which the foil widget is added.
            new_foil: New Foil object to be added.
        """
        if self.tmp_foil_info:
            prev_distance = self.tmp_foil_info[-1].distance
        else:
            prev_distance = 0.0

        if new_foil is None:
            new_foil = CircularFoil(distance=prev_distance)

        foil_widget = FoilWidget(new_foil)
        foil_widget.distance_from_previous = new_foil.distance - prev_distance

        foil_widget.distanceDoubleSpinBox.valueChanged.connect(
            self.calculate_distance)
        foil_widget.foil_deletion.connect(self.delete_foil)
        foil_widget.foilButton.clicked.connect(self._open_foil_dialog)
        foil_widget.timingFoilCheckBox.stateChanged.connect(
            self._check_and_add)

        layout.addWidget(foil_widget)
        self.tmp_foil_info.append(new_foil)
        self.detector_structure_widgets.append(foil_widget)

        if len(self.tof_foils) >= 2:
            foil_widget.timingFoilCheckBox.setEnabled(False)
        return foil_widget

    def _add_default_foils(self, detector: Detector):
        """Add default foils as widgets.

        Return:
            Layout that holds the default foil widgets.
        """
        layout = QtWidgets.QHBoxLayout()

        foils = detector.foils
        for i in range(len(foils)):
            foil_widget = self._add_new_foil(layout, foils[i])
            for index in detector.tof_foils:
                if index == i:
                    foil_widget.timingFoilCheckBox.setChecked(True)
        return layout

    def _check_and_add(self):
        """Check if foil widget needs to be added into tof_foils list or
        removed from it.
        """
        check_box = self.sender()
        for i in range(len(self.detector_structure_widgets)):
            if self.detector_structure_widgets[i].timingFoilCheckBox is \
                    self.sender():
                if check_box.isChecked():
                    if self.tof_foils:
                        if self.tof_foils[0] > i:
                            self.tof_foils.insert(0, i)
                        else:
                            self.tof_foils.append(i)
                        if len(self.tof_foils) >= 2:
                            self._disable_checkboxes()
                    else:
                        self.tof_foils.append(i)
                else:
                    self.tof_foils.remove(i)
                    if 0 < len(self.tof_foils) < 2:
                        self._enable_checkboxes()
                break

    def _disable_checkboxes(self):
        """Disable selection of foil widgets as tof foil if they are not in the
        tof_foils list.
        """
        for i in range(len(self.detector_structure_widgets)):
            if i not in self.tof_foils:
                widget = self.detector_structure_widgets[i]
                widget.timingFoilCheckBox.setEnabled(False)

    def _enable_checkboxes(self):
        """Allow all foil widgets to be selected as tof foil.
        """
        for i in range(len(self.detector_structure_widgets)):
            widget = self.detector_structure_widgets[i]
            widget.timingFoilCheckBox.setEnabled(True)

    def _enable_remove_btn(self):
        """Sets the remove button either enabled or not depending whether an
        item is selected in the efficiency file list.
        """
        self.removeEfficiencyButton.setEnabled(
            bool(self.efficiencyListWidget.currentItem()))

    def _open_foil_dialog(self):
        """Open the FoilDialog which is used to modify the Foil object.
        """
        foil_name = self.sender().text()
        foil_object_index = -1
        for i in range(len(self.tmp_foil_info)):
            if foil_name == self.tmp_foil_info[i].name:
                foil_object_index = i
                break
        FoilDialog(self.tmp_foil_info, foil_object_index, self.icon_manager)
        self.sender().setText(self.tmp_foil_info[foil_object_index].name)

    def __add_efficiency(self):
        """Opens a dialog that allows the user to add efficiency files to
        the Efficiency_files folder of the detector.
        """
        eff_folder = gutils.get_potku_setting(
            DetectorSettingsWidget.EFF_FILE_FOLDER_KEY,
            self.request.default_folder)

        new_eff_files = fdialogs.open_files_dialog(self, eff_folder,
                                                   "Select efficiency files",
                                                   "Efficiency File (*.eff)")
        if not new_eff_files:
            return

        used_eff_files = {
            Detector.get_used_efficiency_file_name(f)
            for f in self.efficiency_files
        }

        for eff_file in new_eff_files:
            used_eff_file = Detector.get_used_efficiency_file_name(eff_file)

            if used_eff_file not in used_eff_files:
                try:
                    self.obj.add_efficiency_file(eff_file)
                    used_eff_files.add(used_eff_file)
                except OSError as e:
                    QtWidgets.QMessageBox.critical(
                        self, "Error",
                        f"Failed to add the efficiency file: {e}\n",
                        QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            else:
                QtWidgets.QMessageBox.critical(
                    self, "Error",
                    f"There already is an efficiency file for element "
                    f"{used_eff_file.stem}.\n", QtWidgets.QMessageBox.Ok,
                    QtWidgets.QMessageBox.Ok)

        self.efficiency_files = self.obj.get_efficiency_files()
        # Store the folder where we previously fetched an eff-file
        gutils.set_potku_setting(DetectorSettingsWidget.EFF_FILE_FOLDER_KEY,
                                 str(eff_file.parent))

    def __remove_efficiency(self):
        """Removes efficiency files from detector's efficiency directory and
        updates settings view.
        """
        self.efficiencyListWidget: QtWidgets.QListWidget
        selected_items = self.efficiencyListWidget.selectedItems()
        if selected_items:
            reply = QtWidgets.QMessageBox.question(
                self, "Confirmation",
                "Are you sure you want to delete selected efficiencies?",
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
                | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
            if reply == QtWidgets.QMessageBox.No or reply == \
                    QtWidgets.QMessageBox.Cancel:
                return

            for item in selected_items:
                selected_eff_file = item.data(QtCore.Qt.UserRole)
                self.obj.remove_efficiency_file(selected_eff_file)

            self.efficiency_files = self.obj.get_efficiency_files()
            self._enable_remove_btn()

    def __plot_efficiency(self):
        """
        Open efficiency plot widget
        """
        self.eff_folder = gutils.get_potku_setting(
            DetectorSettingsWidget.EFF_FILE_FOLDER_KEY,
            self.request.default_folder)
        self.efficiency_files = self.obj.get_efficiency_files()
        self.efficiency_files_list = []
        i = 0
        for file in self.efficiency_files:
            file_name = gf.get_root_dir() / self.eff_folder / str(
                self.efficiency_files[i])
            self.efficiency_files_list.append(file_name)
            i += 1
        EfficiencyDialog(self.efficiency_files_list, self)

    def __open_calibration_dialog(self):
        """
        Open the CalibrationDialog.
        """
        measurements = [
            self.request.samples.measurements.get_key_value(key)
            for key in self.request.samples.measurements.measurements
        ]
        CalibrationDialog(measurements, self.obj, self.run, self)

    def calculate_distance(self):
        """
        Calculate the distances of the foils from the target.
        """
        distance = 0
        for foil, widget in zip(self.tmp_foil_info,
                                self.detector_structure_widgets):
            distance += widget.distance_from_previous
            widget.cumulative_distance = distance
            foil.distance = distance

    def delete_foil(self, foil_widget):
        """Delete a foil from widgets and Foil objects.

        Args:
            foil_widget: Widget to be deleted. Its index is used to delete
            Foil objects as well.
        """
        index_of_item_to_be_deleted = self.detector_structure_widgets.index(
            foil_widget)
        del (self.detector_structure_widgets[index_of_item_to_be_deleted])
        foil_to_be_deleted = self.tmp_foil_info[index_of_item_to_be_deleted]
        # Check if foil to be deleted is in tof_foils and remove it fro the
        # tof_foils list if it is.
        if index_of_item_to_be_deleted in self.tof_foils:
            self.tof_foils.remove(index_of_item_to_be_deleted)
            if 0 < len(self.tof_foils) < 2:
                self._enable_checkboxes()
        self.tmp_foil_info.remove(foil_to_be_deleted)
        for i in range(len(self.tof_foils)):
            if self.tof_foils[i] > index_of_item_to_be_deleted:
                self.tof_foils[i] = self.tof_foils[i] - 1

        self.foils_layout.removeWidget(foil_widget)
        foil_widget.deleteLater()
        self.calculate_distance()
コード例 #29
0
class GlobalSettingsDialog(QtWidgets.QDialog):
    """
    A GlobalSettingsDialog.
    """
    color_scheme = bnd.bind("combo_tofe_colors")
    tofe_invert_x = bnd.bind("check_tofe_invert_x")
    tofe_invert_y = bnd.bind("check_tofe_invert_y")
    tofe_transposed = bnd.bind("check_tofe_transpose")
    tofe_bin_x = bnd.multi_bind(
        ("spin_tofe_bin_x_min", "spin_tofe_bin_x_max")
    )
    tofe_bin_y = bnd.multi_bind(
        ("spin_tofe_bin_y_min", "spin_tofe_bin_y_max")
    )
    comp_x = bnd.bind("spin_tofe_compression_x")
    comp_y = bnd.bind("spin_tofe_compression_y")

    depth_iters = bnd.bind("spin_depth_iterations")

    presim_ions = bnd.bind("presim_spinbox")
    sim_ions = bnd.bind("sim_spinbox")
    ion_division = bnd.bind("ion_division_radios")
    min_concentration = bnd.bind("min_conc_spinbox")

    coinc_count = bnd.bind("line_coinc_count")

    cross_section = bnd.bind("cross_section_radios")

    save_window_geometries = bnd.bind("window_geom_chkbox")

    settings_updated = QtCore.pyqtSignal(GlobalSettings)

    def __init__(self, settings: GlobalSettings):
        """Constructor for the program
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_global_settings.ui", self)

        self.settings = settings
        self.__added_timings = {}  # Placeholder for timings
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        gutils.set_min_max_handlers(
            self.spin_tofe_bin_x_min, self.spin_tofe_bin_x_max
        )
        gutils.set_min_max_handlers(
            self.spin_tofe_bin_y_min, self.spin_tofe_bin_y_max
        )
        gutils.set_btn_group_data(self.ion_division_radios, IonDivision)
        gutils.set_btn_group_data(self.cross_section_radios, CrossSection)
        gutils.fill_combobox(self.combo_tofe_colors, ToFEColorScheme)

        # Connect UI buttons
        self.OKButton.clicked.connect(self.__accept_changes)
        self.cancelButton.clicked.connect(self.close)
        buttons = self.findChild(QtWidgets.QButtonGroup, "elementButtons")
        buttons.buttonClicked.connect(self.__change_element_color)

        self.min_conc_spinbox.setLocale(QtCore.QLocale.c())

        if platform.system() == "Darwin":
            self.gridLayout.setVerticalSpacing(15)
            self.gridLayout.setHorizontalSpacing(15)

        self.__set_values()

    def closeEvent(self, event):
        """Disconnects settings updated signal before closing.
        """
        try:
            self.settings_updated.disconnect()
        except AttributeError:
            pass
        super().closeEvent(event)

    def __set_values(self):
        """Set settings values to dialog.
        """
        for button in self.groupBox_3.findChildren(QtWidgets.QPushButton):
            self.set_btn_color(
                button, self.settings.get_element_color(button.text()))

        label_adc = QtWidgets.QLabel("ADC")
        label_low = QtWidgets.QLabel("Low")
        label_high = QtWidgets.QLabel("High")
        self.grid_timing.addWidget(label_adc, 0, 0)
        self.grid_timing.addWidget(label_low, 1, 0)
        self.grid_timing.addWidget(label_high, 2, 0)
        for i in range(3):
            timing = self.settings.get_import_timing(i)
            label = QtWidgets.QLabel(f"{i}")
            spin_low = self.__create_spinbox(timing[0])
            spin_high = self.__create_spinbox(timing[1])
            self.__added_timings[i] = CoincTiming(i, spin_low, spin_high)
            self.grid_timing.addWidget(label, 0, i + 1)
            self.grid_timing.addWidget(spin_low, 1, i + 1)
            self.grid_timing.addWidget(spin_high, 2, i + 1)
        self.coinc_count = self.settings.get_import_coinc_count()
        # self.__set_cross_sections()
        self.cross_section = self.settings.get_cross_sections()

        # ToF-E graph settings
        self.tofe_invert_x = self.settings.get_tofe_invert_x()
        self.tofe_invert_y = self.settings.get_tofe_invert_y()
        self.tofe_transposed = self.settings.get_tofe_transposed()

        # TODO binding for bin mode
        tofe_bin_mode = self.settings.get_tofe_bin_range_mode()
        self.radio_tofe_bin_auto.setChecked(tofe_bin_mode == 0)
        self.radio_tofe_bin_manual.setChecked(tofe_bin_mode == 1)
        self.tofe_bin_x = self.settings.get_tofe_bin_range_x()
        self.tofe_bin_y = self.settings.get_tofe_bin_range_y()

        self.comp_x = self.settings.get_tofe_compression_x()
        self.comp_y = self.settings.get_tofe_compression_y()

        self.depth_iters = self.settings.get_num_iterations()

        self.presim_ions = self.settings.get_min_presim_ions()
        self.sim_ions = self.settings.get_min_simulation_ions()
        self.ion_division = self.settings.get_ion_division()
        self.min_conc_spinbox.setMinimum(GlobalSettings.MIN_CONC_LIMIT)
        self.min_concentration = self.settings.get_minimum_concentration()

        self.save_window_geometries = gutils.get_potku_setting(
            BaseTab.SAVE_WINDOW_GEOM_KEY, True
        )
        self.color_scheme = self.settings.get_tofe_color()

    @staticmethod
    def __create_spinbox(default):
        """
        Create a spinbox.
        """
        spinbox = QtWidgets.QSpinBox()
        spinbox.stepBy(1)
        spinbox.setMinimum(-1000)
        spinbox.setMaximum(1000)
        spinbox.setValue(int(default))
        return spinbox

    def __accept_changes(self):
        """Accept changed settings and save.
        """
        for button in self.groupBox_3.findChildren(QtWidgets.QPushButton):
            self.settings.set_element_color(button.text(), button.color)
        for key, coinc_timing in self.__added_timings.items():
            self.settings.set_import_timing(
                key, coinc_timing.low.value(), coinc_timing.high.value())
        self.settings.set_import_coinc_count(self.coinc_count)

        self.settings.set_cross_sections(self.cross_section)

        # ToF-E graph settings
        self.settings.set_tofe_invert_x(self.tofe_invert_x)
        self.settings.set_tofe_invert_y(self.tofe_invert_y)
        self.settings.set_tofe_transposed(self.tofe_transposed)
        self.settings.set_tofe_color(self.color_scheme)
        if self.radio_tofe_bin_auto.isChecked():
            self.settings.set_tofe_bin_range_mode(0)
        elif self.radio_tofe_bin_manual.isChecked():
            self.settings.set_tofe_bin_range_mode(1)

        self.settings.set_tofe_bin_range_x(*self.tofe_bin_x)
        self.settings.set_tofe_bin_range_y(*self.tofe_bin_y)
        self.settings.set_tofe_compression_x(self.comp_x)
        self.settings.set_tofe_compression_y(self.comp_y)
        self.settings.set_num_iterations(self.depth_iters)
        self.settings.set_min_presim_ions(self.presim_ions)
        self.settings.set_min_simulation_ions(self.sim_ions)
        self.settings.set_ion_division(self.ion_division)
        self.settings.set_minimum_concentration(self.min_concentration)

        gutils.set_potku_setting(
            BaseTab.SAVE_WINDOW_GEOM_KEY, self.save_window_geometries)

        # Save config and close
        self.settings.save_config()
        self.settings_updated.emit(self.settings)
        self.close()

    def __change_request_directory(self):
        """Change default request directory.
        """
        folder = QtWidgets.QFileDialog.getExistingDirectory(
            self, "Select default request directory",
            directory=self.requestPathLineEdit.text())
        if folder:
            self.requestPathLineEdit.setText(folder)

    def __change_element_color(self, button):
        """Change color of element button.
        
        Args:
            button: QPushButton
        """
        dialog = QtWidgets.QColorDialog(self)
        self.color = dialog.getColor(
            QtGui.QColor(button.color), self,
            f"Select Color for Element: {button.text()}")
        if self.color.isValid():
            self.set_btn_color(button, self.color.name())

    @staticmethod
    def set_btn_color(button, color_name):
        """Change button text color.
        
        Args:
            button: QPushButton
            color_name: String representing color.
        """
        color = QtGui.QColor(color_name)
        style = df.get_btn_stylesheet(color)

        button.color = color.name()
        if not button.isEnabled():
            return  # Do not set color for disabled buttons.
        button.setStyleSheet(style)
コード例 #30
0
ファイル: settings.py プロジェクト: aleekaup2/potku
class MeasurementSettingsDialog(QtWidgets.QDialog):
    """
    Dialog class for handling the measurement parameter input.
    """
    use_request_settings = bnd.bind("defaultSettingsCheckBox")

    def __init__(self, tab, measurement: Measurement, icon_manager):
        """
        Initializes the dialog.

        Args:
            measurement: A Measurement object whose parameters are handled.
            icon_manager: An icon manager.
        """
        super().__init__()
        uic.loadUi(gutils.get_ui_dir() / "ui_specific_settings.ui", self)
        self.warning_text = bnd.bind('warning_text')

        self.tab = tab
        self.measurement = measurement
        self.icon_manager = icon_manager
        self.setWindowTitle("Measurement Settings")
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        screen_geometry = QtWidgets.QDesktopWidget.availableGeometry(
            QtWidgets.QApplication.desktop())
        self.resize(int(self.geometry().width() * 1.2),
                    int(screen_geometry.size().height() * 0.8))
        self.defaultSettingsCheckBox.stateChanged.connect(
            self._change_used_settings)
        self.OKButton.clicked.connect(self._save_settings_and_close)
        self.applyButton.clicked.connect(self._update_parameters)
        self.cancelButton.clicked.connect(self.close)

        preset_folder = gutils.get_preset_dir(
            self.measurement.request.global_settings)
        # Add measurement settings view to the settings view
        self.measurement_settings_widget = MeasurementSettingsWidget(
            self.measurement, preset_folder=preset_folder)
        self.tabs.addTab(self.measurement_settings_widget, "Measurement")

        self.measurement_settings_widget.beam_selection_ok.connect(
            self.OKButton.setEnabled)

        # Add detector settings view to the settings view
        self.detector_settings_widget = DetectorSettingsWidget(
            self.measurement.detector, self.measurement.request,
            self.icon_manager, self.measurement_settings_widget.tmp_run)

        self.tabs.addTab(self.detector_settings_widget, "Detector")

        self.use_request_settings = self.measurement.use_request_settings

        # TODO these should be set in the widget, not here
        self.measurement_settings_widget.nameLineEdit.setText(
            self.measurement.measurement_setting_file_name)
        self.measurement_settings_widget.descriptionPlainTextEdit.setPlainText(
            self.measurement.measurement_setting_file_description)
        self.measurement_settings_widget.dateLabel.setText(
            time.strftime("%c %z %Z",
                          time.localtime(self.measurement.modification_time)))

        # Add profile settings view to the settings view
        self.profile_settings_widget = ProfileSettingsWidget(
            self.measurement, preset_folder=preset_folder)
        self.tabs.addTab(self.profile_settings_widget, "Profile")

        self.tabs.currentChanged.connect(lambda: df.check_for_red(self))

        self.exec()

    def _change_used_settings(self):
        check_box = self.sender()
        if check_box.isChecked():
            self.tabs.setEnabled(False)
        else:
            self.tabs.setEnabled(True)

    def _remove_extra_files(self):
        gf.remove_matching_files(self.measurement.directory,
                                 exts={".measurement", ".profile", ".target"})
        gf.remove_matching_files(self.measurement.directory / "Detector",
                                 exts={".detector"})

    def _update_parameters(self):
        if self.measurement_settings_widget.isotopeComboBox.currentIndex() \
                == -1:
            QtWidgets.QMessageBox.critical(
                self, "Warning",
                "No isotope selected.\n\nPlease select an isotope for the beam "
                "element.", QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return False

        if not self.measurement.measurement_setting_file_name:
            self.measurement.measurement_setting_file_name = \
                self.measurement.name

        # Copy request settings without checking their validity. They
        # have been checked once in request settings anyway.
        if self.use_request_settings:
            self.measurement.use_request_settings = True

            # Remove measurement-specific efficiency files
            if self.measurement.detector is not \
                    self.measurement.request.default_detector:
                self.measurement.detector.remove_efficiency_files()

            self.measurement.clone_request_settings()

            self._remove_extra_files()
            self.measurement.to_file()
            return True

        # Check the target and detector angles
        if not self.measurement_settings_widget.check_angles():
            return False

        if not self.tabs.currentWidget().fields_are_valid:
            QtWidgets.QMessageBox.critical(
                self, "Warning",
                "Some of the setting values have not been set.\n"
                "Please input values in fields indicated in red.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
            return False

        # Use Measurement specific settings
        try:
            self.measurement.use_request_settings = False

            # Set Detector object to settings widget
            self.detector_settings_widget.obj = self.measurement.detector

            # Update settings
            self.measurement_settings_widget.update_settings()
            self.detector_settings_widget.update_settings()
            self.profile_settings_widget.update_settings()

            self._remove_extra_files()
            self.measurement.to_file()
            return True

        except TypeError:
            QtWidgets.QMessageBox.question(
                self, "Warning",
                "Some of the setting values have not been set.\n"
                "Please input setting values to save them.",
                QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)

        return False

    def _save_settings_and_close(self):
        """ Save settings and close dialog if __update_parameters returns True.
        """
        if self._update_parameters():
            self.tab.check_default_settings_clicked()
            self.close()