grouping_tab_widget_presenter.py 17.5 KB
Newer Older
1
2
3
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI,
4
5
#   NScD Oak Ridge National Laboratory, European Spallation Source,
#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
# SPDX - License - Identifier: GPL - 3.0 +
7
from mantidqt.utils.observer_pattern import Observer, Observable, GenericObservable,GenericObserver
Matthew Andrew's avatar
Matthew Andrew committed
8
9
10
import Muon.GUI.Common.utilities.muon_file_utils as file_utils
import Muon.GUI.Common.utilities.xml_utils as xml_utils
import Muon.GUI.Common.utilities.algorithm_utils as algorithm_utils
11
from Muon.GUI.Common import thread_model
12
from Muon.GUI.Common.run_selection_dialog import RunSelectionDialog
13
from Muon.GUI.Common.thread_model_wrapper import ThreadModelWrapper
14
from Muon.GUI.Common.utilities.run_string_utils import run_string_to_list
Matt Cumber's avatar
Matt Cumber committed
15
from Muon.GUI.Common.muon_period_info import MuonPeriodInfoWidget, HEADER_COLUMN_MAP, PERIOD_INFO_NOT_FOUND
16
17
18
19
20
21
22
23


class GroupingTabPresenter(object):
    """
    The grouping tab presenter is responsible for synchronizing the group and pair tables. It also maintains
    functionality which covers both groups/pairs ; e.g. loading/saving/updating data.
    """

24
25
26
27
    @staticmethod
    def string_to_list(text):
        return run_string_to_list(text)

28
29
    def __init__(self, view, model,
                 grouping_table_widget=None,
30
31
                 pairing_table_widget=None,
                 diff_table = None):
32
33
34
35
36
        self._view = view
        self._model = model

        self.grouping_table_widget = grouping_table_widget
        self.pairing_table_widget = pairing_table_widget
37
        self.diff_table = diff_table
38
        self.period_info_widget = MuonPeriodInfoWidget()
39

Matt Cumber's avatar
Matt Cumber committed
40
        self._view.set_description_text('')
41
42
43
44
45
        self._view.on_add_pair_requested(self.add_pair_from_grouping_table)
        self._view.on_clear_grouping_button_clicked(self.on_clear_requested)
        self._view.on_load_grouping_button_clicked(self.handle_load_grouping_from_file)
        self._view.on_save_grouping_button_clicked(self.handle_save_grouping_file)
        self._view.on_default_grouping_button_clicked(self.handle_default_grouping_button_clicked)
46
        self._view.on_period_information_button_clicked(self.handle_period_information_button_clicked)
47
48
49
50
51
52
53
54

        # monitors for loaded data changing
        self.loadObserver = GroupingTabPresenter.LoadObserver(self)
        self.instrumentObserver = GroupingTabPresenter.InstrumentObserver(self)

        # notifiers
        self.groupingNotifier = GroupingTabPresenter.GroupingNotifier(self)
        self.grouping_table_widget.on_data_changed(self.group_table_changed)
55
        self.diff_table.on_data_changed(self.diff_table_changed)
56
        self.pairing_table_widget.on_data_changed(self.pair_table_changed)
57
58
        self.enable_editing_notifier = GroupingTabPresenter.EnableEditingNotifier(self)
        self.disable_editing_notifier = GroupingTabPresenter.DisableEditingNotifier(self)
59
        self.calculation_finished_notifier = GenericObservable()
60
61
62

        self.guessAlphaObserver = GroupingTabPresenter.GuessAlphaObserver(self)
        self.pairing_table_widget.guessAlphaNotifier.add_subscriber(self.guessAlphaObserver)
63
        self.message_observer = GroupingTabPresenter.MessageObserver(self)
64
        self.gui_variables_observer = GroupingTabPresenter.GuiVariablesChangedObserver(self)
65
66
        self.enable_observer = GroupingTabPresenter.EnableObserver(self)
        self.disable_observer = GroupingTabPresenter.DisableObserver(self)
67

68
69
70
        self.disable_tab_observer = GenericObserver(self.disable_editing_without_notifying_subscribers)
        self.enable_tab_observer = GenericObserver(self.enable_editing_without_notifying_subscribers)

71
72
73
74
        self.update_view_from_model_observer = GenericObserver(self.update_view_from_model)

    def update_view_from_model(self):
        self.grouping_table_widget.update_view_from_model()
75
        self.diff_table.update_view_from_model()
76
77
        self.pairing_table_widget.update_view_from_model()

78
79
80
81
82
83
84
85
86
87
    def show(self):
        self._view.show()

    def text_for_description(self):
        """
        Generate the text for the description edit at the top of the widget.
        """
        instrument = self._model.instrument
        n_detectors = self._model.num_detectors
        main_field = self._model.main_field_direction
Matt Cumber's avatar
Matt Cumber committed
88
89
90
91
        text = "{}, {} detectors".format(
            instrument, n_detectors)
        if main_field:
            text += ", main field : {} to muon polarization".format(main_field)
92
93
        return text

94
95
96
    def update_description_text(self, description_text=''):
        if not description_text:
            description_text = self.text_for_description()
97
98
        self._view.set_description_text(description_text)

Matt Cumber's avatar
Matt Cumber committed
99
100
101
    def update_description_text_to_empty(self):
        self._view.set_description_text('')

102
103
    def add_pair_from_grouping_table(self, group_name1="", group_name2=""):

104
105
106
        """
        If user requests to add a pair from the grouping table.
        """
107
        self.pairing_table_widget.handle_add_pair_button_clicked(group_name1, group_name2)
108
109
110
111
112

    def handle_guess_alpha(self, pair_name, group1_name, group2_name):
        """
        Calculate alpha for the pair for which "Guess Alpha" button was clicked.
        """
113
        if len(self._model._data.current_runs) > 1:
114
115
            run, index, ok_clicked = RunSelectionDialog.get_run(self._model._data.current_runs,
                                                                self._model._data.instrument, self._view)
116
117
            if not ok_clicked:
                return
118
            run_to_use = self._model._data.current_runs[index]
119
        else:
120
            run_to_use = self._model._data.current_runs[0]
121

122
123
124
125
126
127
        try:
            ws1 = self._model.get_group_workspace(group1_name, run_to_use)
            ws2 = self._model.get_group_workspace(group2_name, run_to_use)
        except KeyError:
            self._view.display_warning_box('Group workspace not found, try updating all and then recalculating.')
            return
128

Matthew Andrew's avatar
Matthew Andrew committed
129
        ws = algorithm_utils.run_AppendSpectra(ws1, ws2)
130

Matthew Andrew's avatar
Matthew Andrew committed
131
132
133
        new_alpha = algorithm_utils.run_AlphaCalc({"InputWorkspace": ws,
                                                   "ForwardSpectra": [0],
                                                   "BackwardSpectra": [1]})
134

135
136
137
        self._model.update_pair_alpha(pair_name, new_alpha)
        self.pairing_table_widget.update_view_from_model()

138
        self.handle_update_all_clicked()
139
140
141
142
143
144

    def handle_load_grouping_from_file(self):
        # Only XML format
        file_filter = file_utils.filter_for_extensions(["xml"])
        filename = self._view.show_file_browser_and_return_selection(file_filter, [""])

145
146
147
        if filename == '':
            return

148
        groups, pairs, diffs, description, default = xml_utils.load_grouping_from_XML(filename)
149
150
151

        self._model.clear()
        for group in groups:
152
            try:
153
                self._model.add_group(group)
154
155
156
            except ValueError as error:
                self._view.display_warning_box(str(error))

157
        for pair in pairs:
158
159
160
161
162
            try:
                if pair.forward_group in self._model.group_names and pair.backward_group in self._model.group_names:
                    self._model.add_pair(pair)
            except ValueError as error:
                self._view.display_warning_box(str(error))
163
164
        for diff in diffs:
            try:
165
                if diff.positive in self._model.group_names and diff.negative in self._model.group_names:
166
                    self._model.add_diff(diff)
167
                elif diff.positive in self._model.pair_names and diff.negative in self._model.pair_names:
168
169
170
                    self._model.add_diff(diff)
            except ValueError as error:
                self._view.display_warning_box(str(error))
171
172
        # Sets the default from file if it exists, if not selected groups/pairs are set on the logic
        # Select all pairs if there are any pairs otherwise select all groups.
173
174
175
176
177
        if default:
            if default in self._model.group_names:
                self._model.add_group_to_analysis(default)
            elif default in self._model.pair_names:
                self._model.add_pair_to_analysis(default)
178
179
180

        self.grouping_table_widget.update_view_from_model()
        self.pairing_table_widget.update_view_from_model()
181
        self.diff_table.update_view_from_model()
182
        self.update_description_text(description)
183
        self._model._context.group_pair_context.selected = default
184
        self.plot_default_groups_or_pairs()
185
186
187
        self.groupingNotifier.notify_subscribers()

        self.handle_update_all_clicked()
188
189
190
191

    def disable_editing(self):
        self._view.set_buttons_enabled(False)
        self.grouping_table_widget.disable_editing()
192
        self.diff_table.disable_editing()
193
        self.pairing_table_widget.disable_editing()
194
        self.disable_editing_notifier.notify_subscribers()
195

Matthew Andrew's avatar
Matthew Andrew committed
196
    def enable_editing(self, result=None):
197
198
        self._view.set_buttons_enabled(True)
        self.grouping_table_widget.enable_editing()
199
        self.diff_table.enable_editing()
200
        self.pairing_table_widget.enable_editing()
201
        self.enable_editing_notifier.notify_subscribers()
202

203
204
205
    def disable_editing_without_notifying_subscribers(self):
        self._view.set_buttons_enabled(False)
        self.grouping_table_widget.disable_editing()
206
        self.diff_table.disable_editing()
207
208
209
210
211
        self.pairing_table_widget.disable_editing()

    def enable_editing_without_notifying_subscribers(self):
        self._view.set_buttons_enabled(True)
        self.grouping_table_widget.enable_editing()
212
        self.diff_table.enable_editing()
213
214
        self.pairing_table_widget.enable_editing()

215
    def calculate_all_data(self):
216
217
218
        self._model.show_all_groups_and_pairs()

    def handle_update_all_clicked(self):
219
        self.update_thread = self.create_update_thread()
220
        self.update_thread.threadWrapperSetUp(self.disable_editing,
221
                                              self.handle_update_finished,
222
                                              self.error_callback)
223
        self.update_thread.start()
224

225
    def error_callback(self, error_message):
226
        self.enable_editing()
227
228
        self._view.display_warning_box(error_message)

229
230
231
    def handle_update_finished(self):
        self.enable_editing()
        self.groupingNotifier.notify_subscribers()
232
        self.calculation_finished_notifier.notify_subscribers()
233

234
    def handle_default_grouping_button_clicked(self):
235
236
237
238
        status = self._model.reset_groups_and_pairs_to_default()
        if status == "failed":
            self._view.display_warning_box("The default may depend on the instrument. Please load a run.")
            return
239
        self._model.reset_selected_groups_and_pairs()
240
        self.grouping_table_widget.update_view_from_model()
241
        self.diff_table.update_view_from_model()
242
243
        self.pairing_table_widget.update_view_from_model()
        self.update_description_text()
244
245
        self.groupingNotifier.notify_subscribers()
        self.handle_update_all_clicked()
246
        self.plot_default_groups_or_pairs()
247
248
249
250

    def on_clear_requested(self):
        self._model.clear()
        self.grouping_table_widget.update_view_from_model()
251
        self.diff_table.update_view_from_model()
252
        self.pairing_table_widget.update_view_from_model()
Matt Cumber's avatar
Matt Cumber committed
253
        self.update_description_text_to_empty()
254
255
256

    def handle_new_data_loaded(self):
        if self._model.is_data_loaded():
Matthew Andrew's avatar
Matthew Andrew committed
257
            self._model._context.show_raw_data()
258
            self.update_view_from_model()
259
            self.update_description_text()
260
            self.handle_update_all_clicked()
261
            self.plot_default_groups_or_pairs()
262
263
264
265
266
267
        else:
            self.on_clear_requested()

    def handle_save_grouping_file(self):
        filename = self._view.show_file_save_browser_and_return_selection()
        if filename != "":
268
            xml_utils.save_grouping_to_XML(self._model.groups, self._model.pairs, self._model.diffs, filename,
269
                                           description=self._view.get_description_text())
270

271
272
273
    def create_update_thread(self):
        self._update_model = ThreadModelWrapper(self.calculate_all_data)
        return thread_model.ThreadModel(self._update_model)
274

275
276
    def plot_default_groups_or_pairs(self):
        # if we have no pairs or groups selected, generate a default plot
277
        if len(self._model.selected_groups_and_pairs) == 0:
278
279
280
281
282
            if len(self._model.pairs) > 0:  # if we have pairs - then plot all pairs
                self.pairing_table_widget.plot_default_case()
            else:  # else plot groups
                self.grouping_table_widget.plot_default_case()

283
284
285
286
287
288
289
    def handle_period_information_button_clicked(self):
        if self._model._data.periods_info:
            self._add_period_info_to_widget()
        self.period_info_widget.show()

    def _add_period_info_to_widget(self):
        self.period_info_widget.number_of_sequences = self._model._data.periods_info[0]
Matt Cumber's avatar
Matt Cumber committed
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
        names = self._model._data.periods_info[HEADER_COLUMN_MAP["Name"]].split(';')
        frames = self._model._data.periods_info[HEADER_COLUMN_MAP["Frames"]].split(';')
        total_frames = self._model._data.periods_info[HEADER_COLUMN_MAP["Total Frames"]].split(';')
        names, frames, total_frames, count = self._fix_up_period_info_lists(names, frames, total_frames)
        for i in range(count):
            self.period_info_widget.add_period_to_table(names[i], frames[i], total_frames[i])

    def _fix_up_period_info_lists(self, names, frames, total_frames):
        # First find number of periods
        count = max(len(names), len(frames), len(total_frames))
        # Then make sure lists are correct size
        if len(names) != count:
            if names[0] == "":
                names = [PERIOD_INFO_NOT_FOUND] * count
            else:
                names += [PERIOD_INFO_NOT_FOUND] * count-len(names)
        if len(frames) != count:
            if frames[0] == "":
                frames = [PERIOD_INFO_NOT_FOUND] * count
            else:
                frames += [PERIOD_INFO_NOT_FOUND] * count - len(frames)
        if len(total_frames) != count:
            if total_frames[0] == "":
                total_frames = [PERIOD_INFO_NOT_FOUND] * count
            else:
                total_frames += [PERIOD_INFO_NOT_FOUND] * count - len(total_frames)
        return names, frames, total_frames, count
317

318
319
320
321
322
    # ------------------------------------------------------------------------------------------------------------------
    # Observer / Observable
    # ------------------------------------------------------------------------------------------------------------------

    def group_table_changed(self):
323
        self.pairing_table_widget.update_view_from_model()
324
        self.diff_table.update_view_from_model()
325
        self.handle_update_all_clicked()
326
327

    def pair_table_changed(self):
328
        self.grouping_table_widget.update_view_from_model()
329
        self.diff_table.update_view_from_model()
330
331
332
333
334
        self.handle_update_all_clicked()

    def diff_table_changed(self):
        self.grouping_table_widget.update_view_from_model()
        self.pairing_table_widget.update_view_from_model()
335
        self.handle_update_all_clicked()
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363

    class LoadObserver(Observer):

        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.handle_new_data_loaded()

    class InstrumentObserver(Observer):

        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.on_clear_requested()

    class GuessAlphaObserver(Observer):

        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.handle_guess_alpha(arg[0], arg[1], arg[2])

364
365
366
367
368
369
370
371
    class GuiVariablesChangedObserver(Observer):
        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.handle_update_all_clicked()

372
373
374
375
376
377
378
379
    class GroupingNotifier(Observable):

        def __init__(self, outer):
            Observable.__init__(self)
            self.outer = outer  # handle to containing class

        def notify_subscribers(self, *args, **kwargs):
            Observable.notify_subscribers(self, *args, **kwargs)
380
381
382
383
384
385
386
387
388

    class MessageObserver(Observer):

        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer._view.display_warning_box(arg)
389

390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    class EnableObserver(Observer):
        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.enable_editing()

    class DisableObserver(Observer):
        def __init__(self, outer):
            Observer.__init__(self)
            self.outer = outer

        def update(self, observable, arg):
            self.outer.disable_editing()

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    class DisableEditingNotifier(Observable):

        def __init__(self, outer):
            Observable.__init__(self)
            self.outer = outer  # handle to containing class

        def notify_subscribers(self, *args, **kwargs):
            Observable.notify_subscribers(self, *args, **kwargs)

    class EnableEditingNotifier(Observable):

        def __init__(self, outer):
            Observable.__init__(self)
            self.outer = outer  # handle to containing class

        def notify_subscribers(self, *args, **kwargs):
            Observable.notify_subscribers(self, *args, **kwargs)