class DatabaseEditorFrame(wx.Frame): """ Various viewing and editing tools for the SQL database. This object is called on Database toolbar click. """ def __init__(self, roi_map, options): """ :param roi_map: roi_map object :type roi_map: DatabaseROIs """ wx.Frame.__init__(self, None, title='Database Administrator') set_frame_icon(self) set_msw_background_color( self) # If windows, change the background color self.roi_map = roi_map self.options = options self.db_tree = self.get_db_tree() self.SetSize(get_window_size(0.792, 0.781)) self.window_db_editor = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D) self.window_pane_db_tree = wx.ScrolledWindow(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.tree_ctrl_db = wx.TreeCtrl(self.window_pane_db_tree, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_MULTIPLE) self.window_pane_query = wx.Panel(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.text_ctrl_condition = wx.TextCtrl(self.window_pane_query, wx.ID_ANY, "") self.list_ctrl_query_results = wx.ListCtrl( self.window_pane_query, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.data_query_results = DataTable( self.list_ctrl_query_results, columns=['mrn', 'study_instance_uid']) self.combo_box_query_table = wx.ComboBox(self.window_pane_query, wx.ID_ANY, choices=list(self.db_tree), style=wx.CB_DROPDOWN | wx.CB_READONLY) self.button = { 'delete_all_data': wx.Button(self, wx.ID_ANY, "Delete All Data"), 'rebuild_db': wx.Button(self, wx.ID_ANY, "Rebuild Database"), 'calculations': wx.Button(self, wx.ID_ANY, "Calculations"), 'edit_db': wx.Button(self, wx.ID_ANY, "Edit Database"), 'reimport': wx.Button(self, wx.ID_ANY, "Reimport from DICOM"), 'delete_study': wx.Button(self, wx.ID_ANY, "Delete Study"), 'change_mrn_uid': wx.Button(self, wx.ID_ANY, "Change MRN/UID"), 'query': wx.Button(self.window_pane_query, wx.ID_ANY, "Query"), 'clear': wx.Button(self.window_pane_query, wx.ID_ANY, "Clear"), 'export_csv': wx.Button(self.window_pane_query, wx.ID_ANY, "Export"), 'remap_roi_names': wx.Button(self, wx.ID_ANY, "Remap ROI Names"), 'update_from_csv': wx.Button(self, wx.ID_ANY, "Update from CSV"), # 'plan_complexity': wx.Button(self, wx.ID_ANY, "Recalc Plan Complexities"), 'auto_fit_columns': wx.Button(self.window_pane_query, wx.ID_ANY, "Auto-fit Columns") } self.checkbox_auto_backup = wx.CheckBox( self, wx.ID_ANY, "Auto Backup SQLite DB After Import") self.__set_properties() self.__do_layout() self.__do_bind() self.selected_columns = { table: {c: False for c in list(self.db_tree[table])} for table in list(self.db_tree) } self.selected_tables = {table: False for table in list(self.db_tree)} self.window_db_editor.SetSashPosition(250) self.tree_ctrl_db.Expand(self.db_tree_root) self.allow_tree_select_change = True self.Show() def __set_properties(self): self.SetTitle("Database Administrator") self.window_pane_db_tree.SetScrollRate(10, 10) self.window_db_editor.SetMinimumPaneSize(20) self.db_tree_root = self.tree_ctrl_db.AddRoot('DVH Analytics') self.table_nodes = { table: self.tree_ctrl_db.AppendItem(self.db_tree_root, table) for table in self.db_tree } self.column_nodes = {table: {} for table in self.db_tree} for table in self.db_tree: for column in self.db_tree[table]: self.column_nodes[table][ column] = self.tree_ctrl_db.AppendItem( self.table_nodes[table], column) self.combo_box_query_table.SetValue('Plans') self.checkbox_auto_backup.SetFont( wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, "")) self.checkbox_auto_backup.SetValue(self.options.AUTO_SQL_DB_BACKUP) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_query = wx.BoxSizer(wx.VERTICAL) sizer_query_table = wx.StaticBoxSizer( wx.StaticBox(self.window_pane_query, wx.ID_ANY, "Query Results"), wx.VERTICAL) sizer_condition_buttons = wx.BoxSizer(wx.HORIZONTAL) sizer_download_button = wx.BoxSizer(wx.VERTICAL) sizer_clear_button = wx.BoxSizer(wx.VERTICAL) sizer_query_button = wx.BoxSizer(wx.VERTICAL) sizer_auto_fit_columns_button = wx.BoxSizer(wx.VERTICAL) sizer_condition = wx.BoxSizer(wx.VERTICAL) sizer_combo_box = wx.BoxSizer(wx.VERTICAL) sizer_db_tree = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons = wx.BoxSizer(wx.VERTICAL) sizer_dialog_buttons_1 = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons_1.Add(self.button['calculations'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['edit_db'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['reimport'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['delete_study'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['change_mrn_uid'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['rebuild_db'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['delete_all_data'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['remap_roi_names'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['update_from_csv'], 0, wx.ALL, 5) # sizer_dialog_buttons.Add(self.button['plan_complexity'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(sizer_dialog_buttons_1, 0, 0, 0) sizer_dialog_buttons.Add(self.checkbox_auto_backup, 0, wx.LEFT, 5) sizer_wrapper.Add(sizer_dialog_buttons, 0, wx.ALL, 5) sizer_db_tree.Add(self.tree_ctrl_db, 1, wx.EXPAND, 0) self.window_pane_db_tree.SetSizer(sizer_db_tree) label_table = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Table:") sizer_combo_box.Add(label_table, 0, wx.BOTTOM | wx.TOP, 5) sizer_combo_box.Add(self.combo_box_query_table, 0, 0, 0) sizer_condition_buttons.Add(sizer_combo_box, 0, wx.ALL | wx.EXPAND, 5) label_condition = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Condition:") sizer_condition.Add(label_condition, 0, wx.BOTTOM | wx.TOP, 5) sizer_condition.Add(self.text_ctrl_condition, 0, wx.ALL | wx.EXPAND, 0) sizer_condition_buttons.Add(sizer_condition, 1, wx.ALL | wx.EXPAND, 5) label_spacer_1 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_query_button.Add(label_spacer_1, 0, wx.BOTTOM, 5) sizer_query_button.Add(self.button['query'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_query_button, 0, wx.ALL, 5) label_spacer_2 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_download_button.Add(label_spacer_2, 0, wx.BOTTOM, 5) sizer_download_button.Add(self.button['export_csv'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_download_button, 0, wx.ALL, 5) label_spacer_3 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_clear_button.Add(label_spacer_3, 0, wx.BOTTOM, 5) sizer_clear_button.Add(self.button['clear'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_clear_button, 0, wx.ALL, 5) label_spacer_4 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_auto_fit_columns_button.Add(label_spacer_4, 0, wx.BOTTOM, 5) sizer_auto_fit_columns_button.Add(self.button['auto_fit_columns'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_auto_fit_columns_button, 0, wx.ALL, 5) sizer_query.Add(sizer_condition_buttons, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT, 5) sizer_query_table.Add(self.list_ctrl_query_results, 1, wx.EXPAND, 0) sizer_query.Add(sizer_query_table, 1, wx.ALL | wx.EXPAND, 5) self.window_pane_query.SetSizer(sizer_query) self.window_db_editor.SplitVertically(self.window_pane_db_tree, self.window_pane_query) sizer_wrapper.Add(self.window_db_editor, 1, wx.EXPAND, 0) self.SetSizer(sizer_wrapper) self.Layout() self.Center() def __do_bind(self): # self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeAdd, self.tree_ctrl_db, id=self.tree_ctrl_db.GetId()) # All buttons are bound to a function based on their key prepended with 'on_' # For example, query button calls on_query when clicked for key, button in self.button.items(): self.Bind(wx.EVT_BUTTON, getattr(self, 'on_' + key), id=button.GetId()) self.Bind(wx.EVT_CHECKBOX, self.on_auto_backup, id=self.checkbox_auto_backup.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_query_results, self.list_ctrl_query_results) def on_auto_fit_columns(self, *evt): self.data_query_results.set_column_widths(auto=True) def on_tree_add(self, evt): self.update_selected_tree_items() # def UnselectOtherTables(self, selected_table): # self.allow_tree_select_change = False # for table, node in self.table_nodes.items(): # if table != selected_table: # self.tree_ctrl_db.UnselectItem(node) # self.allow_tree_select_change = True def on_query(self, evt): self.update_selected_tree_items() table = self.combo_box_query_table.GetValue() columns = [ c for c, sel in self.selected_columns[table].items() if sel and c not in {'mrn', 'study_instance_uid'} ] columns.sort() if not columns: columns = [ c for c in self.db_tree[table] if c not in {'mrn', 'study_instance_uid'} ] columns.insert(0, 'study_instance_uid') columns.insert(0, 'mrn') condition = self.text_ctrl_condition.GetValue() with DVH_SQL() as cnx: try: data = cnx.query(table, ','.join(columns), condition, bokeh_cds=True) self.data_query_results.set_data(data, columns) except SQLError as e: SQLErrorDialog(self, e) self.data_query_results.clear() def on_clear(self, evt): self.data_query_results.clear() @staticmethod def get_db_tree(): tree = get_database_tree() for table in list(tree): tree[table] = [ column for column in tree[table] if 'string' not in column ] return tree def update_selected_tree_items(self): for table, table_item in self.table_nodes.items(): self.selected_tables[table] = self.tree_ctrl_db.IsSelected( table_item) for column, column_item in self.column_nodes[table].items(): self.selected_columns[table][ column] = self.tree_ctrl_db.IsSelected(column_item) def on_change_mrn_uid(self, evt): self.change_or_delete_dlg(ChangePatientIdentifierDialog) def on_delete_study(self, evt): self.change_or_delete_dlg(DeletePatientDialog) def change_or_delete_dlg(self, class_type): selected_data = self.data_query_results.selected_row_data if selected_data: class_type(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: class_type() def on_reimport(self, evt): selected_data = self.data_query_results.selected_row_data if selected_data: ReimportDialog(self.roi_map, self.options, mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: ReimportDialog(self.roi_map, self.options) @staticmethod def on_edit_db(evt): EditDatabaseDialog() @staticmethod def on_calculations(evt): CalculationsDialog() def on_rebuild_db(self, evt): RebuildDB(self, self.roi_map, self.options) def on_delete_all_data(self, evt): DeleteAllData(self, self.options) def on_export_csv(self, evt): save_data_to_file(self, "Export Data Table to CSV", self.data_query_results.get_csv()) def on_remap_roi_names(self, evt): RemapROIFrame(self.roi_map, remap_all=True) def sort_query_results(self, evt): self.data_query_results.sort_table(evt) def on_auto_backup(self, *evt): self.options.AUTO_SQL_DB_BACKUP = self.checkbox_auto_backup.GetValue() # @staticmethod # def on_plan_complexity(*evt): # with wx.BusyCursor() as busy: # recalculate_plan_complexities_from_beams() def on_update_from_csv(self, *evt): msg = "Load a CSV file and update your database. Please see manual for " \ "more information. Formatting is critical, this can not be undone!" caption = "USE WITH EXTREME CAUTION" ErrorDialog(self, msg, caption, flags=wx.ICON_WARNING | wx.OK | wx.OK_DEFAULT) with wx.FileDialog(self, "Load CSV into Database", style=wx.FD_FILE_MUST_EXIST | wx.FD_OPEN) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return pathname = fileDialog.GetPath() with open(pathname, 'r') as fp: lines = fp.readlines() header_row = "table,column,value,condition" if lines and header_row in lines[0].strip(): params = [csv_to_list(line.strip()) for line in lines[1:]] ProgressFrame(params, update_db_with_csv, title="Update Database from CSV file", action_msg="Processing row", star_map=True) else: msg = "The selected file was not recognized. " \ "Be sure you have a header row of: %s" % header_row caption = "CSV load failure" ErrorDialog(self, msg, caption, flags=wx.ICON_WARNING | wx.OK | wx.OK_DEFAULT)
class DatabaseEditorFrame(wx.Frame): """ Various viewing and editing tools for the SQL database. This object is called on Database toolbar click. """ def __init__(self, roi_map): """ :param roi_map: roi_map object :type roi_map: DatabaseROIs """ wx.Frame.__init__(self, None, title='Database Administrator') set_msw_background_color(self) # If windows, change the background color self.roi_map = roi_map self.db_tree = self.get_db_tree() self.SetSize(get_window_size(0.792, 0.781)) self.window_db_editor = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D) self.window_pane_db_tree = wx.ScrolledWindow(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.tree_ctrl_db = wx.TreeCtrl(self.window_pane_db_tree, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_MULTIPLE) self.window_pane_query = wx.Panel(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.text_ctrl_condition = wx.TextCtrl(self.window_pane_query, wx.ID_ANY, "") self.list_ctrl_query_results = wx.ListCtrl(self.window_pane_query, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.data_query_results = DataTable(self.list_ctrl_query_results, columns=['mrn', 'study_instance_uid']) self.combo_box_query_table = wx.ComboBox(self.window_pane_query, wx.ID_ANY, choices=list(self.db_tree), style=wx.CB_DROPDOWN | wx.CB_READONLY) self.button = {'delete_all_data': wx.Button(self, wx.ID_ANY, "Delete All Data"), 'rebuild_db': wx.Button(self, wx.ID_ANY, "Rebuild Database"), # 'calculations': wx.Button(self, wx.ID_ANY, "Calculations"), 'edit_db': wx.Button(self, wx.ID_ANY, "Edit Database"), 'reimport': wx.Button(self, wx.ID_ANY, "Reimport from DICOM"), 'delete_study': wx.Button(self, wx.ID_ANY, "Delete Study"), 'change_mrn_uid': wx.Button(self, wx.ID_ANY, "Change MRN/UID"), 'query': wx.Button(self.window_pane_query, wx.ID_ANY, "Query"), 'clear': wx.Button(self.window_pane_query, wx.ID_ANY, "Clear"), 'export_csv': wx.Button(self.window_pane_query, wx.ID_ANY, "Export"), 'remap_roi_names': wx.Button(self, wx.ID_ANY, "Remap ROI Names")} self.__set_properties() self.__do_layout() self.__do_bind() self.selected_columns = {table: {c: False for c in list(self.db_tree[table])} for table in list(self.db_tree)} self.selected_tables = {table: False for table in list(self.db_tree)} self.window_db_editor.SetSashPosition(250) self.tree_ctrl_db.Expand(self.db_tree_root) self.allow_tree_select_change = True self.Show() def __set_properties(self): self.SetTitle("Database Administrator") self.window_pane_db_tree.SetScrollRate(10, 10) self.window_db_editor.SetMinimumPaneSize(20) self.db_tree_root = self.tree_ctrl_db.AddRoot('DVH Analytics') self.table_nodes = {table: self.tree_ctrl_db.AppendItem(self.db_tree_root, table) for table in self.db_tree} self.column_nodes = {table: {} for table in self.db_tree} for table in self.db_tree: for column in self.db_tree[table]: self.column_nodes[table][column] = self.tree_ctrl_db.AppendItem(self.table_nodes[table], column) self.combo_box_query_table.SetValue('Plans') def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_query = wx.BoxSizer(wx.VERTICAL) sizer_query_table = wx.StaticBoxSizer(wx.StaticBox(self.window_pane_query, wx.ID_ANY, "Query Results"), wx.VERTICAL) sizer_condition_buttons = wx.BoxSizer(wx.HORIZONTAL) sizer_download_button = wx.BoxSizer(wx.VERTICAL) sizer_clear_button = wx.BoxSizer(wx.VERTICAL) sizer_query_button = wx.BoxSizer(wx.VERTICAL) sizer_condition = wx.BoxSizer(wx.VERTICAL) sizer_combo_box = wx.BoxSizer(wx.VERTICAL) sizer_db_tree = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons = wx.BoxSizer(wx.HORIZONTAL) # sizer_dialog_buttons.Add(self.button['calculations'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['edit_db'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['reimport'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['delete_study'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['change_mrn_uid'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['rebuild_db'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['delete_all_data'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['remap_roi_names'], 0, wx.ALL, 5) sizer_wrapper.Add(sizer_dialog_buttons, 0, wx.ALL, 5) sizer_db_tree.Add(self.tree_ctrl_db, 1, wx.EXPAND, 0) self.window_pane_db_tree.SetSizer(sizer_db_tree) label_table = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Table:") sizer_combo_box.Add(label_table, 0, wx.BOTTOM | wx.TOP, 5) sizer_combo_box.Add(self.combo_box_query_table, 0, 0, 0) sizer_condition_buttons.Add(sizer_combo_box, 0, wx.ALL | wx.EXPAND, 5) label_condition = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Condition:") sizer_condition.Add(label_condition, 0, wx.BOTTOM | wx.TOP, 5) sizer_condition.Add(self.text_ctrl_condition, 0, wx.ALL | wx.EXPAND, 0) sizer_condition_buttons.Add(sizer_condition, 1, wx.ALL | wx.EXPAND, 5) label_spacer_1 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_query_button.Add(label_spacer_1, 0, wx.BOTTOM, 5) sizer_query_button.Add(self.button['query'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_query_button, 0, wx.ALL, 5) label_spacer_2 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_download_button.Add(label_spacer_2, 0, wx.BOTTOM, 5) sizer_download_button.Add(self.button['export_csv'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_download_button, 0, wx.ALL, 5) label_spacer_3 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_clear_button.Add(label_spacer_3, 0, wx.BOTTOM, 5) sizer_clear_button.Add(self.button['clear'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_clear_button, 0, wx.ALL, 5) sizer_query.Add(sizer_condition_buttons, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT, 5) sizer_query_table.Add(self.list_ctrl_query_results, 1, wx.EXPAND, 0) sizer_query.Add(sizer_query_table, 1, wx.ALL | wx.EXPAND, 5) self.window_pane_query.SetSizer(sizer_query) self.window_db_editor.SplitVertically(self.window_pane_db_tree, self.window_pane_query) sizer_wrapper.Add(self.window_db_editor, 1, wx.EXPAND, 0) self.SetSizer(sizer_wrapper) self.Layout() self.Center() def __do_bind(self): # self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeAdd, self.tree_ctrl_db, id=self.tree_ctrl_db.GetId()) # All buttons are bound to a function based on their key prepended with 'on_' # For example, query button calls on_query when clicked for key, button in self.button.items(): self.Bind(wx.EVT_BUTTON, getattr(self, 'on_' + key), id=button.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_query_results, self.list_ctrl_query_results) def on_tree_add(self, evt): self.update_selected_tree_items() # def UnselectOtherTables(self, selected_table): # self.allow_tree_select_change = False # for table, node in self.table_nodes.items(): # if table != selected_table: # self.tree_ctrl_db.UnselectItem(node) # self.allow_tree_select_change = True def on_query(self, evt): self.update_selected_tree_items() table = self.combo_box_query_table.GetValue() columns = [c for c, sel in self.selected_columns[table].items() if sel and c not in {'mrn', 'study_instance_uid'}] columns.sort() if not columns: columns = [c for c in self.db_tree[table] if c not in {'mrn', 'study_instance_uid'}] columns.insert(0, 'study_instance_uid') columns.insert(0, 'mrn') condition = self.text_ctrl_condition.GetValue() wait = wx.BusyInfo("Querying data\nPlease wait...") with DVH_SQL() as cnx: try: data = cnx.query(table, ','.join(columns), condition, bokeh_cds=True) self.data_query_results.set_data(data, columns) except SQLError as e: SQLErrorDialog(self, e) self.data_query_results.clear() del wait def on_clear(self, evt): self.data_query_results.clear() @staticmethod def get_db_tree(): tree = get_database_tree() for table in list(tree): tree[table] = [column for column in tree[table] if 'string' not in column] return tree def update_selected_tree_items(self): for table, table_item in self.table_nodes.items(): self.selected_tables[table] = self.tree_ctrl_db.IsSelected(table_item) for column, column_item in self.column_nodes[table].items(): self.selected_columns[table][column] = self.tree_ctrl_db.IsSelected(column_item) def on_change_mrn_uid(self, evt): self.change_or_delete_dlg(ChangePatientIdentifierDialog) def on_delete_study(self, evt): # TODO: Needs an Are you sure? dialog self.change_or_delete_dlg(DeletePatientDialog) def change_or_delete_dlg(self, class_type): selected_data = self.data_query_results.selected_row_data if selected_data: class_type(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: class_type() def on_reimport(self, evt): selected_data = self.data_query_results.selected_row_data if selected_data: ReimportDialog(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: ReimportDialog() @staticmethod def on_edit_db(evt): EditDatabaseDialog() @staticmethod def on_calculations(evt): CalculationsDialog() def on_rebuild_db(self, evt): RebuildDB(self) def on_delete_all_data(self, evt): DeleteAllData(self) def on_export_csv(self, evt): save_data_to_file(self, "Export Data Table to CSV", self.data_query_results.get_csv()) def on_remap_roi_names(self, evt): RemapROIFrame(self.roi_map, remap_all=True) def sort_query_results(self, evt): self.data_query_results.sort_table(evt)
class QueriedDataFrame(wx.Frame): """ Generate a simple table to view data of the current query for a specified SQL table """ def __init__(self, data_obj, columns, data_key, menu, menu_item_id): """ :param data_obj: object containing data to be viewed for each group :type data_obj: dict :param columns: columns to be displayed in table :type columns: list :param data_key: either 'DVHs', 'Plans', 'Beams', 'Rxs', or 'StatsData' :type data_key: str :param menu: a link to the main app menu, used to toggle Show/Hide status :type menu: Menu :param menu_item_id: the ID of the menu item associated with the specified data_obj """ wx.Frame.__init__(self, None, title='%s Data' % data_key[0:-1]) self.data = data_obj self.columns = columns self.sql_table = data_key self.menu = menu self.menu_item_id = menu_item_id self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.button_export = wx.Button(self, wx.ID_ANY, "Export to CSV") self.radio_button_query_group = wx.RadioBox(self, wx.ID_ANY, 'Query Group', choices=['1', '2']) self.data_table = DataTable(self.list_ctrl) self.data_table.set_data(self.table_data, self.columns) if not self.data[2]: self.radio_button_query_group.Disable() self.__do_bind() self.__set_properties() self.__do_layout() self.run() def __set_properties(self): self.SetSize(get_window_size(0.714, 0.762)) set_msw_background_color(self) set_frame_icon(self) def __do_bind(self): self.Bind(wx.EVT_BUTTON, self.on_export, id=self.button_export.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_table, self.list_ctrl) self.Bind(wx.EVT_CLOSE, self.on_close) self.Bind(wx.EVT_RADIOBOX, self.on_group_select, id=self.radio_button_query_group.GetId()) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_widgets = wx.BoxSizer(wx.HORIZONTAL) sizer_button = wx.BoxSizer(wx.HORIZONTAL) sizer_button.Add(self.button_export, 0, wx.TOP, 15) sizer_widgets.Add(sizer_button, 0, wx.ALL, 10) sizer_widgets.Add(self.radio_button_query_group, 0, wx.ALL, 10) sizer_wrapper.Add(sizer_widgets, 0, 0, 0) sizer_wrapper.Add(self.list_ctrl, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 10) self.SetSizer(sizer_wrapper) self.Center() @property def table_data(self): return { column: getattr(self.selected_data, column) for column in self.columns } @property def selected_group(self): return self.radio_button_query_group.GetSelection() + 1 @property def selected_data(self): return self.data[self.selected_group] def run(self): self.toggle_data_menu_item() self.Show() def on_close(self, *args): self.toggle_data_menu_item() self.Destroy() def on_export(self, *args): save_data_to_file( self, "Export Group %s %s to CSV" % (self.selected_group, self.sql_table), self.data_table.get_csv()) def toggle_data_menu_item(self): short_cut = ['DVHs', 'Plans', 'Rxs', 'Beams'].index(self.sql_table) + 1 show_hide = ['Show', 'Hide']['Show' in self.menu.GetLabel(self.menu_item_id)] self.menu.SetLabel( self.menu_item_id, '%s %s\tCtrl+%s' % (show_hide, self.sql_table, short_cut)) def sort_table(self, evt): self.data_table.sort_table(evt) def on_group_select(self, *evt): self.data_table.clear() self.data_table.set_data(self.table_data, self.columns)
class RadBioFrame: """ Object to be passed into notebook panel for the Rad Bio tab """ def __init__(self, parent, dvh, time_series, regression, control_chart): """ :param parent: notebook panel in main view :type parent: Panel :param dvh: dvh data object :type dvh: DVH :param time_series: Time Series object in notebook :type time_series: TimeSeriesFrame :param regression: Regression frame object in notebook :type regression: RegressionFrame :param control_chart: Control Chart frame object in notebook :type control_chart: ControlChartFrame """ self.parent = parent self.dvh = dvh self.time_series = time_series self.regression = regression self.control_chart = control_chart self.table_published_values = wx.ListCtrl(self.parent, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.text_input_eud_a = wx.TextCtrl(self.parent, wx.ID_ANY, "") self.text_input_gamma_50 = wx.TextCtrl(self.parent, wx.ID_ANY, "") self.text_input_td_50 = wx.TextCtrl(self.parent, wx.ID_ANY, "") self.button_apply_parameters = wx.Button(self.parent, wx.ID_ANY, "Apply Parameters") self.button_export = wx.Button(self.parent, wx.ID_ANY, "Export") self.table_rad_bio = wx.ListCtrl(self.parent, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.columns = ['MRN', 'ROI Name', 'a', u'\u03b3_50', 'TD or TCD', 'EUD', 'NTCP or TCP', 'PTV Overlap', 'ROI Type', 'Rx Dose', 'Total Fxs', 'Fx Dose'] self.width = [100, 175, 50, 50, 80, 80, 80, 100, 100, 100, 100, 100] formats = [wx.LIST_FORMAT_RIGHT] * len(self.columns) formats[0] = wx.LIST_FORMAT_LEFT formats[1] = wx.LIST_FORMAT_LEFT self.data_table_rad_bio = DataTable(self.table_rad_bio, columns=self.columns, widths=self.width, formats=formats) self.__set_properties() self.__do_layout() parent.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_parameter_select, self.table_published_values) parent.Bind(wx.EVT_BUTTON, self.apply_parameters, id=self.button_apply_parameters.GetId()) parent.Bind(wx.EVT_BUTTON, self.on_export_csv, id=self.button_export.GetId()) self.disable_buttons() def __set_properties(self): self.table_published_values.AppendColumn("Structure", format=wx.LIST_FORMAT_LEFT, width=150) self.table_published_values.AppendColumn("Endpoint", format=wx.LIST_FORMAT_LEFT, width=300) self.table_published_values.AppendColumn("a", format=wx.LIST_FORMAT_LEFT, width=-1) self.table_published_values.AppendColumn(u"\u03b3_50", format=wx.LIST_FORMAT_LEFT, width=-1) self.table_published_values.AppendColumn("TD_50", format=wx.LIST_FORMAT_LEFT, width=-1) for i, col in enumerate(self.columns): self.table_rad_bio.AppendColumn(col, width=self.width[i]) self.published_data = [['Brain', 'Necrosis', 5, 3, 60], ['Brainstem', 'Necrosis', 7, 3, 65], ['Optic Chasm', 'Blindness', 25, 3, 65], ['Colon', 'Obstruction/Perforation', 6, 4, 55], ['Ear (mid/ext)', 'Acute serous otitus', 31, 3, 40], ['Ear (mid/ext)', 'Chronic serous otitus', 31, 4, 65], ['Esophagus', 'Perforation', 19, 4, 68], ['Heart', 'Pericarditus', 3, 3, 50], ['Kidney', 'Nephritis', 1, 3, 28], ['Lens', 'Cataract', 3, 1, 18], ['Liver', 'Liver Failure', 3, 3, 40], ['Lung', 'Pneumontis', 1, 2, 24.5], ['Optic Nerve', 'Blindness', 25, 3, 65], ['Retina', 'Blindness', 15, 2, 65]] for row in self.published_data: index = self.table_published_values.InsertItem(50000, str(row[0])) for i in [1, 2, 3, 4]: self.table_published_values.SetItem(index, i, str(row[i])) def __do_layout(self): sizer_main = wx.BoxSizer(wx.VERTICAL) sizer_parameters = wx.BoxSizer(wx.VERTICAL) sizer_parameters_input = wx.StaticBoxSizer(wx.StaticBox(self.parent, wx.ID_ANY, ""), wx.HORIZONTAL) sizer_button = wx.BoxSizer(wx.VERTICAL) sizer_button_2 = wx.BoxSizer(wx.VERTICAL) sizer_td_50 = wx.BoxSizer(wx.VERTICAL) sizer_gamma_50 = wx.BoxSizer(wx.VERTICAL) sizer_eud = wx.BoxSizer(wx.VERTICAL) sizer_published_values = wx.BoxSizer(wx.VERTICAL) label_published_values = wx.StaticText(self.parent, wx.ID_ANY, "Published EUD Parameters from Emami et. al. for 1.8-2.0Gy " "fractions (Click to apply)") label_published_values.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) sizer_published_values.Add(label_published_values, 0, wx.ALL, 5) sizer_published_values.Add(self.table_published_values, 1, wx.ALL, 10) sizer_published_values.SetMinSize(get_window_size(0.298, 0.319)) sizer_main.Add(sizer_published_values, 1, wx.ALL | wx.EXPAND, 10) label_parameters = wx.StaticText(self.parent, wx.ID_ANY, "Parameters:") label_parameters.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) sizer_parameters.Add(label_parameters, 0, 0, 0) label_eud = wx.StaticText(self.parent, wx.ID_ANY, "EUD a-value:") sizer_eud.Add(label_eud, 0, 0, 0) sizer_eud.Add(self.text_input_eud_a, 0, wx.ALL | wx.EXPAND, 5) sizer_parameters_input.Add(sizer_eud, 1, wx.EXPAND, 0) label_gamma_50 = wx.StaticText(self.parent, wx.ID_ANY, u"\u03b3_50:") sizer_gamma_50.Add(label_gamma_50, 0, 0, 0) sizer_gamma_50.Add(self.text_input_gamma_50, 0, wx.ALL | wx.EXPAND, 5) sizer_parameters_input.Add(sizer_gamma_50, 1, wx.EXPAND, 0) label_td_50 = wx.StaticText(self.parent, wx.ID_ANY, "TD_50 or TCD_50:") sizer_td_50.Add(label_td_50, 0, 0, 0) sizer_td_50.Add(self.text_input_td_50, 0, wx.ALL | wx.EXPAND, 5) sizer_parameters_input.Add(sizer_td_50, 1, wx.EXPAND, 0) sizer_button.Add(self.button_apply_parameters, 1, wx.ALL | wx.EXPAND, 15) sizer_button_2.Add(self.button_export, 1, wx.ALL | wx.EXPAND, 15) sizer_parameters_input.Add(sizer_button, 1, wx.EXPAND, 0) sizer_parameters_input.Add(sizer_button_2, 1, wx.EXPAND, 0) sizer_parameters.Add(sizer_parameters_input, 1, wx.ALL | wx.EXPAND, 5) sizer_main.Add(sizer_parameters, 0, wx.ALL | wx.EXPAND, 10) sizer_main.Add(self.table_rad_bio, 1, wx.ALL | wx.EXPAND, 10) self.layout = sizer_main def __set_tooltips(self): self.button_apply_parameters.SetToolTip("Shift or Ctrl click for targeted application.") def enable_buttons(self): self.button_apply_parameters.Enable() def disable_buttons(self): self.button_apply_parameters.Disable() def enable_initial_buttons(self): self.button_apply_parameters.Enable() def on_parameter_select(self, evt): index = self.table_published_values.GetFirstSelected() self.text_input_eud_a.SetValue(str(self.published_data[index][2])) self.text_input_gamma_50.SetValue(str(self.published_data[index][3])) self.text_input_td_50.SetValue(str(self.published_data[index][4])) def update_dvh_data(self, dvh): """ Import dvh data, store into self.dvh and set data in data_table :param dvh: dvh object from main frame :type dvh: DVH """ self.dvh = dvh data = {'MRN': self.dvh.mrn, 'ROI Name': self.dvh.roi_name, 'a': [''] * self.dvh.count, u'\u03b3_50': [''] * self.dvh.count, 'TD or TCD': [''] * self.dvh.count, 'EUD': [''] * self.dvh.count, 'NTCP or TCP': [''] * self.dvh.count, 'PTV Overlap': self.dvh.ptv_overlap, 'ROI Type': self.dvh.roi_type, 'Rx Dose': self.dvh.rx_dose, 'Total Fxs': self.dvh.total_fxs, 'Fx Dose': self.dvh.fx_dose} self.data_table_rad_bio.set_data(data, self.columns) def apply_parameters(self, evt): """ Calculate rad bio values based on parameters supplied by user, pass information on to other tabs in GUI """ # Get the indices of the selected rows, or assume all should be updated selected_indices = get_selected_listctrl_items(self.table_rad_bio) if not selected_indices: selected_indices = range(self.data_table_rad_bio.row_count) # Concert user supplied parameters from text to floats eud_a = float_or_none(self.text_input_eud_a.GetValue()) gamma_50 = float_or_none(self.text_input_gamma_50.GetValue()) td_50 = float_or_none(self.text_input_td_50.GetValue()) # set the data in the datatable for the selected indices for i in selected_indices: current_row = self.data_table_rad_bio.get_row(i) for j in [7, 9]: current_row[j] = convert_value_to_str(current_row[j]) new_row = deepcopy(current_row) new_row[2], new_row[3], new_row[4] = eud_a, gamma_50, td_50 try: new_row[5] = "%0.2f" % round(calc_eud(self.dvh.dvh[:, i], eud_a), 2) except: new_row[5] = 'None' try: new_row[6] = "%0.2f" % round(calc_tcp(gamma_50, td_50, float(new_row[5])), 3) except: new_row[6] = 'None' self.data_table_rad_bio.edit_row(new_row, i) # Update data in in dvh object self.dvh.eud = [] self.dvh.ntcp_or_tcp = [] for i, eud in enumerate(self.data_table_rad_bio.data['EUD']): self.dvh.eud.append(float_or_none(eud)) self.dvh.ntcp_or_tcp.append(float_or_none(self.data_table_rad_bio.data['NTCP or TCP'][i])) # update data in time series self.time_series.update_y_axis_options() if self.time_series.combo_box_y_axis.GetValue() in ['EUD', 'NTCP or TCP']: self.time_series.update_plot() # update data in regression self.regression.stats_data.update_endpoints_and_radbio() self.regression.update_combo_box_y_choices() # update data in control chart self.control_chart.update_combo_box_y_choices() if self.control_chart.combo_box_y_axis.GetValue() in ['EUD', 'NTCP or TCP']: self.control_chart.update_plot() def clear_data(self): self.data_table_rad_bio.delete_all_rows() def get_csv(self, selection=None): return self.data_table_rad_bio.get_csv() def on_export_csv(self, evt): save_data_to_file(self.parent, "Export RadBio table to CSV", self.get_csv()) def get_save_data(self): return self.data_table_rad_bio.get_save_data() def load_save_data(self, save_data): self.data_table_rad_bio.load_save_data(save_data) @property def has_data(self): return any(self.data_table_rad_bio.data['a'])
class QueriedDataFrame(wx.Frame): """ Generate a simple table to view data of the current query for a specified SQL table """ def __init__(self, data_obj, sql_table, menu, menu_item_id): """ :param data_obj: object containing data to be viewed :param sql_table: either 'DVHs', 'Plans', 'Beams', or 'Rxs' :type sql_table: str :param menu: a link to the main app menu, used to toggle Show/Hide status :type menu: Menu :param menu_item_id: the ID of the menu item associated with the specified data_obj """ wx.Frame.__init__(self, None, title='%s Data' % sql_table[0:-1]) self.data = data_obj self.sql_table = sql_table self.menu = menu self.menu_item_id = menu_item_id self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) # self.data_table = DataTable(self.list_ctrl, data=self.table_data, columns=self.columns) self.data_table = DataTable(self.list_ctrl) self.data_table.set_data(self.table_data, self.columns) self.button_export = wx.Button(self, wx.ID_ANY, "Export to CSV") self.__do_bind() self.__set_properties() self.__do_layout() self.run() def __set_properties(self): self.SetSize(get_window_size(0.714, 0.762)) def __do_bind(self): self.Bind(wx.EVT_BUTTON, self.on_export, id=self.button_export.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_table, self.list_ctrl) self.Bind(wx.EVT_CLOSE, self.on_close) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_wrapper.Add(self.button_export, 0, wx.ALL, 10) sizer_wrapper.Add(self.list_ctrl, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 10) self.SetSizer(sizer_wrapper) self.Center() @property def columns(self): if self.sql_table == 'DVHs': columns = self.data.keys elif self.sql_table == 'Rxs': columns = [ 'plan_name', 'fx_dose', 'rx_percent', 'fxs', 'rx_dose', 'fx_grp_number', 'fx_grp_count', 'fx_grp_name', 'normalization_method', 'normalization_object' ] else: columns = [ obj['var_name'] for obj in sql_column_info.values() if obj['table'] == self.sql_table ] for starter_column in ['study_instance_uid', 'mrn']: if starter_column in columns: columns.pop(columns.index(starter_column)) columns.insert(0, starter_column) return columns @property def table_data(self): return {column: getattr(self.data, column) for column in self.columns} def run(self): self.toggle_data_menu_item() self.Show() def on_close(self, *args): self.toggle_data_menu_item() self.Destroy() def on_export(self, *args): save_data_to_file(self, "Export %s to CSV" % self.sql_table, self.data_table.get_csv()) def toggle_data_menu_item(self): short_cut = ['DVHs', 'Plans', 'Rxs', 'Beams'].index(self.sql_table) + 1 show_hide = ['Show', 'Hide']['Show' in self.menu.GetLabel(self.menu_item_id)] self.menu.SetLabel( self.menu_item_id, '%s %s\tCtrl+%s' % (show_hide, self.sql_table, short_cut)) def sort_table(self, evt): self.data_table.sort_table(evt)
class EndpointFrame: """ Object to be passed into notebook panel for the Endpoint tab """ def __init__(self, parent, dvh, time_series, regression, control_chart): """ :param parent: notebook panel in main view :type parent: Panel :param dvh: dvh data object :type dvh: DVH :param time_series: Time Series object in notebook :type time_series: TimeSeriesFrame :param regression: Regression frame object in notebook :type regression: RegressionFrame :param control_chart: Control Chart frame object in notebook :type control_chart: ControlChartFrame """ self.parent = parent self.dvh = dvh self.time_series = time_series self.regression = regression self.control_chart = control_chart self.initial_columns = ['MRN', 'Tx Site', 'ROI Name', 'Volume (cc)'] self.widths = [150, 150, 250, 100] self.button = {'add': wx.Button(self.parent, wx.ID_ANY, "Add Endpoint"), 'del': wx.Button(self.parent, wx.ID_ANY, "Delete Endpoint"), 'exp': wx.Button(self.parent, wx.ID_ANY, 'Export')} self.table = wx.ListCtrl(self.parent, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.table.SetMinSize(get_window_size(0.623, 0.762)) self.data_table = DataTable(self.table) self.endpoint_defs = DataTable(None, columns=['label', 'output_type', 'input_type', 'input_value', 'units_in', 'units_out']) if dvh: self.dvh.endpoints['data'] = self.data_table.data self.dvh.endpoints['defs'] = self.endpoint_defs.data self.__do_bind() self.__set_properties() self.__do_layout() self.disable_buttons() def __do_bind(self): self.parent.Bind(wx.EVT_BUTTON, self.add_ep_button_click, id=self.button['add'].GetId()) self.parent.Bind(wx.EVT_BUTTON, self.del_ep_button_click, id=self.button['del'].GetId()) self.parent.Bind(wx.EVT_BUTTON, self.on_export_csv, id=self.button['exp'].GetId()) def __set_properties(self): for i, column in enumerate(self.initial_columns): self.table.AppendColumn(column, format=wx.LIST_FORMAT_LEFT, width=self.widths[i]) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) vbox = wx.BoxSizer(wx.VERTICAL) hbox = wx.BoxSizer(wx.HORIZONTAL) for key in list(self.button): hbox.Add(self.button[key], 0, wx.ALL, 5) vbox.Add(hbox, 0, wx.ALL | wx.EXPAND, 5) vbox.Add(self.table, 1, wx.EXPAND, 0) sizer_wrapper.Add(vbox, 1, wx.ALL | wx.EXPAND, 20) self.layout = sizer_wrapper def calculate_endpoints(self): columns = [c for c in self.initial_columns] if self.data_table.data: current_labels = [key for key in list(self.data_table.data) if key not in columns] else: current_labels = [] ep = {'MRN': self.dvh.mrn, 'Tx Site': self.dvh.get_plan_values('tx_site'), 'ROI Name': self.dvh.roi_name, 'Volume (cc)': self.dvh.volume} ep_defs = self.endpoint_defs.data if ep_defs: for i, ep_name in enumerate(ep_defs['label']): if ep_name not in columns: columns.append(ep_name) if ep_name in current_labels: ep[ep_name] = deepcopy(self.data_table.data[ep_name]) else: endpoint_input = ep_defs['input_type'][i] endpoint_output = ep_defs['output_type'][i] x = float(ep_defs['input_value'][i]) if endpoint_input == 'relative': x /= 100. if 'V' in ep_name: ep[ep_name] = self.dvh.get_volume_of_dose(x, volume_scale=endpoint_output, dose_scale=endpoint_input) else: ep[ep_name] = self.dvh.get_dose_to_volume(x, dose_scale=endpoint_output, volume_scale=endpoint_input) self.data_table.set_data(ep, columns) self.data_table.set_column_width(0, 150) self.data_table.set_column_width(1, 150) self.data_table.set_column_width(2, 200) def add_ep_button_click(self, evt): dlg = AddEndpointDialog() res = dlg.ShowModal() if res == wx.ID_OK and dlg.is_endpoint_valid: self.endpoint_defs.append_row(dlg.endpoint_row) self.calculate_endpoints() self.enable_buttons() self.update_endpoints_in_dvh() self.time_series.update_y_axis_options() dlg.Destroy() self.regression.stats_data.update_endpoints_and_radbio() self.regression.update_combo_box_choices() self.control_chart.update_combo_box_y_choices() def del_ep_button_click(self, evt): dlg = DelEndpointDialog(self.data_table.columns) res = dlg.ShowModal() if res == wx.ID_OK: for value in dlg.selected_values: self.data_table.delete_column(value) endpoint_def_row = self.endpoint_defs.data['label'].index(value) self.update_endpoints_in_dvh() self.endpoint_defs.delete_row(endpoint_def_row) self.time_series.update_y_axis_options() dlg.Destroy() self.regression.stats_data.update_endpoints_and_radbio() self.regression.update_combo_box_choices() self.control_chart.update_combo_box_y_choices() if self.data_table.column_count == 3: self.button['del'].Disable() self.button['exp'].Disable() def update_dvh(self, dvh): self.dvh = dvh self.update_endpoints_in_dvh() def update_endpoints_in_dvh(self): if self.dvh: self.dvh.endpoints['data'] = self.data_table.data self.dvh.endpoints['defs'] = self.endpoint_defs.data def clear_data(self): self.data_table.delete_all_rows() self.endpoint_defs.delete_all_rows(force_delete_data=True) # no attached layout, force delete if self.data_table.data: for column in list(self.data_table.data): if column not in self.initial_columns: self.data_table.delete_column(column) def enable_buttons(self): for key in list(self.button): self.button[key].Enable() def disable_buttons(self): for key in list(self.button): self.button[key].Disable() def enable_initial_buttons(self): self.button['add'].Enable() def get_csv(self, selection=None): uid = {1: {'title': 'Study Instance UID', 'data': self.dvh.uid}} return self.data_table.get_csv(extra_column_data=uid) def on_export_csv(self, evt): save_data_to_file(self.parent, "Export Endpoints to CSV", self.get_csv()) def get_save_data(self): return deepcopy({'data_table': self.data_table.get_save_data(), 'endpoint_defs': self.endpoint_defs.get_save_data()}) def load_save_data(self, save_data): self.data_table.load_save_data(save_data['data_table'], ignore_layout=True) self.endpoint_defs.load_save_data(save_data['endpoint_defs'], ignore_layout=True) self.calculate_endpoints() @property def has_data(self): if self.endpoint_defs.data and self.endpoint_defs.data['label']: return True return False