grouping_tab_widget_presenter.py 17.8 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
15
from Muon.GUI.Common.muon_period_info_widget import MuonPeriodInfoWidget, CONTEXT_MAP, PERIOD_INFO_NOT_FOUND, INFO_DELIM
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

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

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

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

276
277
    def plot_default_groups_or_pairs(self):
        # if we have no pairs or groups selected, generate a default plot
278
        if len(self._model.selected_groups_and_pairs) == 0:
279
280
281
282
283
            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()

284
    def handle_period_information_button_clicked(self):
Matt Cumber's avatar
Matt Cumber committed
285
        if self._model.is_data_loaded() and self.period_info_widget.is_empty():
286
287
288
289
290
            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
291
292
293
294
295
        names = self._model._data.periods_info[CONTEXT_MAP["Name"]].split(INFO_DELIM)
        types = self._model._data.periods_info[CONTEXT_MAP["Type"]].split(INFO_DELIM)
        frames = self._model._data.periods_info[CONTEXT_MAP["Frames"]].split(INFO_DELIM)
        total_frames = self._model._data.periods_info[CONTEXT_MAP["Total Good Frames"]].split(INFO_DELIM)
        counts = self._model._data.periods_info[CONTEXT_MAP["Counts"]].split(INFO_DELIM)
Matt Cumber's avatar
Matt Cumber committed
296
297
298
299
        tags = self._model._data.periods_info[CONTEXT_MAP["Tag"]].split(INFO_DELIM)
        names, types, frames, total_frames, counts, tags, count = self._fix_up_period_info_lists([names, types, frames,
                                                                                                  total_frames, counts,
                                                                                                  tags])
Matt Cumber's avatar
Matt Cumber committed
300
        for i in range(count):
301
            self.period_info_widget.add_period_to_table(names[i], types[i], frames[i], total_frames[i],
Matt Cumber's avatar
Matt Cumber committed
302
                                                        counts[self.period_info_widget.daq_count], tags[i])
Matt Cumber's avatar
Matt Cumber committed
303

Matt Cumber's avatar
Matt Cumber committed
304
    def _fix_up_period_info_lists(self, info_list):
Matt Cumber's avatar
Matt Cumber committed
305
        # First find number of periods
Matt Cumber's avatar
Matt Cumber committed
306
307
        lengths_list = [len(i) for i in info_list]
        count = max(lengths_list)
Matt Cumber's avatar
Matt Cumber committed
308
        # Then make sure lists are correct size
309
        for i, info in enumerate(info_list):
Matt Cumber's avatar
Matt Cumber committed
310
311
            if len(info) != count:
                if info[0] == "":
312
                    info_list[i] = [PERIOD_INFO_NOT_FOUND] * count
Matt Cumber's avatar
Matt Cumber committed
313
                else:
314
315
                    info_list[i] += [PERIOD_INFO_NOT_FOUND] * (count-len(info))
        return (*info_list, count)
316

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

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

    def pair_table_changed(self):
327
        self.grouping_table_widget.update_view_from_model()
328
        self.diff_table.update_view_from_model()
329
330
331
332
333
        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()
334
        self.handle_update_all_clicked()
335
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

    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])

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

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

371
372
373
374
375
376
377
378
    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)
379
380
381
382
383
384
385
386
387

    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)
388

389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    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()

405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    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)