#! /usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import pickle
import tempfile
import functions
from model import Model
from minimclass import Minim
from minimpypreferences import PrefWin
import ConfigParser
try:
import pygtk
pygtk.require("2.0")
import gobject
except:
print("pygtk Not Availible")
sys.exit(1)
try:
import gtk
except:
print("GTK Not Availible")
sys.exit(1)
class ModelInteface(object):
def close_window(self, widget, data=None):
if self.trial_file_name:
self.save_trial(self.trial_file_name)
self.window.destroy()
if __name__ == "__main__":
sys.exit(0)
def query_trial_dismis(self):
if self.trial_file_name:
self.save_trial(self.trial_file_name)
else:
if len(self.group_liststore) or len(self.variable_liststore):
msg = "Trial is not saved!\nAre you sure you want to continue?"
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Trial data will be lost!")
if dialog.run() == gtk.RESPONSE_NO:
dialog.destroy()
return False
dialog.destroy()
return True
def delete_event(self, widget, event):
if not self.query_trial_dismis():
# do not exit
return True
self.window.destroy()
if __name__ == "__main__":
sys.exit(0)
def group_cell_edited(self, cell, row, new_text, col):
if col == 0:
# names must not be repeated
for r in range(len(self.group_liststore)):
if r != row and new_text == self.group_liststore[r][0]:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Group name already in use! Choose another name!")
return False
if len(new_text) == 0:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Group name can not be empty!")
return False
if col == 1:
if not new_text.isdigit():
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Allocation ratio must be an integer!")
return False
new_text = int(new_text)
if not new_text:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Allocation ratio must be zero!")
return False
self.group_liststore[row][col] = new_text
self.update_allocations()
def group_add_button(self, widget, data=None):
self.group_liststore.append(('Group {0}'.format(len(self.group_liststore)+1), 1))
self.update_allocations()
def group_delete_button(self, widget, data=None):
if self.group_treeview.get_cursor()[0]:
row = self.group_treeview.get_cursor()[0][0]
self.group_liststore.remove(self.group_liststore.get_iter(row))
for idx, record in enumerate(self.allocations):
if record['allocation'] == row:
self.ui_pool.append(self.allocations[idx]['UI'])
self.allocations[idx] = False
elif record['allocation'] > row:
record['allocation'] -= 1
self.allocations = filter(None, self.allocations)
self.update_allocations()
else:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Please select a group first!")
return False
def variable_cell_edited(self, cell, row, new_text, col):
if col == 0:
for r in range(len(self.variable_liststore)):
if r != row and new_text == self.variable_liststore[r][0]:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: This variable name already in use! Choose another name!")
return False
if len(new_text) == 0:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Variable names can not be empty!")
return False
if col == 1:
new_text = float(new_text)
if not new_text:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Variable weight can not be zero!")
return False
if col == 2:
# check for deleting a level
old_levels = self.variable_liststore[row][col].split(',')
new_levels = new_text.split(',')
if len(new_levels) < 2:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: At least two variable levels should be entered! Separate levels with commas")
return False
if len(old_levels) > len(new_levels):
# which levels has been deleted?
del_levels = list(set(old_levels) - set(new_levels))
# convert items to indices
del_levels = [old_levels.index(level) for level in del_levels]
del_levels.sort()
for idx in range(len(self.allocations)):
for i, level in enumerate(self.allocations[idx]['levels']):
if level in del_levels:
self.ui_pool.append(self.allocations[idx]['UI'])
self.allocations[idx] = False
# deleting false items
self.allocations = filter(None, self.allocations)
for idx in range(len(self.allocations)):
for i, level in enumerate(self.allocations[idx]['levels']):
d = 0
for del_level in del_levels:
# for each del_level less than level the level index number should decrement one unit
if level > del_level:
d += 1
self.allocations[idx]['levels'][i] -= d
self.variable_liststore[row][col] = new_text
self.update_allocations()
def variable_add_button(self, widget, data=None):
self.variable_liststore.append(('Variable {0}'.format(len(self.variable_liststore)+1), 1.0, 'Level 0, Level 1'))
self.update_allocations()
def variable_delete_button(self, widget, data=None):
if self.variable_treeview.get_cursor()[0]:
row = self.variable_treeview.get_cursor()[0][0]
self.variable_liststore.remove(self.variable_liststore.get_iter(row))
for idx in range(len(self.allocations)):
self.allocations[idx]['levels'].pop(row)
self.update_allocations()
else:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Please select a variable first!")
return False
def update_allocations(self):
n = len(self.allocations_treeview.get_columns())
while n:
n = self.allocations_treeview.remove_column(self.allocations_treeview.get_column(n-1))
widgets = self.combo_vbox.get_children()
for widget in widgets:
widget.destroy()
table = gtk.Table(2, len(self.variable_liststore)+1)
table.show()
min_group_old_index = self.min_group_combo.get_active()
if min_group_old_index < 0:
min_group_old_index = 0
if (min_group_old_index + 1) > len(self.group_liststore):
min_group_old_index = 0
if self.min_group_combo.get_model():
self.min_group_combo.get_model().clear()
for group in self.group_liststore:
self.min_group_combo.append_text(group[0])
if len(self.group_liststore):
self.min_group_combo.set_active(min_group_old_index)
self.variable_combos = []
n = 0
for variable in self.variable_liststore:
lbl = gtk.Label(variable[0])
lbl.show()
table.attach(lbl, 0, 2, n, n+1)
n += 1
cbo = gtk.combo_box_new_text()
cbo.show()
for level in map(str.strip, variable[2].split(',')):
cbo.append_text(level)
self.variable_combos.append(cbo)
table.attach(cbo, 0, 2, n, n+1)
n += 1
self.combo_vbox.pack_start(table, False, False)
types = ((str,) * (len(self.variable_liststore) + 2))
liststore = gtk.ListStore(*types)
tvcolumn = gtk.TreeViewColumn('UI')
self.allocations_treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', 0)
tvcolumn.set_resizable(True)
tvcolumn.set_expand(True)
tvcolumn = gtk.TreeViewColumn('Group')
self.allocations_treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', 1)
tvcolumn.set_resizable(True)
tvcolumn.set_expand(True)
n = 1
for variable in self.variable_liststore:
n += 1
tvcolumn = gtk.TreeViewColumn(variable[0])
self.allocations_treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', n)
tvcolumn.set_resizable(True)
tvcolumn.set_expand(True)
self.allocations_treeview.set_model(liststore)
self.allocations_treeview.show()
variable_models = [cbo.get_model() for cbo in self.variable_combos]
for row, record in enumerate(self.allocations):
liststore.append()
liststore[row][0] = record['UI']
liststore[row][1] = self.group_liststore[record['allocation']][0]
for col, variable_model in enumerate(variable_models):
liststore[row][col+2] = variable_model[record['levels'][col]][0]
#self.window.show_all()
def get_minimize_case(self, new_case):
model = Model()
model.groups = range(len(self.group_liststore))
model.variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
model.variables_weight = [self.variable_liststore[row][1] for row in range(len(self.variable_liststore))]
model.allocation_ratio = [self.group_liststore[row][1] for row in range(len(self.group_liststore))]
model.allocations = self.allocations
model.prob_method = self.get_prob_method()
model.distance_measure = self.get_distance_measure()
model.high_prob = self.high_prob_spin.get_value()
model.min_group = self.min_group_combo.get_active()
m = Minim(random=self.random, model=model)
m.build_probs(model.high_prob, model.min_group)
m.build_freq_table()
if self.initial_freq_table:
self.add_to_initial(m.freq_table)
return m.enroll(new_case, m.freq_table)
def add_to_initial(self, freq_table):
for row, group in enumerate(freq_table):
for v, variable in enumerate(group):
for l, level in enumerate(variable):
freq_table[row][v][l] += self.initial_freq_table[row][v][l]
def remove_last_allocation(self, prompt=True):
model = self.allocations_treeview.get_model()
if model == None or len(model) == 0:
if self.trial_file_name:
self.want_trial_unlock(prompt)
return False
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: No subject allocated!")
return False
if prompt:
msg = "Deallocation may introduce errors in randomization!\nAre you sure you want to undo the last allocation?"
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Undo Allocation!")
if dialog.run() == gtk.RESPONSE_NO:
dialog.destroy()
return False
dialog.destroy()
row = len(model)-1
self.ui_pool.append(model[row][0])
path = (row,)
iter = model.get_iter(path)
model.remove(iter)
self.allocations.pop()
self.save_trial(self.trial_file_name)
return True
def allocations_undo_button(self, widget, data=None):
self.remove_last_allocation()
def allocations_clear_button(self, widget, data=None):
model = self.allocations_treeview.get_model()
if model == None or len(model) == 0:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Allocation table is empy!")
return
msg = "Are you sure you want to remove all allocations!\nData will be lost permanently, and the trial will be unlocked?"
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Clear Allocations!")
if dialog.run() == gtk.RESPONSE_NO:
dialog.destroy()
return False
dialog.destroy()
while self.remove_last_allocation(False):
pass
def allocations_add_all_button(self, widget, data=None):
model = self.allocations_treeview.get_model()
if not model:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Groups and prognostic factors must be defined first!")
return False
if not self.ui_pool:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, 'Error: Either this trial is finished or has not been saved!')
return False
while self.ui_pool:
for cbo in self.variable_combos:
item = self.random.choice(range(len(cbo.get_model())))
cbo.set_active(item)
model.append()
ui = self.ui_pool.pop()
new_case = {'levels': [], 'allocation': -1, 'UI': ui}
row = len(model)-1
model[row][0] = ui
for col, cbo in enumerate(self.variable_combos):
model[row][col+2] = cbo.get_active_text()
new_case['levels'].append(cbo.get_active())
m_group = self.get_minimize_case(new_case)
new_case['allocation'] = m_group
model[row][1] = self.group_liststore[m_group][0]
self.allocations.append(new_case)
self.save_trial(self.trial_file_name)
def allocations_add_button(self, widget, data=None):
model = self.allocations_treeview.get_model()
if not model:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Groups and prognostic factors must be defined first!")
return False
for cbo in self.variable_combos:
if cbo.get_active() == -1:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, 'Error: Some factor levels have not been selected for this case!')
return False
if not self.ui_pool:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, 'Error: Either this trial is finished or has not been saved!')
return False
model.append()
ui = self.ui_pool.pop()
new_case = {'levels': [], 'allocation': -1, 'UI': ui}
row = len(model)-1
model[row][0] = ui
for col, cbo in enumerate(self.variable_combos):
model[row][col+2] = cbo.get_active_text()
new_case['levels'].append(cbo.get_active())
m_group = self.get_minimize_case(new_case)
new_case['allocation'] = m_group
model[row][1] = self.group_liststore[m_group][0]
self.allocations.append(new_case)
self.save_trial(self.trial_file_name)
self.report_allocation(model)
if len(self.ui_pool) == 0:
self.end_trial()
def report_allocation(self, model):
column = self.allocations_treeview.get_columns()
row = len(model)-1
ret = []
for col, column in enumerate(column):
ret.append(column.get_title() + ': ' + model[row][col])
msg = "New case allocated\n" + '\n'.join(ret)
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format = msg)
dialog.set_title("New Allocation!")
dialog.run()
dialog.destroy()
def end_trial(self):
msg = "{0} cases have been minimized!\nDo you like to extend the sample size".format(len(self.allocations))
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Trial finished!")
if dialog.run() == gtk.RESPONSE_YES:
extra_sample = ""
while not extra_sample.isdigit():
extra_sample = functions.simple_dialog("Extra Sample", self.window)
ss = len(self.allocations)+int(extra_sample)
self.build_ui_pool(range(len(self.allocations), ss))
model = self.allocations_treeview.get_model()
for row in range(len(model)):
model[row][0] = model[row][0].zfill(len(str(ss)))
else:
self.ui_pool = None
dialog.destroy()
def refresh_freq_table(self):
model = Model()
model.groups = range(len(self.group_liststore))
model.variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
model.variables_weight = [self.variable_liststore[row][1] for row in range(len(self.variable_liststore))]
model.allocation_ratio = [self.group_liststore[row][1] for row in range(len(self.group_liststore))]
model.allocations = self.allocations
m = Minim(random=self.random, model=model)
m.build_freq_table()
if self.initial_freq_table:
self.add_to_initial(m.freq_table)
if not len(m.freq_table):
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: No allocation!")
return False
n = len(self.freq_table_treeview.get_columns())
while n:
n = self.freq_table_treeview.remove_column(self.freq_table_treeview.get_column(n-1))
#self.initial_table_hbox.hide()
# following for test only
# m.freq_table = [[[18, 25], [6, 3, 3, 4, 3, 6, 4, 5, 5, 4]], [[39, 45], [12, 8, 7, 7, 7, 9, 6, 12, 11, 5]], [[55, 68], [17, 12, 10, 14, 10, 13, 8, 16, 15, 8]]]
cols = 0
column_names = []
for variable in self.variable_liststore:
column_names.extend([level for level in map(str.strip, variable[2].split(','))])
column_names = ['Group'] + column_names + ['Total']
for var in m.freq_table[0]:
cols += len(var)
col_types = [str] + [int] * (cols + 1)
liststore = gtk.ListStore(*col_types)
for n in range(len(column_names)):
tvcolumn = gtk.TreeViewColumn(column_names[n])
self.freq_table_treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
#cell.set_property('editable', True)
#cell.connect("edited", edit_handler, n)
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', n)
tvcolumn.set_resizable(True)
tvcolumn.set_expand(True)
self.freq_table_treeview.set_model(liststore)
for row, group in enumerate(m.freq_table):
liststore.append()
liststore[row][0] = self.group_liststore[row][0]
col = 1
for variable in group:
for level in variable:
liststore[row][col] = level
col += 1
liststore[row][col] = sum(variable)
liststore.append()
liststore[len(m.freq_table)][0] = "Total"
total = 0
for c in range(1, col):
col_total = sum([liststore[row][c] for row in range(len(m.freq_table))])
total += col_total
liststore[len(m.freq_table)][c] = col_total
liststore[len(m.freq_table)][c+1] = total / len(self.variable_liststore)
if self.freq_table_enable_edit_button.get_label() == "Save as Initial Table":
self.freq_table_enable_edit()
#self.window.show_all()
def notebook_switch_page(self, notebook, page, page_num, data=None):
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, self.notbook_tabs[page_num])
if page_num < 4: return
if not self.trial_is_valid(show_msg=False): return
if self.freq_table_enable_edit_button.get_label() == "Save as Initial Table": return
self.refresh_freq_table()
self.balance_table_refresh()
def license_button_clicked(self, widget, data=None):
self.display_about_dialog()
def display_about_dialog(self):
dialog = gtk.AboutDialog()
dialog.set_name("MinimPy Program")
dialog.set_version("0.1")
dialog.set_copyright("Copyright (c) 2010-2011 Mahmoud Saghaei")
dialog.set_license("Distributed under the GNU GPL v3.\nFor full terms refer to http://www.gnu.org/copyleft/gpl.html.")
dialog.set_website("http://minimpy.sourceforge.net")
dialog.set_authors(["Mahmoud Saghaei"])
dialog.set_logo(self.pixbuf)
dialog.run()
dialog.destroy()
return False
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type =
gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format = "XKnight game\nCopyright (c) 2008-2009 Mahmoud Saghaei\nDistributed under the GNU GPL v3.\nFor full terms refer to http://www.gnu.org/copyleft/gpl.html.")
dialog.set_title("About XKnight Game!")
dialog.run()
dialog.destroy()
def trial_properties_cell_edited(self, cell, row, new_text, col):
self.trial_properties_liststore[row][col] = new_text
def trial_properties_add_button(self, widget, data=None):
self.trial_properties_liststore.append(('Property Name', 'Property Value'))
def trial_properties_delete_button(self, widget, data=None):
if self.trial_properties_treeview.get_cursor()[0]:
row = self.trial_properties_treeview.get_cursor()[0][0]
self.trial_properties_liststore.remove(self.trial_properties_liststore.get_iter(row))
def show_info_message(self, widget, event, message, tooltip):
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format = message)
dialog.set_title(tooltip)
dialog.run()
dialog.destroy()
def create_image_info(self, parent, tooltip, message=""):
eb = gtk.EventBox()
img = gtk.Image()
image_file = os.path.join(os.path.dirname(sys.argv[0]), 'images', 'b_info.png')
img.set_from_file(image_file)
eb.add(img)
if len(message):
eb.connect("button-press-event", self.show_info_message, message, tooltip)
tooltip += "\nClick to see more"
img.set_tooltip_text(tooltip)
img.set_has_tooltip(True)
img.connect('query-tooltip', self.img_query_tooltip)
parent.pack_start(eb, False, False)
def img_query_tooltip(widget, x, y, keyboard_mode, tooltip, user_param1=None):
return False
def notebook_move_focus_out(self, notebook, direction_type, data=None):
print direction_type
def __init__(self):
self.config_file = os.path.join(os.path.dirname(sys.argv[0]), 'config.cfg')
self.config = ConfigParser.RawConfigParser()
self.config.read(self.config_file)
self.random = functions.get_app_random(self.config)
if self.config.getboolean('project', 'load_recent'):
self.trial_file_name = self.config.get('project', 'recent_trial')
else:
self.trial_file_name = None
self.notbook_tabs = []
self.ui_pool = None
self.initial_freq_table = False
self.allocations = []
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Minimization Program")
self.window.connect("delete_event", self.delete_event)
self.window.set_border_width(2)
self.window.set_size_request(750, 400)
self.notebook = gtk.Notebook()
self.notebook.set_tab_pos(self.config.getint('interface', 'tab_pos'))
self.notebook.popup_enable()
self.notebook.connect("switch-page", self.notebook_switch_page)
self.notebook.connect("move-focus-out", self.notebook_move_focus_out)
img = gtk.Image()
image_file = os.path.join(os.path.dirname(sys.argv[0]), 'images', 'logo.png')
img.set_from_file(image_file)
img.show()
self.pixbuf = img.get_pixbuf()
win_vbox = gtk.VBox(False)
win_vbox.pack_start(self.notebook, True, True)
self.statusbar = gtk.Statusbar()
win_vbox.pack_start(self.statusbar, False, False, 0)
self.sb_context_id = self.statusbar.get_context_id("Statusbar")
nb_vbx = gtk.VBox(False)
hbx1 = gtk.HBox(False)
label = gtk.Label("Trial Title: ")
hbx1.pack_start(label, False, False)
self.create_image_info(hbx1, "Trial title!", "Please enter a valid title for your trial!\nTrial title is mandatory!")
self.trial_title_entry = gtk.Entry()
hbx1.pack_start(self.trial_title_entry, True, True)
label = gtk.Label("Sample Size: ")
hbx1.pack_start(label, False, False)
self.create_image_info(hbx1, "Trial sample size!", "Please enter the trial sample size!\nTrial sample size is mandatory!")
adj = gtk.Adjustment(value=30, lower=5, upper=1000000, step_incr=1, page_incr=10)
self.trial_sample_size_spin = gtk.SpinButton(adj)
hbx1.pack_start(self.trial_sample_size_spin, False, False)
nb_vbx.pack_start(hbx1, False, False)
hbx2 = gtk.HBox(False)
label = gtk.Label("Description: ")
hbx2.pack_start(label, False, False)
self.create_image_info(hbx2, "Trial description!", "You may enter a description for your trial!\nTrial description is optional!")
nb_vbx.pack_start(hbx2, False, False)
sep = gtk.HSeparator()
nb_vbx.pack_start(sep, False, False)
hbx3 = gtk.HBox(False)
sep = gtk.VSeparator()
hbx3.pack_start(sep, False, False)
self.trial_description_text = gtk.TextView()
hbx3.pack_start(self.trial_description_text, True, True)
sep = gtk.VSeparator()
hbx3.pack_start(sep, False, False)
nb_vbx.pack_start(hbx3, True, True)
sep = gtk.HSeparator()
nb_vbx.pack_start(sep, False, False)
hbx4 = gtk.HBox(False)
nb_vbx.pack_start(hbx4, True, True)
label = gtk.Label("Probability Method: ")
hb = gtk.HBox(False)
hb.pack_start(label, False, False)
self.create_image_info(hb, "Probability Method", """Probability of assignment subjects to trial groups!\n
The subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. In the simple form 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. If treatments with higher allocations ratios are assigned more probabilities this will be biased-coin minimization. In this method a base probability is used for group with the lowest allocation ratio every time 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 allocation ratios
""")
vbox_prob = gtk.VBox(False)
vbox_prob.pack_start(hb, False, False)
radio = gtk.RadioButton(None, label="Biased Coin Minimization")
vbox_prob.pack_start(radio, False, False)
radio = gtk.RadioButton(radio, label="Naive Minimization")
vbox_prob.pack_start(radio, False, False)
# this method returns the radios in revese order
self.prob_method_radios = radio.get_group()
# so we need to invert the returned list
self.prob_method_radios.reverse()
hb = gtk.HBox(False)
lbl = gtk.Label("Min Group: ")
hb.pack_start(lbl, False, False)
self.create_image_info(hb, "Group with the lowest allocation ratio", "In the biased-coin minimization it is the index of the group with the lowest allocation ratio. Select the group index from the menu. It has no effect in naive minimization")
self.min_group_combo = gtk.combo_box_new_text()
hb.pack_start(self.min_group_combo, False, False)
vbox_prob.pack_start(hb, False, False)
hb = gtk.HBox(False)
lbl = gtk.Label("High Probability: ")
hb.pack_start(lbl, False, False)
self.create_image_info(hb, "Probability for the Min Group", """The amount of probability given to the group 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. In naive minimization this will be the base probability""")
adj = gtk.Adjustment(value=0.7, lower=0.1, upper=1.0, step_incr=0.01, page_incr=0.1)
self.high_prob_spin = gtk.SpinButton(adj, digits=2)
hb.pack_start(self.high_prob_spin, False, False)
vbox_prob.pack_start(hb, False, False)
hbx4.pack_start(vbox_prob, False, False)
sep = gtk.VSeparator()
hbx4.pack_start(sep, False, False)
label = gtk.Label("Distance Measure: ")
hb = gtk.HBox(False)
hb.pack_start(label, False, False)
self.create_image_info(hb, "Distanc measure used to calculate the imbalance score!")
vbox = gtk.VBox(False)
vbox.pack_start(hb, False, False)
radio = gtk.RadioButton(None, label="Marginal Balance: ")
hb = gtk.HBox(False)
hb.pack_start(radio, False, False)
self.create_image_info(hb, "Marginal balance", "Marginal balance proposed by Han and co-workers which tend 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.")
vbox.pack_start(hb, False, False)
radio = gtk.RadioButton(radio, label="Range: ")
hb = gtk.HBox(False)
hb.pack_start(radio, False, False)
vbox.pack_start(hb, False, False)
self.create_image_info(hb, "Range", "For each factor, range is calculated as the difference between adjusted maximum and minimum counts of subject accross all factor levels for each group.")
radio = gtk.RadioButton(radio, label="Standard Deviation: ")
hb = gtk.HBox(False)
hb.pack_start(radio, False, False)
vbox.pack_start(hb, False, False)
self.create_image_info(hb, "Standard Deviation", "For each factor, SD is calculated as the SD of adjusted count values of subject accross all factor levels for each group.")
radio = gtk.RadioButton(radio, label="Variance: ")
hb = gtk.HBox(False)
hb.pack_start(radio, False, False)
vbox.pack_start(hb, False, False)
self.create_image_info(hb, "Variance", "For each factor, variance is calculated as the variance of adjusted count values of subject accross all factor levels for each group.")
hbx4.pack_start(vbox, False, False)
self.distance_measure_radios = radio.get_group()
self.distance_measure_radios.reverse()
sep = gtk.VSeparator()
hbx4.pack_start(sep, False, False)
vbox = gtk.VBox(False)
lbl = gtk.Label("Trial Properties: ")
hb = gtk.HBox(False)
hb.pack_start(lbl, False, False)
vbox.pack_start(hb, False, False)
self.create_image_info(hb, "Trial Properties", "Miscelaneous trial properties such as co-ordinator, moderator, investigator, data analyser, etc. Each property must have a name and value (description)")
hb = gtk.HBox(False)
self.trial_properties_liststore, self.trial_properties_treeview = functions.make_treeview(
(str, str), ['Name', 'Value'],
editable=True, edit_handler=self.trial_properties_cell_edited)
hb.pack_start(self.trial_properties_treeview, True, True)
vbuttonbox = gtk.VButtonBox()
vbuttonbox.set_layout(gtk.BUTTONBOX_START)
button = gtk.Button(None, gtk.STOCK_ADD)
button.connect("clicked", self.trial_properties_add_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_DELETE)
button.connect("clicked", self.trial_properties_delete_button)
vbuttonbox.pack_start(button, False, False)
hb.pack_start(vbuttonbox, False, False)
vbox.pack_start(hb, False, False)
hbx4.pack_start(vbox, True, True)
sep = gtk.VSeparator()
hbx4.pack_start(sep, False, False)
sep = gtk.HSeparator()
nb_vbx.pack_start(sep, False, False)
hbuttonbox = gtk.HButtonBox()
hbuttonbox.set_layout(gtk.BUTTONBOX_START)
self.trial_new_button = gtk.Button(None, gtk.STOCK_NEW)
self.trial_new_button.connect("clicked", self.new_trial_clicked)
hbuttonbox.pack_start(self.trial_new_button, False, False)
self.trial_load_button = gtk.Button(None, gtk.STOCK_OPEN)
self.trial_load_button.connect("clicked", self.load_trial_clicked)
hbuttonbox.pack_start(self.trial_load_button, False, False)
self.trial_save_button = gtk.Button(None, gtk.STOCK_SAVE)
self.trial_save_button.connect("clicked", self.save_trial_clicked)
hbuttonbox.pack_start(self.trial_save_button, False, False)
self.pref_button = gtk.Button(None, gtk.STOCK_PREFERENCES)
self.pref_button.connect("clicked", self.pref_clicked)
hbuttonbox.pack_start(self.pref_button, False, False)
nb_vbx.pack_start(hbuttonbox, False, False)
lbl = gtk.Label("Settings")
self.notbook_tabs.append("Settings: Define and save a new trial or load a previous trial")
self.notebook.append_page(nb_vbx, lbl)
hbox = gtk.HBox(False)
self.group_liststore, self.group_treeview = functions.make_treeview(
(str, int), ['Group Name', 'Allocation Ratio'],
editable=True, edit_handler=self.group_cell_edited)
vb = gtk.VBox(False)
self.create_image_info(vb, "Groups Table", "Click add to add a new group. Then click on the group name or allocation ratio to edit the value. After finishing the edit, hit enter key to fix the edited value. Allocation ratios are integer numeric values. Click delete to delete the selected group.")
vb.pack_start(self.group_treeview, True, True)
hbox.pack_start(vb, True, True)
vbuttonbox = gtk.VButtonBox()
vbuttonbox.set_layout(gtk.BUTTONBOX_START)
button = gtk.Button(None, gtk.STOCK_ADD)
button.connect("clicked", self.group_add_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_DELETE)
button.connect("clicked", self.group_delete_button)
vbuttonbox.pack_start(button, False, False)
hbox.pack_start(vbuttonbox, False, False)
lbl = gtk.Label('Groups')
self.notbook_tabs.append("Groups: Define trial groups and their allocation ratios")
self.group_hbox = hbox
self.notebook.append_page(hbox, lbl)
hbox = gtk.HBox(False)
self.variable_liststore, self.variable_treeview = functions.make_treeview(
(str, float, str), ['Variable Name', 'Weight', 'Levels'],
editable=True, edit_handler=self.variable_cell_edited)
vb = gtk.VBox(False)
self.create_image_info(vb, "Variable Table", "Click add to add a new variable. Then click on the variable name, weight or levels to edit the value. After finishing the edit, hit enter key to fix the edited value. Variable weights are decimal fraction values denoting the relative weight (importance) of that variable in minimization. Variable levels are the labels of levels of each variable separated by a comma. Click delete to delete the selected variable.")
vb.pack_start(self.variable_treeview, True, True)
hbox.pack_start(vb, True, True)
vbuttonbox = gtk.VButtonBox()
vbuttonbox.set_layout(gtk.BUTTONBOX_START)
button = gtk.Button(None, gtk.STOCK_ADD)
button.connect("clicked", self.variable_add_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_DELETE)
button.connect("clicked", self.variable_delete_button)
vbuttonbox.pack_start(button, False, False)
hbox.pack_start(vbuttonbox, False, False)
lbl = gtk.Label('Variables')
self.notbook_tabs.append("Variables: Define trial prognostic factors, their weights and levels")
self.variable_hbox = hbox
self.notebook.append_page(hbox, lbl)
hbox = gtk.HBox(False)
vbox = gtk.VBox(False)
self.allocations_treeview = gtk.TreeView()
vb = gtk.VBox(False)
self.create_image_info(vb, "Allocations Table", "After saving the trial settings, defining groups and variables, now you can allocate incoming subjects to your trial. If the variables are defined you will see drop-down menus, one for each variable. Each menu contains lables, one for each levels within that group. For incominig subjects you have to select appropriate variable levels from these menus and then click add to minimize that subject into your trial. The the program will estimagte the best group for the specified subject based on the minimization settings, and allocate it to the trial. The subject will be added to the allocation table and a dialog will show the minimization result. In the minimization table for each subject you will see the UI (the unique identifier), group and the values level for each variable. Click 'Undo' to remove the last enrolled subject from the table. After removing all subjects, if you hit 'Undo' button for another time, a dialogue will appear and will ask you if you want to unlock the trial. Clicking the 'Clear' button will clear the table and unlock the trial instantly.")
vb.pack_start(self.allocations_treeview, True, True)
hbox.pack_start(vb, True, True)
vbuttonbox = gtk.VButtonBox()
vbuttonbox.set_layout(gtk.BUTTONBOX_START)
if self.config.getboolean('interface', 'research_mode'):
button = gtk.Button("Add All")
button.connect("clicked", self.allocations_add_all_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_ADD)
button.connect("clicked", self.allocations_add_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_UNDO)
button.connect("clicked", self.allocations_undo_button)
vbuttonbox.pack_start(button, False, False)
button = gtk.Button(None, gtk.STOCK_CLEAR)
button.connect("clicked", self.allocations_clear_button)
vbuttonbox.pack_start(button, False, False)
vbox.pack_start(vbuttonbox, False, False)
self.combo_vbox = gtk.VBox(False)
vbox.pack_start(self.combo_vbox, False, False)
hbox.pack_start(vbox, False, False)
lbl = gtk.Label('Allocations')
self.notbook_tabs.append('Allocations: Allocate subjects to treatments. To use minimization select "Minimize" as group name')
self.notebook.append_page(hbox, lbl)
vbox = gtk.VBox(False)
self.create_image_info(vbox, "Frequency table!", "This is the frequency table, showing the counts of different factor levels in each group for subjects already allocated! You can change this table to set an initial allocation load (initial frequency table). This is usually need when you have a number of subject already allocated by any method, and you want to use minimpy for allocation of remaining subjects. This table is editable only at the statrt of the trial, when you are configuring setting. After setting trial title, description, group, variables, etc. you can specify an initial frequency table. Then click on the 'Start Edit; button to make the table editable. The button changes to 'Save as Initial Table'. To change values click on the value, edit it and hit the enter key. After entering the initial frequency table hit the 'Save as Initial Table' button to save this table as the initial frequency table. To clear the initial frequency table and refreshing rows and columns labels click 'Clear / Refresh'.")
self.freq_table_treeview = gtk.TreeView()
vbox.pack_start(self.freq_table_treeview, False, False)
self.initial_table_hbox = gtk.HBox(False)
self.freq_table_enable_edit_button = gtk.Button("Start Edit")
self.freq_table_enable_edit_button.connect("clicked", self.freq_table_start_edit_clicked)
self.initial_table_hbox.pack_start(self.freq_table_enable_edit_button, False, False)
button = gtk.Button("Clear / Refresh")
button.connect("clicked", self.freq_table_delete_initial_clicked)
self.initial_table_hbox.pack_start(button, False, False)
vbox.pack_start(self.initial_table_hbox, False, False)
lbl = gtk.Label('Table')
self.notbook_tabs.append("Table: Displays frequency table for different levels of prognostic factors")
self.notebook.append_page(vbox, lbl)
hbox = gtk.HBox(False)
vb = gtk.VBox(False)
self.create_image_info(vb, "Balance Table", "This table shows the overall balance of different factors levels amoung trial groups. Four different imbalance scores are displayed, and are grouped by each variable, levels within each variable. Overall trial balance is displayed as mean and maximum values observed in each column")
treestore = gtk.TreeStore(str, float, float, float, float)
self.balance_table_treeview = gtk.TreeView(treestore)
col_names = ['Variable/Level', 'Marginal Balance', 'Range', 'Variance', 'SD']
for i in range(5):
cell = gtk.CellRendererText()
column = gtk.TreeViewColumn(col_names[i], cell, text=i)
self.balance_table_treeview.append_column(column)
vb.pack_start(self.balance_table_treeview, True, True)
hbox.pack_start(vb, True, True)
lbl = gtk.Label('Balance')
self.notbook_tabs.append("Balance: Displays current trial balance as four different balance measures")
self.notebook.append_page(hbox, lbl)
vb = gtk.VBox(False)
self.create_image_info(vb, "Export Date", "In this page you can export the trial data to a csv file. You may like to choose between data and schema or both.")
sep = gtk.HSeparator()
vb.pack_start(sep, False, False)
lbl = gtk.Label("Field Arrangement")
vb.pack_start(lbl, False, False)
hb = gtk.HBox(True)
self.export_arrange = 'Delimited'
radio = gtk.RadioButton(label="Delimited")
radio.set_active(True)
radio.connect("toggled", self.export_arrangement_toggled)
hb.pack_start(radio, False, False)
radio = gtk.RadioButton(radio, label="Fixed Width")
radio.connect("toggled", self.export_arrangement_toggled)
hb.pack_start(radio, False, False)
self.export_firstrow_var = True
chk = gtk.CheckButton("First row is variable names")
chk.set_active(True)
chk.connect("toggled", self.export_varnames_chk)
hb.pack_start(chk, False, False)
vb.pack_start(hb, True, False)
sep = gtk.HSeparator()
vb.pack_start(sep, False, False)
lbl = gtk.Label("Field Separator")
vb.pack_start(lbl, False, False)
table = gtk.Table()
self.export_separator = ','
radio = gtk.RadioButton(label="Space ( )")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 0, 1, 0, 1)
radio = gtk.RadioButton(radio, label="Tab (\t)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 1, 2, 0, 1)
radio = gtk.RadioButton(radio, label="Colon (:)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 2, 3, 0, 1)
radio = gtk.RadioButton(radio, label="Comma (,)")
radio.set_active(True)
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 3, 4, 0, 1)
radio = gtk.RadioButton(radio, label="Hyphen (-)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 0, 1, 1, 2)
radio = gtk.RadioButton(radio, label="Pipe (|)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 1, 2, 1, 2)
radio = gtk.RadioButton(radio, label="Semicolon (;)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 2, 3, 1, 2)
radio = gtk.RadioButton(radio, label="Slash (/)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 3, 4, 1, 2)
radio = gtk.RadioButton(radio, label="Bang (!)")
radio.connect("toggled", self.export_separator_toggled)
table.attach(radio, 0, 1, 2, 3)
hb = gtk.HBox(False)
radio = gtk.RadioButton(radio, label="Custom")
hb.pack_start(radio, False, False)
entry = gtk.Entry(max=1)
radio.connect("toggled", self.export_separator_toggled, entry)
hb.pack_start(entry, False, False)
table.attach(hb, 2, 3, 2, 3)
self.export_delimited_table = table
vb.pack_start(table, True, False)
sep = gtk.HSeparator()
vb.pack_start(sep, False, False)
lbl = gtk.Label("Choose the items for export")
vb.pack_start(lbl, False, False)
hb = gtk.HBox(True)
chk = gtk.CheckButton("Allocations")
chk.set_active(True)
chk.connect("toggled", self.export_items_chk, 0)
hb.pack_start(chk, False, False)
chk = gtk.CheckButton("Settings")
chk.set_active(True)
chk.connect("toggled", self.export_items_chk, 1)
hb.pack_start(chk, False, False)
chk = gtk.CheckButton("SPSS syntax")
chk.set_active(True)
chk.connect("toggled", self.export_items_chk, 2)
hb.pack_start(chk, False, False)
self.export_items = [True, True, True]
vb.pack_start(hb, True, False)
sep = gtk.HSeparator()
vb.pack_start(sep, False, False)
hbuttonbox = gtk.HButtonBox()
button = gtk.Button("Export")
button.connect("clicked", self.export_data)
hbuttonbox.pack_start(button, False, False)
vb.pack_start(hbuttonbox, True, True)
lbl = gtk.Label('Export')
self.notbook_tabs.append("Export: Export the trial data as a CSV file")
self.notebook.append_page(vb, lbl)
hbox = gtk.HBox(True)
self.about_text = gtk.TextView()
self.about_text.set_wrap_mode(gtk.WRAP_WORD)
self.about_text.set_editable(False)
self.about_text.set_cursor_visible(False)
self.about_text.set_left_margin(20)
self.about_text.set_right_margin(20)
about_text = """
*************************** MinimPy 0.1 ***********************************
Copyright (c) 2010 Mahmoud Saghaei
http://www.saghaei.com
MinimPy is a desktop minimization program for sequential randomization of subject to treatment groups in a clinical trial.
With this program you can define a new minimization model specifying the probability method used for assigning subject to treatment groups, distance measure which is the imbalance score at a the time of allocation considering the levels of prognostic factor for the a new subject which going to be allocated. Main features of MinimPy include total control over choice of probability method, distance measure, defining an initial minimization preload, adding a new method of probability assignment as biased coin minimization and another as marginal balance which has been shown to be superior to other distance measures.
"""
functions.set_text_buffer(self.about_text, about_text)
hbox.pack_start(self.about_text)
button = gtk.Button(None, gtk.STOCK_ABOUT)
button.connect("clicked", self.license_button_clicked)
functions.textview_add_widget(self.about_text, 0, button)
uri = 'file://' + os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'doc', 'index.html')
button = gtk.LinkButton(uri, "Quick Start Guide")
#button.connect("clicked", self.quick_start_guide_button_clicked)
functions.textview_add_widget(self.about_text, 0, button)
self.notbook_tabs.append("About: Displays information about MinimPy and the developers")
lbl = gtk.Label('Help')
self.notebook.append_page(hbox, lbl)
self.notebook.set_show_tabs(True)
self.window.add(win_vbox)
self.window.show_all()
if self.trial_file_name:
self.trial_file_name = self.load_trial(self.trial_file_name)
else:
self.set_new_trial()
def quick_start_guide_button_clicked(self, widget, data=None):
pass
def pref_clicked(self, widget, data=None):
PrefWin(self)
def new_trial_clicked(self, widget, data=None):
self.set_new_trial()
def set_new_trial(self):
if not self.query_trial_dismis():
return False
self.trial_file_name = None
self.trial_title_entry.set_text('')
self.trial_sample_size_spin.set_value(30)
functions.set_text_buffer(self.trial_description_text, '')
self.trial_properties_liststore.clear()
self.high_prob_spin.set_value(0.7)
self.prob_method_radios[0].set_active(True)
self.distance_measure_radios[0].set_active(True)
self.group_liststore.clear()
self.variable_liststore.clear()
self.allocations_treeview.set_model()
self.allocations = []
self.freq_table_treeview.set_model()
self.initial_freq_table = False
self.freq_table_treeview.set_model()
self.update_allocations()
self.min_group_combo.set_model()
self.lock_trial(True)
self.ui_pool = None
def export_data(self, button, data=None):
if not self.trial_file_name:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Please save the trial first!")
return False
file_name = self.select_file("Enter name of the file to export", gtk.FILE_CHOOSER_ACTION_SAVE)
if not file_name:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Export canceled!")
return False
p = file_name.rfind('.')
sep_p = file_name.rfind(os.sep)
if p > 0 and p > sep_p: # position 0 is unlikely to be the border of file extension
base_file_name = file_name[:p]
else:
base_file_name = file_name
sps_file_name = base_file_name + ".sps"
if file_name[-4:] != '.dat':
dat_file_name = file_name + '.dat'
else:
dat_file_name = file_name
out_put_data = ""
firstcase = 1
if self.export_items[1]: # Settings
out_put_data += self.get_trial_info()
firstcase += out_put_data.count('\n') + 1
if self.export_items[1]: # Allocations
# [{'allocation': 1, 'levels': [0, 0, 0], 'UI': '24'}, {'allocation': 1, 'levels': [1, 1, 0], 'UI': '21'}]
if self.export_firstrow_var:
firstcase += 1
first_row = ['"seq"', '"ui"', '"group"']
for row in self.variable_liststore:
first_row.append(row[0])
out_put_data += '\n' + self.export_separator.join(first_row)
n = 0
for case in self.allocations:
row = [str(n), case['UI'], str(case['allocation'])] + [str(level) for level in case['levels']]
if self.export_arrange == 'Delimited':
out_put_data += '\n' + self.export_separator.join(row)
elif self.export_arrange == 'Fixed Width':
out_put_data += "\n{0:<8}{1:<8}{2:<2}".format(*row[:3]) + ''.join(['{0:<2}'.format(x) for x in row[3:]])
n += 1
output_data_file = open(dat_file_name, 'w')
output_data_file.write(out_put_data)
output_data_file.flush()
output_data_file.close()
if self.export_items[2]: # SPSS Syntax
if self.export_arrange == 'Delimited':
input_file = os.path.join(os.path.dirname(sys.argv[0]), 'data', 'delimited.dat')
input_data = open(input_file).read()
input_data = input_data.replace('$$filename$$', dat_file_name)
input_data = input_data.replace('$$delimiters$$', self.export_separator)
input_data = input_data.replace('$$firstcase$$', str(firstcase))
for row in self.variable_liststore:
input_data += '{0} F2.0\n'.format(row[0])
input_data += '.'
output_file = open(sps_file_name, 'w')
output_file.write(input_data)
output_file.flush()
output_file.close()
elif self.export_arrange == 'Fixed Width':
input_file = os.path.join(os.path.dirname(sys.argv[0]), 'data', 'fixcase.dat')
input_data = open(input_file).read()
input_data = input_data.replace('$$filename$$', dat_file_name)
input_data = input_data.replace('$$firstcase$$', str(firstcase))
n = 18
for row in self.variable_liststore:
input_data += '{0} {1}-{2} F2.0\n'.format(row[0], n, n+1)
n += 2
input_data += '.'
output_file = open(sps_file_name, 'w')
output_file.write(input_data)
output_file.flush()
output_file.close()
self.export_items
self.export_separator
self.export_firstrow_var
self.export_arrange
def export_varnames_chk(self, chk, data=None):
self.export_firstrow_var = not self.export_firstrow_var
def export_arrangement_toggled(self, radio, data=None):
if radio.get_active():
self.export_arrange = radio.get_label()
def export_separator_toggled(self, radio, custom=None):
if radio.get_active():
if radio.get_label() != 'Custom':
self.export_separator = radio.get_label()[-2]
else:
if len(custom.get_text()):
self.export_separator = custom.get_text()
def export_items_chk (self, chk, export_item):
self.export_items[export_item] = not self.export_items[export_item]
def balance_table_refresh(self):
liststore = self.freq_table_treeview.get_model()
if not liststore:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: No allocation!")
return False
table = []
for row in range(len(liststore)-1):
table.append([])
for col in range(1, len(liststore[0])-1):
table[row].append(liststore[row][col])
balance = self.get_trial_balances(table)
treestore = self.balance_table_treeview.get_model()
treestore.clear()
variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
row = 0
for idx, variable in enumerate(variables):
var_total = [0] * 4
level_rows = []
for level in variable:
for i in range(4):
var_total[i] += balance[row][i]
level_rows.append(balance[row])
row += 1
var_total = [self.variable_liststore[idx][0]] + [var_total[i] / len(variable) for i in range(4)]
variable_node = treestore.append(None, var_total)
level_names = map(str.strip, self.variable_liststore[idx][2].split(','))
for level, level_row in enumerate(level_rows):
treestore.append(variable_node, [level_names[level]] + level_row)
treestore.append(None, ['Mean'] + balance[row])
treestore.append(None, ['Max'] + balance[row+1])
#self.window.show_all()
def get_trial_balances(self, table):
model = Model()
m = Minim(random=self.random, model=model)
levels = [[] for col in table[0]]
balances = [[] for col in table[0]] + [[], []]
for row in table:
for col, value in enumerate(row):
levels[col].append(value)
for row, level_count in enumerate(levels):
allocation_ratio = [self.group_liststore[r][1] for r in range(len(self.group_liststore))]
adj_count = [(1.0 * level_count[i]) / allocation_ratio[i] for i in range(len(level_count))]
balances[row].append(m.get_marginal_balance(adj_count))
balances[row].append(max(adj_count) - min(adj_count))
balances[row].append(m.get_variance(adj_count))
balances[row].append(m.get_standard_deviation(adj_count))
for col in range(4):
balances[len(levels)].append(1.0 * sum([balances[row][col] for row in range(len(levels))]) / len(levels))
balances[len(levels)+1].append(max([balances[row][col] for row in range(len(levels))]))
return balances
def freq_table_disable_edit(self):
cols = self.freq_table_treeview.get_columns()
for n, col in enumerate(cols):
cells = col.get_cell_renderers()
for cell in cells:
cell.set_property('editable', False)
def freq_table_enable_edit(self):
cols = self.freq_table_treeview.get_columns()
for n, col in enumerate(cols):
cells = col.get_cell_renderers()
for cell in cells:
cell.set_property('editable', True)
cell.connect("edited", self.freq_table_treeview_edit_handler, n, len(cols))
def freq_table_start_edit_clicked(self, button, data=False):
initial_freq_model = self.freq_table_treeview.get_model()
if not initial_freq_model:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Trial is not initialized!")
return False
if not len(initial_freq_model):
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Trial is not initialized!")
return False
if button.get_label() == "Start Edit":
self.freq_table_enable_edit()
button.set_label("Save as Initial Table")
else:
if initial_freq_model[-1][-1] == -1:
functions.error_dialog(self.window, 'Error in initial frequency table!\nSome discrepancy between rows and colums sums', 'Frequency table error!')
return False
groups = range(len(self.group_liststore))
variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
table = [[[0 for l in v] for v in variables] for g in groups]
for row, group in enumerate(table):
col = 1
for v, variable in enumerate(group):
for l, level in enumerate(variable):
table[row][v][l] = initial_freq_model[row][col]
col += 1
self.initial_freq_table = table
button.set_label("Start Edit")
def freq_table_treeview_edit_handler(self, cell, row, new_text, col, cols):
model = self.freq_table_treeview.get_model()
if (col == 0) or (int(row) == (len(model) - 1)) or (col == (len(model[0]) - 1)):
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: This cell can not be changed!")
return False
if not new_text.isdigit():
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Only interger values allowed!")
return False
model[row][col] = int(new_text)
col_sum = 0
for r in range(len(model)-1):
col_sum += model[r][col]
model[-1][col] = col_sum
variables = [range(len(self.variable_liststore[r][2].split(','))) for r in range(len(self.variable_liststore) )]
table_row = [[0 for L in v] for v in variables]
cum_cols = 0
for v, variable in enumerate(table_row):
cum_cols += len(variable)
if cum_cols >= col:
sel_cols = range(cum_cols - len(variable) + 1, cum_cols + 1)
break
row_sum = 0
for c in sel_cols:
row_sum += model[row][c]
err_sum = False
cum_cols = 0
for v, variable in enumerate(table_row):
cum_cols += len(variable)
sel_cols = range(cum_cols - len(variable) + 1, cum_cols + 1)
row_sum_ext = 0
for c in sel_cols:
row_sum_ext += model[row][c]
if row_sum_ext != row_sum: err_sum = True
if not err_sum:
model[row][-1] = row_sum
else:
model[row][-1] = -1
total = 0
for r in range(len(model)-1):
total += model[r][-1]
if not err_sum:
model[-1][-1] = total
else:
model[-1][-1] = -1
def freq_table_delete_initial_clicked(self, button, data=None):
initial_freq_model = self.freq_table_treeview.get_model()
if not initial_freq_model:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Trial is not initialized!")
return False
msg = "Are you sure you want to delete the initial table?\nAll entered values into the table will be lost!\nGroup and factor level labels will be refreshed!"
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Initial Table Delete / Refresh!")
if dialog.run() == gtk.RESPONSE_NO:
dialog.destroy()
return False
dialog.destroy()
self.refresh_freq_table()
self.initial_freq_table = False
self.freq_table_disable_edit()
self.freq_table_enable_edit_button.set_label("Start Edit")
def load_trial_clicked(self, button, data=None):
file_name = self.select_file("Select file", gtk.FILE_CHOOSER_ACTION_OPEN)
if not file_name:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Trial load canceled!")
return False
self.trial_file_name = self.load_trial(file_name)
def save_trial_clicked(self, button, data=None):
if not self.trial_is_valid():
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: This trial is not ready to save! Please check the requirements")
return False
file_name = self.select_file("Enter name of the file", gtk.FILE_CHOOSER_ACTION_SAVE)
if not file_name:
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, "Error: Trial save canceled!")
return False
self.build_ui_pool()
self.trial_file_name = self.save_trial(file_name)
def load_trial(self, file_name, lock=True):
trial_state = self.trial_sample_size_spin.get_property("sensitive")
temp_file = self.save_trial()
try:
fp = open(file_name, 'rb')
data = pickle.load(fp)
fp.close()
self.ui_pool = data['ui_pool']
self.trial_title_entry.set_text(data['trial_title'])
self.trial_sample_size_spin.set_value(data['sample_size'])
functions.set_text_buffer(self.trial_description_text, data['trial_description'])
self.set_trial_properties(data['trial_properties'])
self.high_prob_spin.set_value(data['high_prob'])
self.initial_freq_table = data['initial_freq_table']
self.prob_method_radios[data['prob_method']].set_active(True)
self.distance_measure_radios[data['distance_measure']].set_active(True)
self.allocations = data['allocations']
self.set_groups_data(data['groups'])
self.set_variables_data(data['variables'])
self.update_allocations()
if len(self.group_liststore):
self.min_group_combo.set_active(data['min_group'])
if lock:
self.lock_trial()
return file_name
except Exception as ex:
self.lock_trial(trial_state)
self.load_trial(file_name=temp_file, lock=False)
functions.error_dialog(self.window, 'An error occured while loading the trial!\nThe selected file may not be valid\n' + repr(ex) + '\nTrial restored to its previous state', 'Trial load failed!')
return False
def set_groups_data(self, groups):
self.group_liststore.clear()
for group in groups:
self.group_liststore.append((group['name'], group['allocation_ratio']))
def set_variables_data(self, variables):
self.variable_liststore.clear()
for variable in variables:
self.variable_liststore.append((variable['name'], variable['weight'], variable['levels']))
def build_ui_pool(self, lst=None):
if not lst:
ss = self.trial_sample_size_spin.get_value_as_int()
lst = range(ss)
self.random.shuffle(lst)
self.ui_pool = map(lambda x: x.zfill(len(str(max(lst)))), map(str, lst))
def get_trial_properties(self):
table = [['', ''] for row in range(len(self.trial_properties_liststore))]
for row in range(len(table)):
for col in range(len(table[0])):
table[row][col] = self.trial_properties_liststore[row][col]
return table
def set_trial_properties(self, table):
self.trial_properties_liststore.clear()
for row in range(len(table)):
self.trial_properties_liststore.append(table[row])
def trial_is_valid(self, show_msg=True):
if not self.trial_title_entry.get_text():
if show_msg: functions.error_dialog(self.window, "Please specify the trial title first", "Trial title")
self.notebook.set_current_page(0)
self.window.set_focus(self.trial_title_entry)
return False
if not functions.get_text_buffer(self.trial_description_text):
if show_msg: functions.error_dialog(self.window, "Please enter a description for this trial first", "Trial description")
self.notebook.set_current_page(0)
self.window.set_focus(self.trial_description_text)
return False
if len(self.group_liststore) < 2:
if show_msg: functions.error_dialog(self.window, "At least two groups must be defined for the trial", "Trial groups")
self.notebook.set_current_page(1)
self.window.set_focus(self.group_treeview)
return False
if len(self.variable_liststore) < 1:
if show_msg: functions.error_dialog(self.window, "At least one prognostic factor (variable) must be defined for the trial", "Trial variables")
self.notebook.set_current_page(2)
self.window.set_focus(self.variable_treeview)
return False
if not show_msg: return True
msg = "Are you sure you want to save the trial?\nOnce you saved a trial no change in trial settings are allowed!\nTrial details:\n" + self.get_trial_info()
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Trial Save!")
if dialog.run() == gtk.RESPONSE_YES:
dialog.destroy()
return True
dialog.destroy()
return False
def want_trial_unlock(self, prompt=True):
if prompt:
msg = "Do you want to unlock the trial!\nUnlocking the trial make changes of trial setting possible!\nTrial file will be released.\nYou have to save the trial again!"
dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
dialog.set_title("Unlock Trial!")
if dialog.run() == gtk.RESPONSE_NO:
dialog.destroy()
return False
dialog.destroy()
self.lock_trial(True)
self.ui_pool = None
self.trial_file_name = None
def lock_trial(self, unlock=False):
self.trial_save_button.set_property("sensitive", unlock)
self.trial_sample_size_spin.set_property("sensitive", unlock)
for radio in self.prob_method_radios:
radio.set_property("sensitive", unlock)
self.min_group_combo.set_property("sensitive", unlock)
self.high_prob_spin.set_property("sensitive", unlock)
for radio in self.distance_measure_radios:
radio.set_property("sensitive", unlock)
for child in self.group_hbox.get_children():
child.set_property("sensitive", unlock)
for child in self.variable_hbox.get_children():
child.set_property("sensitive", unlock)
for child in self.initial_table_hbox.get_children():
child.set_property("sensitive", unlock)
self.statusbar.pop(self.sb_context_id)
self.statusbar.push(self.sb_context_id, ("Trial lock!", "Trial unlocked!")[unlock])
def get_trial_info(self):
ret = []
ret.append("Title: " + self.trial_title_entry.get_text())
ret.append("Sample Size: " + str(self.trial_sample_size_spin.get_value_as_int()))
ret.append("Description: " + functions.get_text_buffer(self.trial_description_text))
ret.append("Probability Method: " + self.get_prob_method_name())
ret.append("Distance Measure: " + self.get_distance_measure_name())
ret.append("High Probability: " + str(self.high_prob_spin.get_value()))
ret.append("Groups:\n" + str(self.get_groups_info()))
ret.append("Variables:\n" + str(self.get_variables_info()))
return '\n'.join(ret)
def save_trial(self, file_name=None):
if file_name:
# do not lock if this is a temporary save
self.lock_trial()
else:
file_name = tempfile.mkstemp()[1]
try:
data = {}
data['ui_pool'] = self.ui_pool
data['trial_title'] = self.trial_title_entry.get_text()
data['sample_size'] = self.trial_sample_size_spin.get_value_as_int()
data['trial_description'] = functions.get_text_buffer(self.trial_description_text)
data['trial_properties'] = self.get_trial_properties()
data['high_prob'] = self.high_prob_spin.get_value()
data['min_group'] = self.min_group_combo.get_active()
data['initial_freq_table'] = self.initial_freq_table
data['prob_method'] = self.get_prob_method()
data['distance_measure'] = self.get_distance_measure()
data['allocations'] = self.allocations
data['groups'] = self.get_groups_data()
data['variables'] = self.get_variables_data()
fp = open(file_name, 'wb')
pickle.dump(data, fp)
fp.close()
self.config.set('project', 'recent_trial', file_name)
with open(self.config_file, 'wb') as config_file:
self.config.write(config_file)
# useful if temp save
return file_name
except Exception as ex:
functions.error_dialog(self.window, "An unknown error occured during trial save!\n" + repr(ex), "Trial save error!")
return False
def get_distance_measure_name(self):
for distance_measure_radio in self.distance_measure_radios:
if distance_measure_radio.get_active():
return distance_measure_radio.get_label()
return "Unknown"
def get_distance_measure(self):
for idx, distance_measure_radio in enumerate(self.distance_measure_radios):
if distance_measure_radio.get_active():
return idx
return 0
def get_prob_method_name(self):
for prob_method_radio in self.prob_method_radios:
if prob_method_radio.get_active():
return prob_method_radio.get_label()
return "Unknown"
def get_prob_method(self):
for idx, prob_method_radio in enumerate(self.prob_method_radios):
if prob_method_radio.get_active():
return idx
return 0
def get_variables_info(self):
variables = []
n = 0
for row in self.variable_liststore:
n += 1
variable = "{0}) {1}, weight = {2}, levels = {3}".format(n, *row)
variables.append(variable)
return '\n'.join(variables)
def get_variables_data(self):
variables = []
for row in self.variable_liststore:
variable = {}
variable['name'] = row[0]
variable['weight'] = row[1]
variable['levels'] = row[2]
variables.append(variable)
return variables
def get_groups_info(self):
groups = []
n = 0
for row in self.group_liststore:
n += 1
group = "{0}) {1}, Allocation Ratio = {2}".format(n, *list(row))
groups.append(group)
return '\n'.join(groups)
def get_groups_data(self):
groups = []
for row in self.group_liststore:
group = {}
group['name'] = row[0]
group['allocation_ratio'] = row[1]
groups.append(group)
return groups
def select_file(self, title, action):
"""
A generic method returning file name
"""
stock = {gtk.FILE_CHOOSER_ACTION_OPEN: gtk.STOCK_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE: gtk.STOCK_SAVE}
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, stock[action], gtk.RESPONSE_OK)
fdialog = gtk.FileChooserDialog(title, self.window, action, buttons)
if action == gtk.FILE_CHOOSER_ACTION_SAVE:
fdialog.set_do_overwrite_confirmation(True)
fdialog.set_default_response(gtk.RESPONSE_OK)
response = fdialog.run()
if response == gtk.RESPONSE_OK:
file_name = fdialog.get_filename()
else:
file_name = None
fdialog.destroy()
return file_name
def main():
gtk.main()
return 0
if __name__ == "__main__":
ModelInteface()
main()