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