OSDN Git Service

File system path for statndalone version
[minimpy2/mp2.git] / main_window.py
1 from PySide2 import QtWidgets as qtw
2 from PySide2 import QtCore as qtc
3 from PySide2 import QtGui as qtg
4 from tendo.singleton import SingleInstanceException
5 from about_minimisation import AboutMinimisationDialog
6 from config import Config
7
8 from db import Database
9 from enrol_form import EnrolForm
10 from factor_levels import FactorLevels
11 from freq_table import FreqTable
12 from mnd import *
13 from ui_main_window import Ui_MainWindow
14 from minim import Minim
15 from model import Model
16 from my_random import Random
17 from run_test import RunTest
18 from trial import *
19 import sys
20 from tendo import singleton
21 from utils import resource_path
22
23
24 # noinspection PyTypeChecker
25 class MainWindow(qtw.QMainWindow):
26     def __init__(self, settings):
27         super(MainWindow, self).__init__()
28         self.prev_tab_index = None
29         self.helpdict = {
30                     ('tialsTableWidget',): self.tr("""Each trial has a uniqe tile and also a code usually in the form of an abreviation of the title.\nLast column show if the trial is the active one. Only one trial can be active at any time. Other pages of the application relate to this active trial. To make a treial active check the box in last column.\nClick and then type over the trial title or code to edit it"""),
31                     ('trialSetting',): self.tr('''In this page you can view and edit different settings for the current trial. Click on blue lable for each setting item to view detailed help information for that setting'''),
32                     ('trialCreatedLabel','trialCreatedValueLabel'): self.tr('''Creation date of this trial\n'''),
33                     ('trialModifiedLabel','trialModifiedValueLabel'): self.tr('''Modification date of this trial\n'''),
34                     ('trialTitleLabel','trialTitleLineEdit'): self.tr('''Title of this trial. Title must be unique\n'''),
35                     ('trialCodeLabel','trialCodeLineEdit'): self.tr('''Code of the trial usually title abbreviation\n'''),
36                     ('trialProbmethodLabel','trialProbMethodComboBox'): self.tr('''The method used for calculating of the assignment probabilities. There are two choices. Biased coin minimization which include the effect of allocation ratios into the computation, and naive method which do not include the effect of allocation ratios. Biased coin is the preffered method. These options only is meaningfull when we have treatments with unequal allocation ratios. Biased Coin method is based on the different probabilities of assignment for subjects to trial groups. The subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Therefore if treatments with higher allocations ratios are assigned more probabilities this will be considered as biased-coin minimization. In this method a base probability is used for the group with the lowest allocation ratio when that group is selected as the preferred treatment. Probabilities for other groups (PHi) when they selected as preferred treatments are calculated as a function of base probability and their allocation ratios. In Naive method the subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Here the probabilities are not affected by allocation ratios and usually the same high probability are used for all treatment groups when they are selected as preferred treatments (base probability). This is denoted by Naive Minimization. In this method, probabilities for non-preferred treatments are distributed equally.\n'''),
37                     ('trialBaseProbabilityLabel','trialBaseProbabilitySlider','baseProbLabel'): self.tr('''The probability given to the treatment with the lowest allocation ratio in biased-coin minimization. In naive minimization it is the probability used for all treatment groups when they are selected as preferred treatments. Use the spin button to specify the value.\n'''),
38                     ('trialDistanceMethodLabel','trialDistanceMethodComboBox'): self.tr('''Distance measure is used to calculate the imbalance score. There are four options here. Marginal balance computes the cumulative difference between every possible pairs of level counts. This is the default and the one which is recommended. It tends to minimize more accurately when treatment groups are not equal in size. For each factor level marginal balance is calculated as a function of adjusted number of patients present in a factor level for each treatment group. Range calculates the difference between the maximum and the mimimum values in level counts. Standard deviation calculates the standard deviation of level counts and variance calculate their variances. All of these measures uses adjusted level counts. Adjustment performs relative to the values of allocation ratios.\n'''),
39                     ('trialIdentifierTypeLabel', 'trialIdentifierTypeComboBox'): self.tr('''Type of subject identifier. It can be Numeric, Alpha or Alphanumeric.\nOnce a subject is enrolled, you can not change identifier type'''),
40                     ('trialIdentifierOrderLabel','trialIentifierOrderComboBox'): self.tr('''The order of identifier sequence. It can be Sequential or Random.\n'''),
41                     ('trialIdentifierLengthLabel','max_sample_size_label'): self.tr('''Length  of subjects identifier in character. The longer the length the higher  will be the possible sample size\n'''),
42                     ('trialRecycleIdsLabel','trialRecycleIdsCheckBox'): self.tr('''Check to reuse IDs of a deleted subject. The default is to discard\n'''),
43                     ('trialNewSubjectRandomLabel','trialNewSubjectRandomCheckBox'): self.tr('''If unchecked (the default), enrol form will show with the level for each factor unselected. If checked, for each factor a random level will be selected. This is only for test purpose to rapidly and randomly enrol a subject\n'''),
44                     ('trialArmsWeightLabel','trialArmsWeightDoubleSlider', 'armWeightLabel'): self.tr(''' This is equalizing factor for arms of trial. The higher the arms weight the higher will be the possibility of having equal group sizes at the end of study\n'''),
45                     ('treatmentTableWidget','Treatments'): self.tr('''For each treatment a name (unique for the trial) and an integer value for allocation ratio are needed. Allocation ratios are integer numeric values which denote the ratios of the treatment counts. For example if we have three groups a, b and c with their allocation ratios 1, 2 and 3 respectively, this mean that in the final sample nearly 1/6 of subjects will be from treatment a, 1/3 from b and 1/2 from c\nYou have to define at lease two groups to use minimisation on them.\nClick on treatment title and type over it to edit the title of the treatment, and use the spin control to change the allocation ratios'''),
46                     ('Factors','factorTableWidget'): self.tr('''Factors or prognostic factors, are the subject factors which are selected to match subjects based on them. At lease one factor is mandatory for minimisation. Each factor has a name (unique for the trial) and a weight. Weight indicates the relative importance of the factor for minimization. For each factor you must define at least two levels. As an example factor may be gender and its levels may be Male and Female.\nFactor title can be edited by clicking and typing over the title of the factor\n\nClick on the levels button on tool bar or double click on levels in table to manage (add, edit, delete) levels of the selected factor'''),
47                     ('factor_levels',): self.tr('''Manage (add, edit, delete) levels of the selected factor'''),
48                     ('Frequencies','freqGridLayout'): self.tr('''This table shows frequencies of subjects enrolled in the trial or entered via preload table depending on the active checkboxes. When you have already enrolled a number of subjects (using other tools or software) and you want to continue with this minimisation program for the remaining cases, you can enter data of already enrolled subject as counts. You enter counts of different treatments accross all levels of each subject into the preload table. Then the counts from this table will be added to counts of enrolled subject by this application and total counts will be used in minimisation model.\nIt is as if you have entered the list of subject into your existing list. The effect is exactly the same. When defining preload please note that sum of subjects across different levels of a factor must be equal to thats of other factors, otherwise an error will be generated.\nIf you have a greal number of subjects and have no need to keep their list, you may convert them into preload table. Select frequencies check box and unselect preload checkbox, then click 'convert to preload' to clear all subjects and add them to preload table.'''),
49                     ('Subjects', 'subjectTableWidget'): self.tr('''Subjects enrolled in the trial will be listed in subjects table. Each row belongs to one subject and shows subject ID, treatment, levels of factors, dates of enrollment and modification (if any). Subject can be added, edited or deleted from this table, although editing or deleting subject may compromise the minimisation model and therefore are not recommended.To enter a new subject, click plus sign in the tool bar. To edit double click and to delete click on Delete button on toolbar. A progressbar above the subject table show the enrollment progress as the number of subject enrolled compared to the remaining counts of identifiers'''),
50                     ('Balance','balanceTreeWidget'): self.tr('''In this table you can see four different scale related to overall trial balance (marginal balance, range, variance, SD) and randomness. Also the balances for each factor or for each level within each factor are displayed. The last two rows of the table show the mean and max values for each scale. The lower the numeric values of these balance, the more balance we have in our trial. Also a scale of randomness for enrolling subjects is calculated based on run test.'''),
51                     ('start', 'startWidget'): self.tr('''A quick list of steps to start a new minimization.''')
52         }
53
54         self.settings = settings
55
56
57         engine = self.settings.value('random_engine', 'random', type=str)
58         self.random = Random(engine)
59         self.ui = Ui_MainWindow()
60         self.ui.setupUi(self)
61         lang = settings.value('language', 'en_US', type=str)
62         if lang != 'en_US':
63             self.setLayoutDirection(qtc.Qt.RightToLeft)
64         filename = resource_path('start_{}.html'.format(lang))
65         start_str = open(filename, encoding='utf8').read()
66         self.ui.textEdit.setHtml(start_str)
67         self.ui.actionNew.setIcon(qtg.QIcon(resource_path('images/add.png')))
68         self.ui.actionDelete.setIcon(qtg.QIcon(resource_path('images/delete.png')))
69         self.ui.actionLevels.setIcon(qtg.QIcon(resource_path('images/levels.png')))
70         self.ui.actionSave.setIcon(qtg.QIcon(resource_path('images/save.png')))
71         self.ui.actionHelp.setIcon(qtg.QIcon(resource_path('images/help.png')))
72         self.ui.actionQuit.setIcon(qtg.QIcon(resource_path('images/exit.png')))
73         self.ui.actionConfig.setIcon(qtg.QIcon(resource_path('images/config.png')))
74         self.ui.actionAbout_MinimPy2.setIcon(qtg.QIcon(resource_path('images/about.png')))
75         self.database = Database.get_instance(self)
76         self.trial = None
77
78         self.ui.tabWidget.currentChanged.connect(self.page_changed)
79
80         self.ui.actionNew.triggered.connect(self.action_new)
81         self.ui.actionDelete.triggered.connect(self.action_delete)
82         self.ui.actionSave.triggered.connect(self.action_save)
83         self.ui.actionLevels.triggered.connect(self.factor_levels)
84         self.ui.actionQuit.triggered.connect(self.close)
85         self.ui.actionImport.triggered.connect(self.import_mnd_file)
86         self.ui.actionExport.triggered.connect(self.export_mnd_file)
87         self.ui.actionHelp.triggered.connect(self.action_help)
88         self.ui.actionConfig.triggered.connect(self.on_config)
89         self.ui.actionAbout_Minimisation.triggered.connect(self.on_about_minimisation)
90         self.ui.actionAbout_MinimPy2.triggered.connect(self.on_about_minimpy)
91         self.ui.actionAbout_Qt.triggered.connect(lambda : qtw.QMessageBox.aboutQt(self, ''))
92
93         lst = [self.ui.trialCreatedLabel, self.ui.trialCreatedValueLabel,
94                self.ui.trialModifiedLabel, self.ui.trialModifiedValueLabel,
95                self.ui.trialTitleLabel, self.ui.trialCodeLabel,
96                self.ui.trialProbmethodLabel, self.ui.trialBaseProbabilityLabel,
97                self.ui.baseProbLabel, self.ui.trialDistanceMethodLabel,
98                self.ui.trialIdentifierTypeLabel, self.ui.trialIdentifierOrderLabel,
99                self.ui.trialIdentifierLengthLabel, self.ui.max_sample_size_label,
100                self.ui.trialRecycleIdsLabel, self.ui.trialNewSubjectRandomLabel,
101                self.ui.trialArmsWeightLabel, self.ui.armWeightLabel]
102         for w in lst:
103             self.set_help_handler(w)
104
105         self.ui.tialsTableWidget.cellChanged.connect(self.trial_cell_changed)
106         self.ui.treatmentTableWidget.itemChanged.connect(self.treatment_item_changed)
107         self.ui.factorTableWidget.itemChanged.connect(self.factor_item_changed)
108         self.ui.factorTableWidget.cellClicked.connect(self.set_help_handler(self.ui.factorTableWidget))
109
110         self.ui.tialsTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
111         self.ui.tialsTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
112
113         self.ui.treatmentTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
114         self.ui.treatmentTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
115
116         self.ui.factorTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
117         self.ui.factorTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
118
119         self.ui.subjectTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
120         self.ui.subjectTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
121
122         self.ui.editPreloadCheckBox.toggled.connect(self.on_edit_preload)
123         self.ui.convertPreloadButton.clicked.connect(self.on_convert_preload)
124         self.ui.factorTableWidget.cellDoubleClicked.connect(self.factor_column_dblclicked)
125         self.ui.subjectTableWidget.cellDoubleClicked.connect(self.subject_column_dblclicked)
126
127         self.ui.trialIdentifierLengthSpinBox.valueChanged.connect(self.on_identifier_length)
128         self.ui.trialBaseProbabilitySlider.valueChanged.connect(self.on_base_prob_change)
129         self.ui.trialArmsWeightDoubleSlider.valueChanged.connect(self.on_arm_weight_change)
130
131         self.freqTable = None
132
133         if self.settings.value('show_tutorial_at_start', True):
134             self.ui.tabWidget.setCurrentIndex(7)
135             self.ui.showAtStartCheckBox.setChecked(False)
136         else:
137             self.ui.tabWidget.setCurrentIndex(0)
138             self.ui.showAtStartCheckBox.setChecked(True)
139         self.ui.balanceTreeWidget.setColumnCount(5)
140         self.ui.balanceTreeWidget.setHeaderLabels([self.tr('Factor/Level'), self.tr('Marginal balance'), self.tr('Range'), self.tr('Variance'), self.tr('SD')])
141
142         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
143         if last_trial_id != 0:
144             trial_setting = self.database.get_trial_setting(last_trial_id)
145             #  when db file accidentally deleted but the last_trial_id persist
146             if not trial_setting:
147                 trial_setting = self.database.get_first_trial_setting()
148                 if not trial_setting:
149                     last_trial_id = self.database.insert_trial(self.tr('New trial'))
150                     self.settings.setValue('last_trial_id', last_trial_id)
151                     trial_setting = self.database.get_trial_setting(last_trial_id)
152             else:
153                 self.settings.setValue('last_trial_id', trial_setting.value('id'))
154         else:
155             trial_setting = self.database.get_first_trial_setting()
156             if not trial_setting:
157                 last_trial_id = self.database.insert_trial(self.tr('New trial'))
158                 self.settings.setValue('last_trial_id', last_trial_id)
159                 trial_setting = self.database.get_trial_setting(last_trial_id)
160             else:
161                 self.settings.setValue('last_trial_id', trial_setting.value('id'))
162         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
163         if last_trial_id != 0:
164             trial_setting = self.database.get_trial_setting(last_trial_id)
165             if trial_setting:
166                 self.trial = Trial(trial_setting)
167                 #if self.database.has_preload(self.trial.id) or self.database.has_subject(self.trial.id):
168                 self.ui.frequenciesCheckBox.toggled.connect(self.set_cur_count)
169                 self.ui.preloadCheckBox.toggled.connect(self.set_cur_count)
170         self.load_trials()
171         self.ui.actionSave.setEnabled(False)
172         self.ui.actionLevels.setEnabled(False)
173         if self.settings.value('show_tutorial_at_start', True, bool):
174             self.ui.tabWidget.setCurrentIndex(7)
175             self.ui.showAtStartCheckBox.setChecked(False)
176         else:
177             self.ui.tabWidget.setCurrentIndex(0)
178             self.ui.showAtStartCheckBox.setChecked(True)
179         self.ui.showAtStartCheckBox.toggled.connect(self.toggle_show_at_start)
180
181     def on_identifier_length(self, value):
182         self.trial.identifier_length = value
183         self.update_sample_size_label()
184
185     def toggle_show_at_start(self, check):
186         self.settings.setValue('show_tutorial_at_start', not check)
187
188     def on_about_minimpy(self):
189         about = '{}\n{} {} {} ({})\n{} {}\n{}'.format(self.tr('MinimPy'),
190                                                       self.tr('MinimPy'),
191                                                       self.tr('version'),
192                                                       self.tr('2.0'),
193                                                       self.tr('minimpy2'),
194                                                       self.tr('Copyright'),
195                                                       self.tr('2020'),
196                                                       self.tr('Dr. Mahmoud Saghaei'))
197         qtw.QMessageBox.about(self, self.tr('minimpy2'), about)
198
199     def on_about_minimisation(self):
200         ab_min = AboutMinimisationDialog(self)
201         ab_min.show()
202
203     def set_help_handler(self, w):
204         def help_handler():
205             self.setCursor(qtc.Qt.ArrowCursor)
206             name = w.objectName()
207             for key in self.helpdict:
208                 if name in key:
209                     qtw.QMessageBox.information(
210                         self, self.tr('Help!'),
211                         self.tr(self.helpdict[key])
212                     )
213             return True
214         clickable(w).connect(help_handler)
215
216     def on_config(self):
217         cur_lang = self.settings.value('language', 'en_US', type=str)
218         cur_random = self.settings.value('random_engine', 'random', type=str)
219         try:
220             config = Config(self)
221             config.exec_()
222         except:
223             qtw.QMessageBox.critical(self, self.tr('Error'), self.tr(
224                 'Error in languages.lst file format\nPlease see the READ.ME file in locales folder'))
225             sys.exit(1)
226         new_lang = self.settings.value('language', 'en_US', type=str)
227         new_random = self.settings.value('random_engine', 'random', type=str)
228         if new_lang != cur_lang or new_random != cur_random:
229             qtw.QMessageBox.information(self,
230                                         self.tr('Restart needed'),
231                                         self.tr('Changes will be applied after restart!'))
232
233     def action_help(self):
234         currentIndex = self.ui.tabWidget.currentIndex()
235         pages = ['tialsTableWidget', 'trialSetting', 'treatmentTableWidget',
236                  'factorTableWidget', 'freqGridLayout',
237                  'subjectTableWidget', 'balanceTreeWidget', 'start']
238         page = pages[currentIndex]
239         for key in self.helpdict:
240             if page in key:
241                 qtw.QMessageBox.information(
242                     self, self.tr('Help!'),
243                     self.tr(self.helpdict[key])
244                 )
245
246     def on_arm_weight_change(self, value):
247         self.ui.armWeightLabel.setText('{}'.format(value))
248
249     def on_base_prob_change(self, value):
250         self.ui.baseProbLabel.setText('{:4.2f}'.format(value / 100.0))
251
252     def on_idenqdialogbuttonboxtifier_length(self, value):
253         self.trial.identifier_length = value
254         self.update_sample_size_label()
255
256     def update_sample_size_label(self):
257         ss, max_sample_size = self.trial.get_max_sample_size()
258         self.ui.max_sample_size_label.setText(self.tr('Maximum sample size > {}').format(max_sample_size))
259
260     def closeEvent(self, event):
261         button = qtw.QMessageBox.question(self, self.tr("Warning"),
262                                           self.tr('Are you sure you want to quit?'),
263                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
264         if button != qtw.QMessageBox.Yes:
265             event.ignore()
266         else:
267             event.accept()
268
269     def page_changed(self, currentTabIndex):
270         self.setCursor(qtc.Qt.ArrowCursor)
271         self.ui.actionNew.setEnabled(False)
272         self.ui.actionDelete.setEnabled(False)
273         self.ui.actionSave.setEnabled(False)
274         self.ui.actionLevels.setEnabled(False)
275         if self.trial == None and currentTabIndex != 0:
276             qtw.QMessageBox.information(
277                 self, self.tr('No current trial!'),
278                 self.tr('Fist you have to define and select a trial as current!')
279             )
280             self.ui.actionNew.setEnabled(True)
281             self.ui.actionDelete.setEnabled(True)
282             self.ui.actionSave.setEnabled(False)
283             self.ui.actionLevels.setEnabled(False)
284             self.ui.tabWidget.setCurrentIndex(0)
285             return
286         if self.prev_tab_index == 1:
287             self.save_trial_setting()
288             last_trial_id = self.settings.value('last_trial_id', 0, type=int)
289             trial_setting = self.database.get_trial_setting(last_trial_id)
290             self.trial = Trial(trial_setting)
291         self.prev_tab_index = currentTabIndex
292         if currentTabIndex == 0:  # trials
293             self.ui.actionNew.setEnabled(True)
294             self.ui.actionDelete.setEnabled(True)
295             self.load_trials()
296         elif currentTabIndex == 1:  # trial  settings
297             self.ui.actionSave.setEnabled(True)
298             self.load_trial_settings()
299         elif currentTabIndex == 2:  # treatments
300             self.ui.actionNew.setEnabled(True)
301             self.ui.actionDelete.setEnabled(True)
302             self.load_treatments()
303         elif currentTabIndex == 3:  # factors
304             self.ui.actionNew.setEnabled(True)
305             self.ui.actionDelete.setEnabled(True)
306             self.ui.actionLevels.setEnabled(True)
307             self.load_factors()
308         elif currentTabIndex == 4:  # Frequencies
309             self.ui.frequenciesCheckBox.setChecked(False)
310             self.ui.preloadCheckBox.setChecked(False)
311             self.ui.editPreloadCheckBox.setChecked(False)
312             self.ui.actionSave.setEnabled(True)
313             self.ui.actionDelete.setEnabled(True)
314             self.ui.convertPreloadButton.setEnabled(False)
315             self.ui.convertWarningCheckBox.setVisible(False)
316             self.load_frequencies()
317         elif currentTabIndex == 5:  # Subjects
318             self.ui.actionNew.setEnabled(True)
319             self.ui.actionDelete.setEnabled(True)
320             self.load_subjects()
321         elif currentTabIndex == 6:  # Balance
322             self.load_balance()
323
324     def get_factor_level_dict(self, trial_id):
325         factor_list = self.database.read_factors(trial_id)
326         factors = []
327         for factor in factor_list:
328             f = {'id': factor[0], 'title': factor[1], 'weight': factor[2], 'levels': []}
329             levels = self.database.factor_levels(factor[0])
330             for level in levels:
331                 lv = {'id': level[0], 'title': level[1]}
332                 f['levels'].append(lv)
333             if self.trial.new_subject_random:
334                 index = self.random.randint(0, len(f['levels']) - 1)
335                 f['selected_level_id'] = f['levels'][index]['id']
336             else:
337                 f['selected_level_id'] = -1
338             factors.append(f)
339         return factors
340
341     def add_new_subject(self):
342         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
343         if last_trial_id == 0:
344             return
345         factors = self.get_factor_level_dict(last_trial_id)
346         if len(factors) < 1:
347             qtw.QMessageBox.information(
348                 self,
349                 self.tr('Factor'),
350                 self.tr('Define one factor at least')
351             )
352             return
353         for f in factors:
354             if len(f['levels']) == 0:
355                 qtw.QMessageBox.information(
356                     self,
357                     self.tr('Levels'),
358                     self.tr('Define at least two levels for each factor')
359                 )
360                 return
361         treatments = self.database.read_treatments(last_trial_id)
362         if len(treatments) < 2:
363             qtw.QMessageBox.information(
364                 self,
365                 self.tr('Treatment'),
366                 self.tr('Define two treatments at least')
367             )
368             return
369         treatment = {'selected_treatment_id': -1, 'treatments': []}
370         for t in treatments:
371             treatment['treatments'].append({'id': t[0], 'title': t[1]})
372         enrol_form = EnrolForm(self, factors, treatment, False)
373         enrol_form.exec_()
374         if treatment['selected_treatment_id'] == -1:
375             return
376         self.update_subjects_progress()
377
378     def subject_column_dblclicked(self, row, col):
379         button = qtw.QMessageBox.question(self, self.tr("Warning"),
380                                           self.tr('''Are you sure you want to edit subject at row "{}" ?\n
381 Editing subject may invalidate your research and the result of minimisation''').format(row),
382                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
383         if button != qtw.QMessageBox.Yes:
384             return
385         subject_id = int(self.ui.subjectTableWidget.item(row, 0).text())
386         identifier = self.ui.subjectTableWidget.item(row, 1).text()
387         treatment_id = self.database.get_subject_treatment_id(subject_id)
388         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
389         if last_trial_id == 0:
390             return
391         factors = self.get_factor_level_dict(last_trial_id)
392         subject_levels = self.database.get_subject_levels(subject_id)
393         for factor in factors:
394             for subject_level in subject_levels:
395                 if factor['id'] == subject_level[0]:
396                     factor['selected_level_id'] = subject_level[1]
397                     break
398         treatments = self.database.read_treatments(last_trial_id)
399         treatment = {'selected_treatment_id': treatment_id, 'treatments': []}
400         for t in treatments:
401             treatment['treatments'].append({'id': t[0], 'title': t[1]})
402         enrol_form = EnrolForm(self, factors, treatment, True, identifier)
403         enrol_form.exec_()
404         result = enrol_form.result()
405         if result != qtw.QDialog.Accepted:
406             return
407         treatment_id = treatment['selected_treatment_id']
408         for factor in factors:
409             for subject_level in subject_levels:
410                 if factor['id'] == subject_level[0]:
411                     subject_level[1] = factor['selected_level_id']
412                     break
413         self.database.delete_subject_levels(subject_id)
414         self.database.insert_subject_levels(last_trial_id, subject_id, subject_levels)
415         self.database.update_subject_treatment_id(subject_id, treatment_id)
416         self.clear_subject_filters()
417         self.ui.subjectTableWidget.selectRow(row)
418         self.ui.subjectTableWidget.setFocus()
419
420     def get_min_free_identifier(self, used_ids):
421         n = 0
422         while n in used_ids:
423             n += 1
424         return n
425
426     def enrol_one(self, selected_indices, selected_ids):
427         trial_id = self.settings.value('last_trial_id', 0, type=int)
428         if trial_id == 0:
429             return
430         max_sample_size, f = self.trial.get_max_sample_size()
431         subject_count = self.database.get_subject_count(trial_id)
432         if subject_count == max_sample_size:
433             qtw.QMessageBox.critical(
434                 self,
435                 self.tr('Error'),
436                 self.tr(
437                     'Sample size consumed!\nNo further subject enrol possible!\nPlease convert cases to preload and continue')
438             )
439             return
440         used_ids = self.database.get_used_identifiers(trial_id)
441         if not self.trial.recycle_ids:
442             used_ids.extend(self.database.get_discarded_identifiers(trial_id))
443         if not used_ids:
444             used_ids = [-1]
445         if self.trial.identifier_order == SEQUENTIAL:
446             identifier_value = self.get_min_free_identifier(used_ids)
447         else:
448             identifier_value = self.get_random_identifier(used_ids, int(max_sample_size))
449         new_case = {'levels': selected_indices, 'allocation': -1, 'UI': identifier_value}
450         # minimised group and preferred group
451         m_treatment, p_treatment, probs = self.get_minimize_case(new_case, trial_id)
452         treatments = self.database.read_treatments(trial_id)
453         treatment_id = treatments[m_treatment][0]
454         subject_id = self.database.insert_subject(identifier_value, treatment_id, trial_id)
455         self.database.insert_subject_levels(trial_id, subject_id, selected_ids)
456         if subject_count == 0:
457             self.load_subjects()
458         else:
459             self.clear_subject_filters()
460         self.select_row(self.ui.subjectTableWidget, subject_id)
461         if p_treatment is None:
462             p_treatment = 0
463         return treatments[m_treatment][1], \
464                m_treatment, \
465                treatments[p_treatment][1], \
466                p_treatment, \
467                self.trial.format_subject_identifier(identifier_value), probs
468
469     def select_row(self, table_widget, row_id):
470         for r in range(table_widget.rowCount()):
471             item = table_widget.item(r, 0)
472             if item is None: continue
473             if row_id == int(item.text()):
474                 table_widget.selectRow(r)
475                 return
476
477     def get_minimize_case(self, new_case, trial_id):
478         model = Model()
479         num_treatments = self.database.get_num_treatments(trial_id)
480         model.groups = list(range(num_treatments))
481         model.variables = self.database.get_factors_level_indices(trial_id)
482         model.variables_weight = self.database.get_factor_weights(trial_id)
483         model.allocation_ratio = self.database.get_allocation_ratios(trial_id)
484         model.allocations = self.database.get_allocations(trial_id)
485         model.prob_method = self.trial.prob_method
486         model.distance_measure = self.trial.dist_method
487         model.high_prob = self.trial.base_prob
488         model.min_group = self.database.get_min_allocation_ratio_group_index(trial_id)
489         model.arms_weight = self.trial.arms_weight
490         m = Minim(random=self.random, model=model)
491         m.build_probs(model.high_prob, model.min_group)
492         m.build_freq_table()
493         if self.database.has_preload(trial_id):
494             self.add_to_preload(m.freq_table, trial_id)
495         minimised_group = m.enroll(new_case, m.freq_table)
496         preffered_group= m.pref_group
497         probs = m.selected_probs
498         return minimised_group, preffered_group, probs
499
500     def add_to_preload(self, freq_table, trial_id):
501         preload = self.database.get_preload(trial_id)
502         treatments = self.database.read_treatments(trial_id)
503         factors = self.database.read_factors(trial_id)
504         for row, group in enumerate(freq_table):
505             for v, variable in enumerate(group):
506                 for l, level in enumerate(variable):
507                     factor = factors[v]
508                     levels = self.database.factor_levels(factor[0])
509                     level = levels[l]
510                     treatment = treatments[row]
511                     key = (treatment[0], factor[0], level[0])
512                     freq_table[row][v][l] += preload[key]
513
514     def get_random_identifier(self, used_ids, max_sample_size):
515         if (max_sample_size / len(used_ids) < 100):
516             pv = list(range(max_sample_size))
517             for used_id in used_ids:
518                 if used_id in pv:
519                     pv.remove(used_id)
520             index = self.random.randint(0, len(pv) - 1)
521             return pv[index]
522         else:
523             v = self.random.randint(0, max_sample_size - 1)
524             while v in used_ids:
525                 v = self.random.randint(0, max_sample_size - 1)
526             return v
527
528     def update_subjects_progress(self):
529         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
530         if last_trial_id == 0:
531             return
532         ss, ss_label = self.trial.get_max_sample_size()
533         ss -= self.database.get_count_discarded_identifiers(last_trial_id)
534         self.ui.subjectProgressBar.setMaximum(ss)
535         cnt = self.database.get_subject_count(last_trial_id)
536         self.ui.subjectProgressBar.setValue(cnt)
537
538     def load_subjects(self):
539         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
540         if last_trial_id == 0:
541             return
542         self.ui.subjectTableWidget.setRowCount(0)
543         cnt = self.database.get_subject_count(last_trial_id)
544         self.update_subjects_progress()
545         if cnt == 0:
546             return
547         factors = self.database.read_factors(last_trial_id)
548         self.ui.subjectTableWidget.setColumnCount(len(factors) + 5)
549         headers = ['#', self.tr('ID'), self.tr('Treatment')]
550         for factor in factors:
551             headers.append(factor[1])
552         headers += [self.tr('Enrolled'), self.tr('Modified')]
553         self.ui.subjectTableWidget.setHorizontalHeaderLabels(headers)
554         header = self.ui.subjectTableWidget.horizontalHeader()
555         header.setSectionResizeMode(
556             self.ui.subjectTableWidget.columnCount() - 1,
557             qtw.QHeaderView.ResizeToContents
558         )
559         header.setSectionResizeMode(
560             self.ui.subjectTableWidget.columnCount() - 2,
561             qtw.QHeaderView.ResizeToContents
562         )
563         self.ui.subjectTableWidget.insertRow(0)
564         for i in range(1, len(headers)):
565             lineEdit = qtw.QLineEdit()
566             lineEdit.setPlaceholderText(self.tr('Filter'))
567             lineEdit.editingFinished.connect(self.subject_colum_editing_finished)
568             self.ui.subjectTableWidget.setCellWidget(0, i, lineEdit)
569         self.ui.subjectTableWidget.hideColumn(0)
570         self.load_subject_rows()
571
572     def subject_colum_editing_finished(self):
573         self.load_subject_rows()
574
575     def clear_subject_filters(self):
576         for c in range(1, self.ui.subjectTableWidget.columnCount()):
577             lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
578             lineEdit.setText('')
579         self.load_subject_rows()
580
581     def load_subject_rows(self):
582         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
583         if last_trial_id == 0:
584             return
585         subjects = self.database.get_subjects(last_trial_id)
586         self.ui.subjectTableWidget.setRowCount(1)
587         fields = [None]
588         for c in range(1, self.ui.subjectTableWidget.columnCount()):
589             lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
590             if lineEdit is None:
591                 self.load_subjects()
592                 return
593             text = lineEdit.text().strip()
594             fields.append(None if len(text) == 0 else text)
595         # model = self.ui.subjectTableWidget.model()
596         for subject in subjects:
597             subject_id = subject[0]
598             treatment_id = subject[1]
599             identifier_value = subject[2]
600             enrolled = subject[3]
601             modified = subject[4]
602             treatment = self.database.get_treatment_title(treatment_id)
603             row = [subject_id, self.trial.format_subject_identifier(identifier_value), treatment]
604             subject_levels = self.database.get_subject_levels(subject_id)
605             row.extend(self.database.get_subject_level_titles(subject_levels))
606             row.extend([enrolled, modified])
607             for c, row_text in enumerate(row):
608                 if fields[c] == None:
609                     continue
610                 if not fields[c] in row_text:
611                     break
612             else:
613                 r = self.ui.subjectTableWidget.rowCount()
614                 self.ui.subjectTableWidget.insertRow(r)
615                 # data = model.headerData(r, qtc.Qt.Vertical)
616                 for c, row_text in enumerate(row):
617                     item = qtw.QTableWidgetItem(str(row_text))
618                     item.setFlags(item.flags() ^ qtc.Qt.ItemIsEditable)
619                     self.ui.subjectTableWidget.setItem(r, c, item)
620         labels = [str(n) for n in range(1, len(subjects) + 1)]
621         labels.insert(0, '')
622         self.ui.subjectTableWidget.setVerticalHeaderLabels(labels)
623
624     def set_cur_count(self, checked):
625         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
626         if last_trial_id == 0:
627             return
628         show_freq = self.ui.frequenciesCheckBox.isChecked()
629         show_preload = self.ui.preloadCheckBox.isChecked()
630         if show_preload and not show_freq:
631             self.ui.editPreloadCheckBox.setEnabled(True)
632             self.on_edit_preload(self.ui.editPreloadCheckBox.isChecked())
633         else:
634             self.ui.editPreloadCheckBox.setEnabled(False)
635             self.ui.editPreloadCheckBox.setChecked(False)
636         self.ui.convertPreloadButton.setEnabled(False)
637         if show_preload and show_freq:
638             cur_count = self.database.get_preload_with_freq(last_trial_id)
639         elif show_preload and not show_freq:
640             cur_count = self.database.get_preload(last_trial_id)
641         elif show_freq and not show_preload:
642             cur_count = self.database.get_freq(last_trial_id)
643             if self.database.has_subject(last_trial_id):
644                 self.ui.convertPreloadButton.setEnabled(True)
645         else:
646             cur_count = self.database.get_empty_freq(last_trial_id)
647         if cur_count is None:
648             return
649         self.freqTable.set_counts(cur_count)
650
651     def on_convert_preload(self):
652         if not self.ui.convertWarningCheckBox.isChecked():
653             qtw.QMessageBox.information(
654                 self, self.tr('Double check!'),
655                 self.tr('Need your final confirm')
656             )
657             self.ui.convertWarningCheckBox.setVisible(True)
658             return
659         button = qtw.QMessageBox.question(self, self.tr("Deleting preload"),
660                                           self.tr('''Are you certainly sure you want to convert all subjects into preload?
661 ALL subjects will be deleted'''),
662                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
663         if button != qtw.QMessageBox.Yes:
664             self.ui.convertWarningCheckBox.setChecked(False)
665             self.ui.convertWarningCheckBox.setVisible(False)
666             return
667         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
668         if last_trial_id == 0:
669             return
670         self.ui.convertWarningCheckBox.setChecked(False)
671         self.ui.convertWarningCheckBox.setVisible(False)
672         self.ui.convertPreloadButton.setEnabled(False)
673         self.freqTable.add_to_preload()
674         self.database.delete_subjects(last_trial_id)
675         self.ui.frequenciesCheckBox.setChecked(False)
676         self.ui.preloadCheckBox.setChecked(True)
677
678     def on_edit_preload(self, check):
679         self.freqTable.toggleReadOnly()
680
681     def load_frequencies(self):
682         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
683         if last_trial_id == 0:
684             return
685         self.freqTable = FreqTable(self, self.ui, last_trial_id)
686         self.freqTable.build()
687         if self.database.has_subject(self.trial.id):
688             self.ui.frequenciesCheckBox.setChecked(True)
689         elif self.database.has_preload(self.trial.id):
690             self.ui.preloadCheckBox.setChecked(True)
691
692     def factor_levels(self):
693         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
694         if last_trial_id == 0:
695             return
696         selected = self.ui.factorTableWidget.selectedIndexes()
697         if not selected:
698             return
699         factor_id = int(self.ui.factorTableWidget.item(selected[0].row(), 0).text())
700         factor_title = self.ui.factorTableWidget.item(selected[0].row(), 1).text()
701         factorLevels = FactorLevels(self, last_trial_id, factor_id, factor_title)
702         factorLevels.exec_()
703         self.load_factors()
704
705     def getCurrentTrialFunc(self, trial):
706         def getToggleFunc(checked):
707             trial_id = int(trial[0])
708             title = trial[1]
709             if len(title) > 100:
710                 title = title[:97] + '...'
711             self.setWindowTitle(self.tr('minimpy2 [{}]').format(title))
712             self.settings.setValue('last_trial_id', trial_id)
713             self.trial = Trial(self.database.get_trial_setting(trial_id))
714
715         return getToggleFunc
716
717     def action_new(self):
718         self.setCursor(qtc.Qt.ArrowCursor)
719         currentTabIndex = self.ui.tabWidget.currentIndex()
720         if currentTabIndex == 0:  # trials
721             self.add_new_trial()
722         elif currentTabIndex == 2:  # treatments
723             self.add_new_treatment()
724         elif currentTabIndex == 3:  # factors
725             self.add_new_factor()
726         elif currentTabIndex == 5:  # subjects
727             self.add_new_subject()
728
729     def action_save(self):
730         self.setCursor(qtc.Qt.ArrowCursor)
731         currentTabIndex = self.ui.tabWidget.currentIndex()
732         if currentTabIndex == 1:  # setting
733             self.save_trial_setting()
734         elif currentTabIndex == 4:
735             self.freqTable.on_save_preload()
736
737     def add_new_factor(self):
738         self.setCursor(qtc.Qt.ArrowCursor)
739         if self.check_trial_subjects_or_preload():
740             return
741         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
742         if last_trial_id == 0:
743             return
744         title, ok = qtw.QInputDialog.getText(
745             self,
746             self.tr('Factor title'),
747             self.tr('Title'),
748             qtw.QLineEdit.EchoMode.Normal
749         )
750         if ok and len(title.strip()) != 0:
751             if self.database.factor_title_exists(title, last_trial_id):
752                 qtw.QMessageBox.critical(self,
753                                          self.tr('Error!'),
754                                          self.tr('''Factor title '{}' already exist!'''.format(title))
755                                          )
756                 return
757             factor_id = self.database.insert_factor(last_trial_id, title)
758             self.ui.factorTableWidget.blockSignals(True)
759             factor = [str(factor_id), title, '', 1.0]
760             self.add_factor_row(factor)
761             self.ui.factorTableWidget.blockSignals(False)
762
763     def add_new_treatment(self):
764         if self.check_trial_subjects_or_preload():
765             return
766         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
767         if last_trial_id == 0:
768             return
769         title, ok = qtw.QInputDialog.getText(
770             self,
771             self.tr('Treatment title'),
772             self.tr('Title'),
773             qtw.QLineEdit.EchoMode.Normal
774         )
775         if ok and len(title.strip()) != 0:
776             if self.database.treatment_title_exists(title, last_trial_id):
777                 qtw.QMessageBox.critical(self,
778                                          self.tr('Error!'),
779                                          self.tr('''Treatment title '{}' already exist!'''.format(title))
780                                          )
781                 return
782             treatment_id = self.database.insert_treatment(last_trial_id, title)
783             self.ui.treatmentTableWidget.blockSignals(True)
784             cols = [str(treatment_id), title, 1]
785             self.add_treatment_row(cols)
786             self.ui.treatmentTableWidget.blockSignals(False)
787
788     def add_new_trial(self):
789         title, ok = qtw.QInputDialog.getText(
790             self,
791             self.tr('Trial title'),
792             self.tr('Title'),
793             qtw.QLineEdit.EchoMode.Normal
794         )
795         if ok and len(title.strip()) != 0:
796             if self.database.trial_title_exists(title):
797                 qtw.QMessageBox.critical(self,
798                                          self.tr('Error!'),
799                                          self.tr('''A trial title '{}' already exist!'''.format(title))
800                                          )
801                 return
802             trial_id = self.database.insert_trial(title)
803             self.settings.setValue('last_trial_id', trial_id)
804             self.trial = Trial(self.database.get_trial_setting(trial_id))
805             self.load_trials()
806
807     def action_delete(self):
808         self.setCursor(qtc.Qt.ArrowCursor)
809         currentTabIndex = self.ui.tabWidget.currentIndex()
810         if currentTabIndex == 0:
811             self.delete_trial()
812         elif currentTabIndex == 2:  # treatments
813             self.delete_treatment()
814         elif currentTabIndex == 3:  # factors
815             self.delete_factor()
816         elif currentTabIndex == 4:  # preload
817             self.freqTable.on_clear_preload()
818         elif currentTabIndex == 5:  # subjects
819             self.delete_subject()
820
821     def delete_subject(self):
822         selected = self.ui.subjectTableWidget.selectedIndexes()
823         if not selected:
824             return
825         identifier = self.ui.subjectTableWidget.item(selected[0].row(), 1).text()
826         button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
827                                           self.tr('Are you sure you want to delete subject "{}"?').format(identifier),
828                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
829         if button != qtw.QMessageBox.Yes:
830             return
831         subject_id = int(self.ui.subjectTableWidget.item(selected[0].row(), 0).text())
832         subject = self.database.get_subject(subject_id)
833         id_value = subject.value('identifier_value')
834         trial_id = subject.value('trial_id')
835         if self.trial.recycle_ids:
836             id_value = None
837         self.database.delete_subject(subject_id, id_value, trial_id)
838         self.ui.subjectTableWidget.removeRow(selected[0].row())
839         self.update_subjects_progress()
840
841     def check_trial_subjects_or_preload(self):
842         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
843         if last_trial_id == 0:
844             return
845         has_subject = self.database.has_subject(last_trial_id)
846         has_preload = self.database.has_preload(last_trial_id)
847         if has_subject or has_preload:
848             qtw.QMessageBox.warning(self, self.tr('New factor not allowed!'),
849                                     self.tr('''Trial already has subject or preload. 
850                                     You can not add or delete factors, treatments or levels'''))
851             return True
852         return False
853
854     def delete_treatment(self):
855         if self.check_trial_subjects_or_preload():
856             return
857         selected = self.ui.treatmentTableWidget.selectedIndexes()
858         if not selected:
859             return
860         title = self.ui.treatmentTableWidget.item(selected[0].row(), 1).text()
861         button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
862                                           self.tr('Are you sure you want to delete "%s"?') % title,
863                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
864         if button != qtw.QMessageBox.Yes:
865             return
866         treatment_id = int(self.ui.treatmentTableWidget.item(selected[0].row(), 0).text())
867         self.database.delete_treatment(treatment_id)
868         self.ui.treatmentTableWidget.blockSignals(True)
869         self.ui.treatmentTableWidget.removeRow(selected[0].row())
870         self.ui.treatmentTableWidget.blockSignals(False)
871
872     def delete_factor(self):
873         if self.check_trial_subjects_or_preload():
874             return
875         selected = self.ui.factorTableWidget.selectedIndexes()
876         if not selected:
877             return
878         title = self.ui.factorTableWidget.item(selected[0].row(), 1).text()
879         button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
880                                           self.tr('Are you sure you want to delete "%s"?') % title,
881                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
882         if button != qtw.QMessageBox.Yes:
883             return
884         factor_id = int(self.ui.factorTableWidget.item(selected[0].row(), 0).text())
885         self.database.delete_factor(factor_id)
886         self.ui.factorTableWidget.blockSignals(True)
887         self.ui.factorTableWidget.removeRow(selected[0].row())
888         self.ui.factorTableWidget.blockSignals(False)
889
890     def delete_trial(self):
891         selected = self.ui.tialsTableWidget.selectedIndexes()
892         if not selected:
893             return
894         title = self.ui.tialsTableWidget.item(selected[0].row(), 1).text()
895         button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
896                                           self.tr('Are you sure you want to delete "%s"?') % title,
897                                           qtw.QMessageBox.Yes | qtw.QMessageBox.No)
898         if button != qtw.QMessageBox.Yes:
899             return
900         trial_id = int(self.ui.tialsTableWidget.item(selected[0].row(), 0).text())
901         self.database.delete_trial(trial_id)
902         self.setWindowTitle(self.tr('minimpy2'))
903         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
904         if last_trial_id == trial_id:
905             self.settings.setValue('last_trial_id', 0)
906             self.trial = None
907         self.ui.tialsTableWidget.removeRow(selected[0].row())
908
909     def factor_item_changed(self, itemWidget):
910         row, col = itemWidget.row(), itemWidget.column()
911         column = 'title'
912         value = self.ui.factorTableWidget.item(row, col).text()
913         if col == 3:
914             column = 'weight'
915             value = float(value)
916         factor_id = int(self.ui.factorTableWidget.item(row, 0).text())
917         oldValue = self.database.update_factor(factor_id, column, value)
918         if oldValue is not None:
919             self.abort_table_widget_change(self.ui.factorTableWidget, row, col, oldValue)
920
921     def abort_table_widget_change(self, table, row, col, oldValue):
922         table.item(row, col).setText(oldValue)
923
924     def treatment_item_changed(self, itemWidget):
925         row, col = itemWidget.row(), itemWidget.column()
926         column = 'title'
927         value = self.ui.treatmentTableWidget.item(row, col).text()
928         if col == 2:
929             column = 'ratio'
930             value = int(value)
931         treatment_id = int(self.ui.treatmentTableWidget.item(row, 0).text())
932         oldValue = self.database.update_treatment(treatment_id, column, value)
933         if oldValue is not None:
934             print('not save')
935             self.abort_table_widget_change(self.ui.treatmentTableWidget, row, col, oldValue)
936
937     def trial_cell_changed(self, row, col):
938         trial_id = int(self.ui.tialsTableWidget.item(row, 0).text())
939         column = 'title'
940         value = self.ui.tialsTableWidget.item(row, col).text()
941         if col == 2:
942             column = 'code'
943         oldValue = self.database.update_trial(trial_id, column, value)
944         if oldValue is not None:
945             self.abort_table_widget_change(self.ui.tialsTableWidget, row, col, oldValue)
946
947     def load_treatments(self):
948         self.ui.treatmentTableWidget.blockSignals(True)
949         self.ui.treatmentTableWidget.setRowCount(0)
950         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
951         if last_trial_id == 0:
952             self.ui.treatmentTableWidget.blockSignals(False)
953             return
954         query = self.database.load_treatments(last_trial_id)
955         self.ui.treatmentTableWidget.setColumnCount(3)
956         ID = self.tr('ID')
957         Title = self.tr('Title')
958         Ratio = self.tr('Ratio')
959         self.ui.treatmentTableWidget.setHorizontalHeaderLabels([ID, Title, Ratio])
960         header = self.ui.treatmentTableWidget.horizontalHeader()
961         header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
962         header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
963         header.setSectionResizeMode(2, qtw.QHeaderView.ResizeToContents)
964         while query.next():
965             cols = [str(query.value('id')), query.value('title'), query.value('ratio')]
966             self.add_treatment_row(cols)
967         self.ui.treatmentTableWidget.blockSignals(False)
968
969     def add_treatment_row(self, cols):
970         r = self.ui.treatmentTableWidget.rowCount()
971         self.ui.treatmentTableWidget.insertRow(r)
972         id_item = qtw.QTableWidgetItem(cols[0])
973         id_item.setTextAlignment(qtc.Qt.AlignCenter)
974         self.ui.treatmentTableWidget.setCellWidget(r, 0, qtw.QLabel())
975         self.ui.treatmentTableWidget.setItem(r, 0, id_item)
976         title_item = qtw.QTableWidgetItem(cols[1])
977         self.ui.treatmentTableWidget.setItem(r, 1, title_item)
978         ratio_spin = qtw.QSpinBox()
979         ratio_spin.setMinimum(1)
980         ratio_spin.setMaximum(20)
981         ratio_spin.setValue(cols[2])
982         ratio_item = qtw.QTableWidgetItem(str(cols[2]))
983         self.ui.treatmentTableWidget.setItem(r, 2, ratio_item)
984         ratio_spin.valueChanged.connect(lambda value: ratio_item.setText(str(value)))
985         self.ui.treatmentTableWidget.setCellWidget(r, 2, ratio_spin)
986
987     def add_one_trial_cell(self, r, c, trial):
988         item = trial[c]
989         oneItem = qtw.QTableWidgetItem(item)
990         if c == 0:
991             oneItem.setTextAlignment(qtc.Qt.AlignCenter)
992             self.ui.tialsTableWidget.setCellWidget(r, c, qtw.QLabel())
993         if c == 3:
994             check = qtw.QRadioButton()
995             check.setStyleSheet('text-align: center; margin-left:50%; margin-right:50%;')
996             check.toggled.connect(self.getCurrentTrialFunc(trial))
997             check.setChecked(item)
998             self.ui.tialsTableWidget.setCellWidget(r, c, check)
999         self.ui.tialsTableWidget.setItem(r, c, oneItem)
1000
1001     def load_factors(self):
1002         self.ui.factorTableWidget.blockSignals(True)
1003         self.ui.factorTableWidget.setRowCount(0)
1004         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1005         if last_trial_id == 0:
1006             return
1007         self.ui.factorTableWidget.setColumnCount(4)
1008         header = self.ui.factorTableWidget.horizontalHeader()
1009         header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
1010         header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
1011         header.setSectionResizeMode(2, qtw.QHeaderView.Stretch)
1012         self.ui.factorTableWidget.setHorizontalHeaderLabels([self.tr('ID'), self.tr('Factor'), self.tr('Levels'),
1013                                                              self.tr('Weight')])
1014         query = self.database.load_factors(last_trial_id)
1015         factors = []
1016         while query.next():
1017             factor = [str(query.value('id')), query.value('title'), query.value('weight')]
1018             factors.append(factor)
1019         for factor in factors:
1020             levels = self.database.get_levels(int(factor[0]))
1021             factor.insert(2, levels)
1022             self.add_factor_row(factor)
1023         self.ui.factorTableWidget.blockSignals(False)
1024
1025     def factor_column_dblclicked(self, row, col):
1026         if col != 2:
1027             return
1028         self.factor_levels()
1029
1030     def add_factor_row(self, factor):
1031         r = self.ui.factorTableWidget.rowCount()
1032         self.ui.factorTableWidget.insertRow(r)
1033         id_item = qtw.QTableWidgetItem(factor[0])
1034         id_item.setTextAlignment(qtc.Qt.AlignCenter)
1035         self.ui.factorTableWidget.setCellWidget(r, 0, qtw.QLabel())
1036         self.ui.factorTableWidget.setItem(r, 0, id_item)
1037         title_item = qtw.QTableWidgetItem(factor[1])
1038         self.ui.factorTableWidget.setItem(r, 1, title_item)
1039         levels_item = qtw.QTableWidgetItem(factor[2])
1040         levels_item.setTextAlignment(qtc.Qt.AlignCenter)
1041         self.ui.factorTableWidget.setCellWidget(r, 2, qtw.QLabel())
1042         self.ui.factorTableWidget.setItem(r, 2, levels_item)
1043         weight_spin = qtw.QDoubleSpinBox()
1044         weight_spin.setMinimum(1.0)
1045         weight_spin.setMaximum(10.0)
1046         weight_spin.setSingleStep(0.10)
1047         weight_spin.setValue(factor[3])
1048         weight_item = qtw.QTableWidgetItem(str(factor[3]))
1049         self.ui.factorTableWidget.setItem(r, 3, weight_item)
1050         weight_spin.valueChanged.connect(lambda value: weight_item.setText(str(value)))
1051         self.ui.factorTableWidget.setCellWidget(r, 3, weight_spin)
1052
1053     def load_trials(self):
1054         self.ui.tialsTableWidget.blockSignals(True)
1055         self.ui.tialsTableWidget.setRowCount(0)
1056         query = self.database.get_db().exec_('SELECT id, title, code FROM trials')
1057         trial_list = []
1058         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1059         while query.next():
1060             row = [
1061                 str(query.value('id')),
1062                 query.value('title'),
1063                 query.value('code'),
1064                 query.value('id') == last_trial_id
1065             ]
1066
1067             trial_list.append(row)
1068         if len(trial_list) == 0:
1069             return
1070         self.ui.tialsTableWidget.setRowCount(len(trial_list))
1071         self.ui.tialsTableWidget.setColumnCount(len(trial_list[0]))
1072         title = self.tr('Title')
1073         trial_id = self.tr('ID')
1074         code = self.tr('Code')
1075         current = self.tr('Current')
1076         header_lst = [trial_id, title, code, current]
1077         self.ui.tialsTableWidget.setHorizontalHeaderLabels(header_lst)
1078         header = self.ui.tialsTableWidget.horizontalHeader()
1079         header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
1080         header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
1081         header.setSectionResizeMode(2, qtw.QHeaderView.ResizeToContents)
1082         header.setSectionResizeMode(3, qtw.QHeaderView.ResizeToContents)
1083         for r in range(len(trial_list)):
1084             for c in range(len(trial_list[r])):
1085                 self.add_one_trial_cell(r, c, trial_list[r])
1086         self.ui.tialsTableWidget.setColumnWidth(1, 400)
1087         self.ui.tialsTableWidget.blockSignals(False)
1088
1089     def save_trial_setting(self):
1090         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1091         if last_trial_id == 0:
1092             return
1093         self.database.update_trial_setting(last_trial_id, self.ui)
1094         self.setWindowTitle(self.tr('minimpy2 [{}]').format(self.ui.trialTitleLineEdit.text()))
1095         self.load_trial_settings()
1096
1097     def load_trial_settings(self):
1098         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1099         if last_trial_id == 0:
1100             return
1101         if self.database.has_subject(last_trial_id):
1102             self.ui.trialIdentifierTypeComboBox.setEnabled(False)
1103         else:
1104             self.ui.trialIdentifierTypeComboBox.setEnabled(True)
1105         self.database.load_trial_setting(last_trial_id, self.ui)
1106         self.on_identifier_length(self.trial.identifier_length)
1107         self.on_base_prob_change(self.trial.base_prob * 100)
1108
1109     def load_balance(self):
1110         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1111         if last_trial_id == 0:
1112             return
1113         if not self.database.has_preload(last_trial_id) and not self.database.has_subject(last_trial_id):
1114             return
1115         table = []
1116         freq = self.database.get_preload_with_freq(last_trial_id)
1117         treatments = self.database.read_treatments(last_trial_id)
1118         factors = self.database.read_factors(last_trial_id)
1119         for treatment in treatments:
1120             row = []
1121             for factor in factors:
1122                 t = treatment[0]
1123                 f = factor[0]
1124                 levels = self.database.factor_levels(f)
1125                 for level in levels:
1126                     l = level[0]
1127                     k = (t, f, l)
1128                     row.append(freq[k])
1129             table.append(row)
1130         balance = self.get_trial_balance(table, last_trial_id)
1131         variables = []
1132         for factor in factors:
1133             f = factor[0]
1134             levels = self.database.factor_levels(f)
1135             variables.append(list(range(len(levels))))
1136         row = 0
1137         self.ui.balanceTreeWidget.clear()
1138         for idx, variable in enumerate(variables):
1139             wt = factors[idx][2]
1140             var_total = [0] * 4
1141             level_rows = []
1142             for level in variable:
1143                 for i in range(4):
1144                     balance[row][i] *= float(wt)
1145                     var_total[i] += balance[row][i]
1146                 level_rows.append(balance[row])
1147                 row += 1
1148             var_total = [factors[idx][1]] + [var_total[j] / len(variable) for j in range(4)]
1149             var_total = list(map(str, var_total))
1150             variable_node = qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, var_total)
1151             levels = self.database.factor_levels(factors[idx][0])
1152             level_names = [level[1] for level in levels]
1153             for level, level_row in enumerate(level_rows):
1154                 level_str_list = list(map(str, [level_names[level]] + level_row))
1155                 qtw.QTreeWidgetItem(variable_node, level_str_list)
1156         last_row = row
1157         rows = len(balance) - 2
1158         for col in range(4):
1159             balance[rows].append(1.0 * sum([balance[row][col] for row in range(rows)]) / rows)
1160             balance[rows + 1].append(max([balance[row][col] for row in range(rows)]))
1161         means = list(map(str, [self.tr('Mean')] + balance[last_row]))
1162         qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, means)
1163         maxes = list(map(str, [self.tr('Max')] + balance[last_row + 1]))
1164         qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, maxes)
1165         self.ui.balanceProgressBar.setValue(100 - 100 * balance[last_row][0])
1166         self.ui.mean_balance_label.setText(self.tr('Balance (mean MB = {:.4f})').format(balance[last_row][0]))
1167         subjects = self.database.get_subjects(last_trial_id)
1168         if len(subjects) == 0:
1169             self.ui.randomness_label.setText(self.tr('No subject enrolled!'))
1170             self.ui.randomnessProgressBar.setValue(0)
1171             return
1172         else:
1173             self.set_randomness(subjects, last_trial_id)
1174
1175     def set_randomness(self, subjects, trial_id):
1176         id_dict = self.database.get_treatments_id_dict(trial_id)
1177         seq = [id_dict[subject[1]] for subject in subjects]
1178         mean = sum(seq) / len(seq)
1179         seq = list(map(lambda x: 1 if x > mean else 0, seq))
1180         rt = RunTest(seq, 0, 1)
1181         z, p = rt.get_p()
1182         if p == None:
1183             return
1184         self.ui.randomnessProgressBar.setValue(p * 100)
1185         self.ui.randomness_label.setText('Randomness (P = {:.4f})'.format(p))
1186
1187     def get_trial_balance(self, table, trial_id):
1188         model = Model()
1189         m = Minim(random=self.random, model=model)
1190         levels = [[] for col in table[0]]
1191         balances = [[] for col in table[0]] + [[], []]
1192         for row in table:
1193             for col, value in enumerate(row):
1194                 levels[col].append(value)
1195         treatments = self.database.read_treatments(trial_id)
1196         allocation_ratio = [treatment[2] for treatment in treatments]
1197         for row, level_count in enumerate(levels):
1198             adj_count = [(1.0 * level_count[i]) / allocation_ratio[i] for i in range(len(level_count))]
1199             balances[row].append(m.get_marginal_balance(adj_count))
1200             balances[row].append(max(adj_count) - min(adj_count))
1201             balances[row].append(m.get_variance(adj_count))
1202             balances[row].append(m.get_standard_deviation(adj_count))
1203         return balances
1204
1205     def import_mnd_file(self):
1206         filename = qtw.QFileDialog.getOpenFileName(self,
1207                                                    self.tr('Select mnd file'),
1208                                                    qtc.QDir.currentPath(),
1209                                                    'mnd files (*.mnd)')
1210         if filename is None:
1211             return
1212         mnd = Mnd(filename[0])
1213         if mnd.data_file_valid():
1214             button = qtw.QMessageBox.question(self, self.tr("Warning"),
1215                                               mnd.get_detail() + '\n' + self.tr('Do you want to save this trial?'),
1216                                               qtw.QMessageBox.Yes | qtw.QMessageBox.No)
1217             if button != qtw.QMessageBox.Yes:
1218                 return
1219             trial_id = mnd.import_data(self.database)
1220             self.settings.setValue('last_trial_id', trial_id)
1221             self.trial = Trial(self.database.get_trial_setting(trial_id))
1222             if self.ui.tabWidget.currentIndex() != 0:
1223                 self.ui.tabWidget.setCurrentIndex(0)
1224             else:
1225                 self.load_trials()
1226             qtw.QMessageBox.information(self,
1227                                         self.tr('Imported'),
1228                                         self.tr('''Data imported successfully!''')
1229                                         )
1230         else:
1231             qtw.QMessageBox.information(
1232                 self, self.tr('Import error!'),
1233                 self.tr('Data are not valid!')
1234             )
1235
1236     def export_mnd_file(self):
1237         last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1238         if last_trial_id == 0:
1239             return
1240         filename = qtw.QFileDialog.getSaveFileName(self,
1241                                                    self.tr('Export to file'),
1242                                                    qtc.QDir.currentPath(),
1243                                                    'mnd files (*.mnd)')
1244         if filename is None:
1245             return
1246         filename = filename[0]
1247         if not filename.endswith('.mnd'):
1248             filename += '.mnd'
1249         mnd = Mnd(filename)
1250         if mnd.export_data(last_trial_id, self.database):
1251             qtw.QMessageBox.information(self, self.tr('Export'), self.tr('Current trial exported successfully to "{}"').format(filename))
1252
1253
1254 def clickable(widget):
1255     class Filter(qtc.QObject):
1256         clicked = qtc.Signal()
1257         def eventFilter(self, obj, event):
1258             if obj == widget:
1259                 if event.type() == qtc.QEvent.MouseButtonRelease:
1260                     if obj.rect().contains(event.pos()):
1261                         self.clicked.emit()
1262                         # The developer can opt for .emit(obj) to get the object within the slot.
1263                         return True
1264             return False
1265     filter = Filter(widget)
1266     widget.installEventFilter(filter)
1267     return filter.clicked
1268
1269
1270 def start_app():
1271     try:
1272         app = qtw.QApplication(sys.argv)
1273     except RuntimeError:
1274         app = qtc.QCoreApplication.instance()
1275     app.setWindowIcon(qtg.QIcon(resource_path('images/logo.png')))
1276     settings = qtc.QSettings('net.saghaei', 'minimpy2')
1277     lang = settings.value('language', 'en_US', type=str)
1278     if lang != 'en_US':
1279         translator = qtc.QTranslator()
1280         translator.load(resource_path('locales/{}'.format(lang)))
1281         app.installTranslator(translator)
1282     mw = MainWindow(settings)
1283     app.mainWindow = mw
1284     mw.show()
1285     exit_code = app.exec_()
1286     return exit_code
1287
1288
1289 if __name__ == '__main__':
1290     try:
1291         me = singleton.SingleInstance('me')
1292     except SingleInstanceException:
1293         sys.exit(1)
1294     start_app()