Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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'])
Esempio n. 5
0
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)
Esempio n. 6
0
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