OSDN Git Service

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