1 from PySide2 import QtWidgets as qtw
2 from PySide2 import QtCore as qtc
3 from PySide2 import QtGui as qtg
5 from about_minimisation import AboutMinimisationDialog
6 from config import Config
8 from db import Database
9 from enrol_form import EnrolForm
10 from factor_levels import FactorLevels
11 from freq_table import FreqTable
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
22 # noinspection PyTypeChecker
23 class MainWindow(qtw.QMainWindow):
24 def __init__(self, settings):
25 super(MainWindow, self).__init__()
26 self.prev_tab_index = None
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.''')
52 self.settings = settings
55 engine = self.settings.value('random_engine', 'random', type=str)
56 self.random = Random(engine)
57 self.ui = Ui_MainWindow()
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)
70 self.ui.tabWidget.currentChanged.connect(self.page_changed)
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, ''))
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]
95 self.set_help_handler(w)
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))
102 self.ui.tialsTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
103 self.ui.tialsTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
105 self.ui.treatmentTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
106 self.ui.treatmentTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
108 self.ui.factorTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
109 self.ui.factorTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
111 self.ui.subjectTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
112 self.ui.subjectTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
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)
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)
123 self.freqTable = None
125 if self.settings.value('show_tutorial_at_start', True):
126 self.ui.tabWidget.setCurrentIndex(7)
127 self.ui.showAtStartCheckBox.setChecked(False)
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')])
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)
145 self.settings.setValue('last_trial_id', trial_setting.value('id'))
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)
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)
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)
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)
169 self.ui.tabWidget.setCurrentIndex(0)
170 self.ui.showAtStartCheckBox.setChecked(True)
171 self.ui.showAtStartCheckBox.toggled.connect(self.toggle_show_at_start)
173 def toggle_show_at_start(self, check):
174 self.settings.setValue('show_tutorial_at_start', not check)
176 def on_about_minimpy(self):
177 about = '{}\n{} {} {} ({})\n{} {}\n{}'.format(self.tr('MinimPy'),
182 self.tr('Copyright'),
184 self.tr('Dr. Mahmoud Saghaei'))
185 qtw.QMessageBox.about(self, self.tr('minimpy2'), about)
187 def on_about_minimisation(self):
188 ab_min = AboutMinimisationDialog(self)
191 def set_help_handler(self, w):
193 self.setCursor(qtc.Qt.ArrowCursor)
194 name = w.objectName()
195 for key in self.helpdict:
197 qtw.QMessageBox.information(
198 self, self.tr('Help!'),
199 self.tr(self.helpdict[key])
202 clickable(w).connect(help_handler)
205 cur_lang = self.settings.value('language', 'en_US', type=str)
206 cur_random = self.settings.value('random_engine', 'random', type=str)
208 config = Config(self)
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'))
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!'))
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:
229 qtw.QMessageBox.information(
230 self, self.tr('Help!'),
231 self.tr(self.helpdict[key])
234 def on_arm_weight_change(self, value):
235 self.ui.armWeightLabel.setText('{}'.format(value))
237 def on_base_prob_change(self, value):
238 self.ui.baseProbLabel.setText('{:4.2f}'.format(value / 100.0))
240 def on_identifier_length(self, value):
241 self.trial.identifier_length = value
242 self.update_sample_size_label()
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))
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:
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!')
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)
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)
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)
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)
309 elif currentTabIndex == 6: # Balance
312 def get_factor_level_dict(self, trial_id):
313 factor_list = self.database.read_factors(trial_id)
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])
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']
325 f['selected_level_id'] = -1
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:
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': []}
337 treatment['treatments'].append({'id': t[0], 'title': t[1]})
338 enrol_form = EnrolForm(self, factors, treatment, False)
340 if treatment['selected_treatment_id'] == -1:
342 self.update_subjects_progress()
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:
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:
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]
364 treatments = self.database.read_treatments(last_trial_id)
365 treatment = {'selected_treatment_id': treatment_id, 'treatments': []}
367 treatment['treatments'].append({'id': t[0], 'title': t[1]})
368 enrol_form = EnrolForm(self, factors, treatment, True, identifier)
370 result = enrol_form.result()
371 if result != qtw.QDialog.Accepted:
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']
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()
386 def get_min_free_identifier(self, used_ids):
392 def enrol_one(self, selected_indices, selected_ids):
393 trial_id = self.settings.value('last_trial_id', 0, type=int)
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(
403 'Sample size consumed!\nNo further subject enrol possible!\nPlease convert cases to preload and continue')
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))
411 if self.trial.identifier_order == SEQUENTIAL:
412 identifier_value = self.get_min_free_identifier(used_ids)
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:
425 self.clear_subject_filters()
426 self.select_row(self.ui.subjectTableWidget, subject_id)
427 if p_treatment is None:
429 return treatments[m_treatment][1], \
431 treatments[p_treatment][1], \
433 self.trial.format_subject_identifier(identifier_value), probs
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)
443 def get_minimize_case(self, new_case, trial_id):
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)
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
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):
474 levels = self.database.factor_levels(factor[0])
476 treatment = treatments[row]
477 key = (treatment[0], factor[0], level[0])
478 freq_table[row][v][l] += preload[key]
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:
486 index = self.random.randint(0, len(pv) - 1)
489 v = self.random.randint(0, max_sample_size - 1)
491 v = self.random.randint(0, max_sample_size - 1)
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:
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)
504 def load_subjects(self):
505 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
506 if last_trial_id == 0:
508 self.ui.subjectTableWidget.setRowCount(0)
509 cnt = self.database.get_subject_count(last_trial_id)
510 self.update_subjects_progress()
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
525 header.setSectionResizeMode(
526 self.ui.subjectTableWidget.columnCount() - 2,
527 qtw.QHeaderView.ResizeToContents
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()
538 def subject_colum_editing_finished(self):
539 self.load_subject_rows()
541 def clear_subject_filters(self):
542 for c in range(1, self.ui.subjectTableWidget.columnCount()):
543 lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
545 self.load_subject_rows()
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:
551 subjects = self.database.get_subjects(last_trial_id)
552 self.ui.subjectTableWidget.setRowCount(1)
554 for c in range(1, self.ui.subjectTableWidget.columnCount()):
555 lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
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:
576 if not fields[c] in row_text:
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)]
588 self.ui.subjectTableWidget.setVerticalHeaderLabels(labels)
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:
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())
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)
612 cur_count = self.database.get_empty_freq(last_trial_id)
613 if cur_count is None:
615 self.freqTable.set_counts(cur_count)
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')
623 self.ui.convertWarningCheckBox.setVisible(True)
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)
633 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
634 if last_trial_id == 0:
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)
644 def on_edit_preload(self, check):
645 self.freqTable.toggleReadOnly()
647 def load_frequencies(self):
648 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
649 if last_trial_id == 0:
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)
658 def factor_levels(self):
659 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
660 if last_trial_id == 0:
662 selected = self.ui.factorTableWidget.selectedIndexes()
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)
671 def getCurrentTrialFunc(self, trial):
672 def getToggleFunc(checked):
673 trial_id = int(trial[0])
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))
683 def action_new(self):
684 self.setCursor(qtc.Qt.ArrowCursor)
685 currentTabIndex = self.ui.tabWidget.currentIndex()
686 if currentTabIndex == 0: # trials
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()
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()
703 def add_new_factor(self):
704 self.setCursor(qtc.Qt.ArrowCursor)
705 if self.check_trial_subjects_or_preload():
707 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
708 if last_trial_id == 0:
710 title, ok = qtw.QInputDialog.getText(
712 self.tr('Factor title'),
714 qtw.QLineEdit.EchoMode.Normal
716 if ok and len(title.strip()) != 0:
717 if self.database.factor_title_exists(title, last_trial_id):
718 qtw.QMessageBox.critical(self,
720 self.tr('''Factor title '{}' already exist!'''.format(title))
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)
729 def add_new_treatment(self):
730 if self.check_trial_subjects_or_preload():
732 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
733 if last_trial_id == 0:
735 title, ok = qtw.QInputDialog.getText(
737 self.tr('Treatment title'),
739 qtw.QLineEdit.EchoMode.Normal
741 if ok and len(title.strip()) != 0:
742 if self.database.treatment_title_exists(title, last_trial_id):
743 qtw.QMessageBox.critical(self,
745 self.tr('''Treatment title '{}' already exist!'''.format(title))
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)
754 def add_new_trial(self):
755 title, ok = qtw.QInputDialog.getText(
757 self.tr('Trial title'),
759 qtw.QLineEdit.EchoMode.Normal
761 if ok and len(title.strip()) != 0:
762 if self.database.trial_title_exists(title):
763 qtw.QMessageBox.critical(self,
765 self.tr('''A trial title '{}' already exist!'''.format(title))
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))
773 def action_delete(self):
774 self.setCursor(qtc.Qt.ArrowCursor)
775 currentTabIndex = self.ui.tabWidget.currentIndex()
776 if currentTabIndex == 0:
778 elif currentTabIndex == 2: # treatments
779 self.delete_treatment()
780 elif currentTabIndex == 3: # factors
782 elif currentTabIndex == 4: # preload
783 self.freqTable.on_clear_preload()
784 elif currentTabIndex == 5: # subjects
785 self.delete_subject()
787 def delete_subject(self):
788 selected = self.ui.subjectTableWidget.selectedIndexes()
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:
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:
803 self.database.delete_subject(subject_id, id_value, trial_id)
804 self.ui.subjectTableWidget.removeRow(selected[0].row())
805 self.update_subjects_progress()
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:
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'''))
820 def delete_treatment(self):
821 if self.check_trial_subjects_or_preload():
823 selected = self.ui.treatmentTableWidget.selectedIndexes()
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:
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)
838 def delete_factor(self):
839 if self.check_trial_subjects_or_preload():
841 selected = self.ui.factorTableWidget.selectedIndexes()
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:
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)
856 def delete_trial(self):
857 selected = self.ui.tialsTableWidget.selectedIndexes()
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:
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)
873 self.ui.tialsTableWidget.removeRow(selected[0].row())
875 def factor_item_changed(self, itemWidget):
876 row, col = itemWidget.row(), itemWidget.column()
878 value = self.ui.factorTableWidget.item(row, col).text()
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)
887 def abort_table_widget_change(self, table, row, col, oldValue):
888 table.item(row, col).setText(oldValue)
890 def treatment_item_changed(self, itemWidget):
891 row, col = itemWidget.row(), itemWidget.column()
893 value = self.ui.treatmentTableWidget.item(row, col).text()
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)
902 def trial_cell_changed(self, row, col):
903 trial_id = int(self.ui.tialsTableWidget.item(row, 0).text())
905 value = self.ui.tialsTableWidget.item(row, col).text()
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)
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:
918 query = self.database.load_treatments(last_trial_id)
919 self.ui.treatmentTableWidget.setColumnCount(3)
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)
929 cols = [str(query.value('id')), query.value('title'), query.value('ratio')]
930 self.add_treatment_row(cols)
931 self.ui.treatmentTableWidget.blockSignals(False)
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)
951 def add_one_trial_cell(self, r, c, trial):
953 oneItem = qtw.QTableWidgetItem(item)
955 oneItem.setTextAlignment(qtc.Qt.AlignCenter)
956 self.ui.tialsTableWidget.setCellWidget(r, c, qtw.QLabel())
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)
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:
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'),
978 query = self.database.load_factors(last_trial_id)
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)
989 def factor_coloumn_dblclicked(self, row, col):
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)
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')
1022 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1025 str(query.value('id')),
1026 query.value('title'),
1027 query.value('code'),
1028 query.value('id') == last_trial_id
1031 trial_list.append(row)
1032 if len(trial_list) == 0:
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)
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:
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()
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:
1065 if self.database.has_subject(last_trial_id):
1066 self.ui.trialIdentifierTypeComboBox.setEnabled(False)
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)
1073 def load_balance(self):
1074 last_trial_id = self.settings.value('last_trial_id', 0, type=int)
1075 if last_trial_id == 0:
1077 if not self.database.has_preload(last_trial_id) and not self.database.has_subject(last_trial_id):
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:
1085 for factor in factors:
1088 levels = self.database.factor_levels(f)
1089 for level in levels:
1094 balance = self.get_trial_balance(table, last_trial_id)
1096 for factor in factors:
1098 levels = self.database.factor_levels(f)
1099 variables.append(list(range(len(levels))))
1101 self.ui.balanceTreeWidget.clear()
1102 for idx, variable in enumerate(variables):
1103 wt = factors[idx][2]
1106 for level in variable:
1108 balance[row][i] *= float(wt)
1109 var_total[i] += balance[row][i]
1110 level_rows.append(balance[row])
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)
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)
1137 self.set_randomness(subjects, last_trial_id)
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)
1148 self.ui.randomnessProgressBar.setValue(p * 100)
1149 self.ui.randomness_label.setText('Randomness (P = {:.4f})'.format(p))
1151 def get_trial_balance(self, table, trial_id):
1153 m = Minim(random=self.random, model=model)
1154 levels = [[] for col in table[0]]
1155 balances = [[] for col in table[0]] + [[], []]
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))
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:
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:
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)
1190 qtw.QMessageBox.information(self,
1191 self.tr('Imported'),
1192 self.tr('''Data imported successfully!''')
1195 qtw.QMessageBox.information(
1196 self, self.tr('Import error!'),
1197 self.tr('Data are not valid!')
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:
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:
1210 filename = filename[0]
1211 if not filename.endswith('.mnd'):
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))
1218 def clickable(widget):
1219 class Filter(qtc.QObject):
1220 clicked = qtc.Signal()
1221 def eventFilter(self, obj, event):
1223 if event.type() == qtc.QEvent.MouseButtonRelease:
1224 if obj.rect().contains(event.pos()):
1226 # The developer can opt for .emit(obj) to get the object within the slot.
1229 filter = Filter(widget)
1230 widget.installEventFilter(filter)
1231 return filter.clicked
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)
1243 translator = qtc.QTranslator()
1244 translator.load('locales/{}'.format(lang))
1245 app.installTranslator(translator)
1246 mw = MainWindow(settings)
1249 exit_code = app.exec_()
1253 if __name__ == '__main__':