示例#1
0
    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()
示例#2
0
    def __init__(self, parent, group_data, time_series, regression,
                 control_chart):
        """
        :param parent:  notebook panel in main view
        :type parent: Panel
        :param group_data: dvh, table_data, and stats_data
        :type group_data: dict
        :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.group_data = group_data
        self.time_series = time_series
        self.regression = regression
        self.control_chart = control_chart
        self.group_data = group_data
        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 = {
            key: wx.ListCtrl(self.parent,
                             wx.ID_ANY,
                             style=wx.BORDER_SUNKEN | wx.LC_HRULES
                             | wx.LC_REPORT | wx.LC_VRULES)
            for key in [1, 2]
        }
        for table in self.table.values():
            table.SetMinSize(get_window_size(0.623, 0.28))
        self.data_table = {key: DataTable(self.table[key]) for key in [1, 2]}

        self.endpoint_defs = DataTable(None,
                                       columns=[
                                           'label', 'output_type',
                                           'input_type', 'input_value',
                                           'units_in', 'units_out'
                                       ])

        for key in [1, 2]:
            if self.group_data[key]['dvh']:
                self.group_data[key]['dvh'].endpoints[
                    'data'] = self.data_table[key].data
                self.group_data[key]['dvh'].endpoints[
                    'defs'] = self.endpoint_defs.data

        self.__do_bind()
        self.__set_properties()
        self.__do_layout()

        self.disable_buttons()
示例#3
0
    def __init__(self, parent, roi_map, physician, physician_roi):
        """
        :param parent: GUI parent
        :param roi_map: roi_map object
        :type roi_map: DatabaseROIs
        :param physician: initial physician value
        :type physician: str
        :param physician_roi: initial physician roi
        :type physician_roi: str
        """
        wx.Dialog.__init__(self, parent)
        self.parent = parent
        self.roi_map = roi_map
        self.initial_physician = physician
        self.initial_physician_roi = physician_roi

        self.combo_box_physician = wx.ComboBox(
            self,
            wx.ID_ANY,
            choices=list(self.roi_map.physicians),
            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_physician_roi = wx.ComboBox(self,
                                                   wx.ID_ANY,
                                                   choices=[],
                                                   style=wx.CB_DROPDOWN
                                                   | wx.CB_READONLY)
        self.list_ctrl_variations = wx.ListCtrl(
            self,
            wx.ID_ANY,
            style=wx.LC_NO_HEADER | wx.LC_REPORT | wx.BORDER_SUNKEN)
        self.button_select_all = wx.Button(self, wx.ID_ANY, "Select All")
        self.button_deselect_all = wx.Button(self, wx.ID_ANY, "Deselect All")
        self.button_add = wx.Button(self, wx.ID_ANY, "Add")
        self.button_delete = wx.Button(self, wx.ID_ANY, "Delete")
        self.button_move = wx.Button(self, wx.ID_ANY, "Move")
        self.button_dismiss = wx.Button(self, wx.ID_CANCEL, "Dismiss")

        self.button_move.Disable()
        self.button_delete.Disable()
        self.button_deselect_all.Disable()

        self.button_add_physician = wx.Button(self, wx.ID_ANY, "Add")
        self.button_add_physician_roi = wx.Button(self, wx.ID_ANY, "Add")

        self.columns = ['Variations']
        self.data_table = DataTable(self.list_ctrl_variations,
                                    columns=self.columns,
                                    widths=[400])

        self.__set_properties()
        self.__do_layout()
        self.__do_bind()

        self.run()
示例#4
0
    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()
示例#5
0
    def __init__(self, main_app_frame):

        self.main_app_frame = main_app_frame
        self.parent = main_app_frame.notebook_tab['Endpoints']
        self.group_data = main_app_frame.group_data
        self.time_series = main_app_frame.time_series
        self.regression = main_app_frame.regression
        self.control_chart = main_app_frame.control_chart
        self.group_data = main_app_frame.group_data
        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 = {
            key: wx.ListCtrl(self.parent,
                             wx.ID_ANY,
                             style=wx.BORDER_SUNKEN | wx.LC_HRULES
                             | wx.LC_REPORT | wx.LC_VRULES)
            for key in [1, 2]
        }
        for table in self.table.values():
            table.SetMinSize(get_window_size(0.623, 0.28))
        self.data_table = {key: DataTable(self.table[key]) for key in [1, 2]}

        self.endpoint_defs = DataTable(None,
                                       columns=[
                                           'label', 'output_type',
                                           'input_type', 'input_value',
                                           'units_in', 'units_out'
                                       ])

        for key in [1, 2]:
            if self.group_data[key]['dvh']:
                self.group_data[key]['dvh'].endpoints[
                    'data'] = self.data_table[key].data
                self.group_data[key]['dvh'].endpoints[
                    'defs'] = self.endpoint_defs.data

        self.__do_bind()
        self.__set_properties()
        self.__do_layout()

        self.disable_buttons()
示例#6
0
    def __init__(self, app):
        """
        :param app: easier to pass main frame pointer than several links to each data type
        :type app: DVHAMainFrame
        """
        wx.Dialog.__init__(self, None)

        self.app = app

        # Each of these objects shoudl have a has_data property, if False, those UI elements in this class will
        # be disabled (i.e., don't allow user to export empty tables
        self.enabled = {'DVHs': self.app.group_data[1]['dvh'].has_data,
                        'DVHs Summary': self.app.group_data[1]['dvh'].has_data,
                        'Endpoints': self.app.endpoint.has_data,
                        'Radbio': self.app.radbio.has_data,
                        'Charting Variables': self.app.time_series.has_data}

        checkbox_keys = ['DVHs', 'DVHs Summary', 'Endpoints', 'Radbio', 'Charting Variables']
        self.checkbox = {key: wx.CheckBox(self, wx.ID_ANY, key) for key in checkbox_keys}

        # set to a dictionary because previous versions had a tree with Regression
        self.list_ctrl = {'Charting Variables': wx.ListCtrl(self, wx.ID_ANY,
                                                            style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)}

        time_series_column = "Variables"
        time_series_variables = self.app.time_series.combo_box_y_axis.GetItems()
        time_series_data = {time_series_column: time_series_variables}
        self.data_table_time_series = DataTable(self.list_ctrl['Charting Variables'],
                                                columns=[time_series_column], widths=[400])
        self.data_table_time_series.set_data(time_series_data, [time_series_column])

        # set to a dictionary because previous versions had a table with Regression
        self.button_select_data = {'Charting Variables': {'Select': wx.Button(self, wx.ID_ANY, "Select All"),
                                                          'Deselect': wx.Button(self, wx.ID_ANY, "Deselect All")}}

        self.button_save = wx.Button(self, wx.ID_OK, "Save")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.button_select_all = wx.Button(self, wx.ID_ANY, 'Select All')
        self.button_deselect_all = wx.Button(self, wx.ID_ANY, 'Deselect All')

        self.__set_properties()
        self.__do_bind()
        self.__do_layout()

        self.run()
示例#7
0
    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()
示例#8
0
    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()
示例#9
0
    def __init__(self, main_app_frame):

        self.main_app_frame = main_app_frame
        self.parent = main_app_frame.notebook_tab['Rad Bio']
        self.group_data = main_app_frame.group_data
        self.time_series = main_app_frame.time_series
        self.regression = main_app_frame.regression
        self.control_chart = main_app_frame.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.radio_box_query_group = wx.RadioBox(self.parent,
                                                 wx.ID_ANY,
                                                 'Query Group',
                                                 choices=['1', '2', 'Both'])
        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 = {
            grp: wx.ListCtrl(self.parent,
                             wx.ID_ANY,
                             style=wx.BORDER_SUNKEN | wx.LC_HRULES
                             | wx.LC_REPORT | wx.LC_VRULES)
            for grp in [1, 2]
        }
        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 = {
            grp: DataTable(self.table_rad_bio[grp],
                           columns=self.columns,
                           widths=self.width,
                           formats=formats)
            for grp in [1, 2]
        }

        self.__set_properties()
        self.__do_layout()
        self.__do_bind()

        self.disable_buttons()
示例#10
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
示例#11
0
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.layout_set = False

        self.sizer_dvhs = wx.BoxSizer(wx.VERTICAL)

        set_msw_background_color(self)  # If windows, change the background color

        self.options = Options()

        # Initial DVH object and data
        self.save_data = {}
        self.group_data = {1: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None},
                           2: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None}}

        self.toolbar_keys = ['Open', 'Close', 'Save', 'Export', 'Import', 'Database', 'ROI Map', 'Settings']
        self.toolbar_ids = {key: i + 1000 for i, key in enumerate(self.toolbar_keys)}

        # sql_columns.py contains dictionaries of all queryable variables along with their
        # SQL columns and tables. Numerical categories include their units as well.
        self.categorical_columns = sql_columns.categorical
        self.numerical_columns = sql_columns.numerical

        # Keep track of currently selected row in the query tables
        self.selected_index_categorical = None
        self.selected_index_numerical = None

        # Load ROI Map now and pass to other objects for continuity
        # TODO: Need a method to address multiple users editing roi_map at the same time
        self.roi_map = DatabaseROIs()

        self.query_filters = None
        self.reset_query_filters()

        self.__add_menubar()
        self.__add_tool_bar()
        self.__add_layout_objects()
        self.__bind_layout_objects()

        columns = {'categorical': ['category_1', 'category_2', 'Filter Type'],
                   'numerical': ['category', 'min', 'max', 'Filter Type']}
        self.data_table_categorical = DataTable(self.table_categorical, columns=columns['categorical'])
        self.data_table_numerical = DataTable(self.table_numerical, columns=columns['numerical'])

        self.__set_properties()
        self.__set_tooltips()
        self.__add_notebook_frames()
        self.__do_layout()

        self.disable_query_buttons('categorical')
        self.disable_query_buttons('numerical')
        self.button_query_execute.Disable()
        self.__disable_notebook_tabs()

        self.Bind(wx.EVT_CLOSE, self.on_quit)
        self.tool_bar_windows = {key: None for key in ['import', 'database', 'roi_map']}

        wx.CallAfter(self.__catch_failed_sql_connection_on_app_launch)

        self.__do_subscribe()
示例#12
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)
示例#13
0
class EndpointFrame:
    """
    Object to be passed into notebook panel for the Endpoint tab
    """
    def __init__(self, parent, group_data, time_series, regression,
                 control_chart):
        """
        :param parent:  notebook panel in main view
        :type parent: Panel
        :param group_data: dvh, table_data, and stats_data
        :type group_data: dict
        :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.group_data = group_data
        self.time_series = time_series
        self.regression = regression
        self.control_chart = control_chart
        self.group_data = group_data
        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 = {
            key: wx.ListCtrl(self.parent,
                             wx.ID_ANY,
                             style=wx.BORDER_SUNKEN | wx.LC_HRULES
                             | wx.LC_REPORT | wx.LC_VRULES)
            for key in [1, 2]
        }
        for table in self.table.values():
            table.SetMinSize(get_window_size(0.623, 0.28))
        self.data_table = {key: DataTable(self.table[key]) for key in [1, 2]}

        self.endpoint_defs = DataTable(None,
                                       columns=[
                                           'label', 'output_type',
                                           'input_type', 'input_value',
                                           'units_in', 'units_out'
                                       ])

        for key in [1, 2]:
            if self.group_data[key]['dvh']:
                self.group_data[key]['dvh'].endpoints[
                    'data'] = self.data_table[key].data
                self.group_data[key]['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 table in self.table.values():
            for i, column in enumerate(self.initial_columns):
                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)
        for grp in [1, 2]:
            vbox.Add(
                wx.StaticText(self.parent, wx.ID_ANY, "Query Group %s:" % grp),
                0, wx.EXPAND | wx.BOTTOM, 5)
            vbox.Add(self.table[grp], 1, wx.EXPAND, 0)
            vbox.Add((10, 10), 0, 0, 0)
        sizer_wrapper.Add(vbox, 1, wx.ALL | wx.EXPAND, 20)
        self.layout = sizer_wrapper

    def calculate_endpoints(self):

        columns = {key: [c for c in self.initial_columns] for key in [1, 2]}
        if self.data_table[1].data:
            current_labels = [
                key for key in list(self.data_table[1].data)
                if key not in columns
            ]
        else:
            current_labels = []

        eps = {
            grp: {
                'MRN': group_data['dvh'].mrn,
                'Tx Site': group_data['dvh'].get_plan_values('tx_site'),
                'ROI Name': group_data['dvh'].roi_name,
                'Volume (cc)': group_data['dvh'].volume
            }
            for grp, group_data in self.group_data.items() if group_data['dvh']
        }

        ep_defs = self.endpoint_defs.data
        for group, ep in eps.items():
            if ep_defs:
                for i, ep_name in enumerate(ep_defs['label']):

                    if ep_name not in columns[group]:
                        columns[group].append(ep_name)

                        if ep_name in current_labels:
                            ep[ep_name] = deepcopy(
                                self.data_table[group].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.

                            dvh = self.group_data[group]['dvh']
                            if 'V' in ep_name:
                                ep[ep_name] = dvh.get_volume_of_dose(
                                    x,
                                    volume_scale=endpoint_output,
                                    dose_scale=endpoint_input)
                            else:
                                ep[ep_name] = dvh.get_dose_to_volume(
                                    x,
                                    dose_scale=endpoint_output,
                                    volume_scale=endpoint_input)

        for group, ep in eps.items():
            self.data_table[group].set_data(ep, columns[group])
            self.data_table[group].set_column_width(0, 150)
            self.data_table[group].set_column_width(1, 150)
            self.data_table[group].set_column_width(2, 200)

    def add_ep_button_click(self, evt):
        # TODO: Track down duplicate table refreshes/calculations
        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()
        dlg.Destroy()
        self.update_endpoints_and_radbio_in_group_data()
        self.regression.update_combo_box_choices()
        self.control_chart.update_combo_box_y_choices()
        self.time_series.update_y_axis_options()

    def del_ep_button_click(self, evt):
        dlg = DelEndpointDialog(self.data_table[1].columns)
        res = dlg.ShowModal()
        if res == wx.ID_OK:
            for value in dlg.selected_values:
                for data_table in self.data_table.values():
                    if data_table.columns and value in data_table.columns:
                        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.update_endpoints_and_radbio_in_group_data()

            self.time_series.update_y_axis_options()
        dlg.Destroy()

        for group in self.group_data.values():
            if group['stats_data']:
                group['stats_data'].update_endpoints_and_radbio()
        self.regression.update_combo_box_choices()
        self.control_chart.update_combo_box_y_choices()

        for data_table in self.data_table.values():
            if data_table.column_count == 3:
                self.button['del'].Disable()
                self.button['exp'].Disable()

    def update_dvh(self, group_data):
        self.group_data = group_data
        self.update_endpoints_in_dvh()

    def update_endpoints_and_radbio_in_group_data(self):
        for grp, data in self.group_data.items():
            if data['stats_data']:
                data['stats_data'].update_endpoints_and_radbio()
                if grp == 2:
                    sync_variables_in_stats_data_objects(
                        self.group_data[1]['stats_data'],
                        self.group_data[2]['stats_data'])

    def update_endpoints_in_dvh(self):
        for group in [1, 2]:
            if self.group_data[group]['dvh']:
                self.group_data[group]['dvh'].endpoints[
                    'data'] = self.data_table[group].data
                self.group_data[group]['dvh'].endpoints[
                    'defs'] = self.endpoint_defs.data

    def clear_data(self):
        for data_table in self.data_table.values():
            data_table.delete_all_rows()
        self.endpoint_defs.delete_all_rows(
            force_delete_data=True)  # no attached layout, force delete

        for data_table in self.data_table.values():
            if data_table.data:
                for column in list(data_table.data):
                    if column not in self.initial_columns:
                        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.group_data[1]['dvh'].uid
            }
        }
        csv = self.data_table[1].get_csv(extra_column_data=uid)
        if self.group_data[2]['dvh']:
            uid = {
                1: {
                    'title': 'Study Instance UID',
                    'data': self.group_data[2]['dvh'].uid
                }
            }
            csv = 'Group 1\n%s\n\nGroup 2\n%s' % (
                csv, self.data_table[2].get_csv(extra_column_data=uid))

        return csv

    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 {
            'data_table_1': self.data_table[1].get_save_data(),
            'data_table_2': self.data_table[2].get_save_data(),
            'endpoint_defs': self.endpoint_defs.get_save_data()
        }

    def load_save_data(self, save_data):
        self.data_table[1].load_save_data(save_data['data_table_1'],
                                          ignore_layout=True)
        self.data_table[2].load_save_data(save_data['data_table_2'],
                                          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
示例#14
0
class RoiManager(wx.Dialog):
    """
    Dialog accessible from the DICOM import GUI to allow user to easily update the ROI map with new plans
    """
    def __init__(self, parent, roi_map, physician, physician_roi):
        """
        :param parent: GUI parent
        :param roi_map: roi_map object
        :type roi_map: DatabaseROIs
        :param physician: initial physician value
        :type physician: str
        :param physician_roi: initial physician roi
        :type physician_roi: str
        """
        wx.Dialog.__init__(self, parent)
        self.parent = parent
        self.roi_map = roi_map
        self.initial_physician = physician
        self.initial_physician_roi = physician_roi

        self.combo_box_physician = wx.ComboBox(
            self,
            wx.ID_ANY,
            choices=list(self.roi_map.physicians),
            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_physician_roi = wx.ComboBox(self,
                                                   wx.ID_ANY,
                                                   choices=[],
                                                   style=wx.CB_DROPDOWN
                                                   | wx.CB_READONLY)
        self.list_ctrl_variations = wx.ListCtrl(
            self,
            wx.ID_ANY,
            style=wx.LC_NO_HEADER | wx.LC_REPORT | wx.BORDER_SUNKEN)
        self.button_select_all = wx.Button(self, wx.ID_ANY, "Select All")
        self.button_deselect_all = wx.Button(self, wx.ID_ANY, "Deselect All")
        self.button_add = wx.Button(self, wx.ID_ANY, "Add")
        self.button_delete = wx.Button(self, wx.ID_ANY, "Delete")
        self.button_move = wx.Button(self, wx.ID_ANY, "Move")
        self.button_dismiss = wx.Button(self, wx.ID_CANCEL, "Dismiss")

        self.button_move.Disable()
        self.button_delete.Disable()
        self.button_deselect_all.Disable()

        self.button_add_physician = wx.Button(self, wx.ID_ANY, "Add")
        self.button_add_physician_roi = wx.Button(self, wx.ID_ANY, "Add")

        self.columns = ['Variations']
        self.data_table = DataTable(self.list_ctrl_variations,
                                    columns=self.columns,
                                    widths=[400])

        self.__set_properties()
        self.__do_layout()
        self.__do_bind()

        self.run()

    def __set_properties(self):
        self.SetTitle("ROI Manager")
        self.combo_box_physician.SetValue(self.initial_physician)
        self.update_physician_rois()
        self.combo_box_physician_roi.SetValue(self.initial_physician_roi)
        self.update_variations()

    def __do_bind(self):
        self.Bind(wx.EVT_COMBOBOX,
                  self.physician_ticker,
                  id=self.combo_box_physician.GetId())
        self.Bind(wx.EVT_COMBOBOX,
                  self.physician_roi_ticker,
                  id=self.combo_box_physician_roi.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.add_physician,
                  id=self.button_add_physician.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.add_physician_roi,
                  id=self.button_add_physician_roi.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.select_all,
                  id=self.button_select_all.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.deselect_all,
                  id=self.button_deselect_all.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.add_variation,
                  id=self.button_add.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.move_variations,
                  id=self.button_move.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.delete_variations,
                  id=self.button_delete.GetId())
        self.Bind(wx.EVT_LIST_ITEM_SELECTED,
                  self.update_button_enable,
                  id=self.list_ctrl_variations.GetId())
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED,
                  self.update_button_enable,
                  id=self.list_ctrl_variations.GetId())

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_variation_buttons = wx.BoxSizer(wx.VERTICAL)
        sizer_variation_table = wx.BoxSizer(wx.VERTICAL)
        sizer_select = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, ""),
                                         wx.VERTICAL)
        sizer_variations = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician_roi = wx.BoxSizer(wx.VERTICAL)
        sizer_physician_roi_row_2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician = wx.BoxSizer(wx.VERTICAL)

        label_physician = wx.StaticText(self, wx.ID_ANY, "Physician:")
        sizer_physician.Add(label_physician, 0, wx.LEFT, 5)
        sizer_physician_row = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician_row.Add(self.combo_box_physician, 1,
                                wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_physician_row.Add(self.button_add_physician, 0,
                                wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
        sizer_physician.Add(sizer_physician_row, 1, wx.EXPAND, 0)
        sizer_select.Add(sizer_physician, 0, wx.ALL | wx.EXPAND, 5)

        label_physician_roi = wx.StaticText(self, wx.ID_ANY, "Physician ROI:")
        sizer_physician_roi.Add(label_physician_roi, 0, wx.LEFT, 5)
        sizer_physician_roi_row_2.Add(self.combo_box_physician_roi, 1,
                                      wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_physician_roi_row_2.Add(self.button_add_physician_roi, 0,
                                      wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
        sizer_physician_roi.Add(sizer_physician_roi_row_2, 0, wx.EXPAND, 0)
        sizer_select.Add(sizer_physician_roi, 0, wx.ALL | wx.EXPAND, 5)

        label_variations = wx.StaticText(self, wx.ID_ANY, "Variations:")
        label_variations_buttons = wx.StaticText(self, wx.ID_ANY, " ")
        sizer_variation_table.Add(label_variations, 0, 0, 0)
        sizer_variation_table.Add(self.list_ctrl_variations, 1,
                                  wx.ALL | wx.EXPAND, 0)
        sizer_variations.Add(sizer_variation_table, 0, wx.ALL, 5)
        sizer_variation_buttons.Add(label_variations_buttons, 0, 0, 0)
        sizer_variation_buttons.Add(self.button_add, 0,
                                    wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
                                    5)
        sizer_variation_buttons.Add(self.button_delete, 0, wx.EXPAND | wx.ALL,
                                    5)
        sizer_variation_buttons.Add(self.button_move, 0, wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(self.button_select_all, 0,
                                    wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(self.button_deselect_all, 0,
                                    wx.EXPAND | wx.ALL, 5)
        sizer_variations.Add(sizer_variation_buttons, 0, wx.EXPAND | wx.ALL, 5)
        sizer_select.Add(sizer_variations, 0, wx.ALL | wx.EXPAND, 5)

        sizer_wrapper.Add(sizer_select, 0, wx.ALL, 5)
        sizer_buttons.Add(self.button_dismiss, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_wrapper.Add(sizer_buttons, 0, wx.ALIGN_CENTER | wx.ALL, 0)

        self.SetSizer(sizer_wrapper)
        self.Layout()
        self.Fit()
        self.Center()

    def run(self):
        self.ShowModal()
        self.Destroy()

    def update_physicians(self, old_physicians=None):
        choices = list(self.roi_map.physicians)
        self.update_combo_box_choices(self.combo_box_physician, choices,
                                      old_physicians)

    def update_physician_rois(self, old_physician_rois=None):
        choices = self.roi_map.get_physician_rois(self.physician)
        self.update_combo_box_choices(self.combo_box_physician_roi, choices,
                                      old_physician_rois)

    @staticmethod
    def update_combo_box_choices(combo_box, choices, old_choices):
        if choices:
            value = choices[0]
            if old_choices:
                value = list(set(choices) - set(old_choices))
                value = value[0] if len(value) else combo_box.GetValue()

            combo_box.Clear()
            combo_box.AppendItems(choices)
            combo_box.SetValue(value)

    def update_variations(self):
        self.data_table.set_data(self.variation_table_data, self.columns)
        self.update_button_enable()

    def physician_ticker(self, evt):
        self.update_physician_rois()
        self.update_variations()

    def physician_roi_ticker(self, evt):
        self.update_variations()

    @property
    def physician(self):
        return self.combo_box_physician.GetValue()

    @property
    def physician_roi(self):
        return self.combo_box_physician_roi.GetValue()

    @property
    def variations(self):
        variations = self.roi_map.get_variations(
            self.combo_box_physician.GetValue(),
            self.combo_box_physician_roi.GetValue())
        variations = list(
            set(variations) -
            {self.combo_box_physician_roi.GetValue()})  # remove physician roi
        variations.sort()
        return variations

    @property
    def variation_table_data(self):
        return {'Variations': self.variations}

    @property
    def selected_indices(self):
        return get_selected_listctrl_items(self.list_ctrl_variations)

    @property
    def selected_values(self):
        return [
            self.list_ctrl_variations.GetItem(i, 0).GetText()
            for i in self.selected_indices
        ]

    @property
    def variation_count(self):
        return len(self.variations)

    def select_all(self, evt):
        self.apply_global_selection()

    def deselect_all(self, evt):
        self.apply_global_selection(on=0)

    def apply_global_selection(self, on=1):
        for i in range(self.variation_count):
            self.list_ctrl_variations.Select(i, on=on)

    def delete_variations(self, evt):
        self.roi_map.delete_variations(self.physician, self.physician_roi,
                                       self.selected_values)
        self.update_variations()

    def add_variation(self, evt):
        AddVariation(self.parent, self.physician, self.roi_map,
                     self.physician_roi)
        self.update_variations()

    def move_variations(self, evt):
        choices = [
            roi for roi in self.roi_map.get_physician_rois(self.physician)
            if roi != self.physician_roi
        ]
        MoveVariationDialog(self, self.selected_values, self.physician,
                            self.physician_roi, choices, self.roi_map)
        self.update_variations()

    def update_button_enable(self, *evt):
        if self.selected_indices:
            self.button_move.Enable()
            self.button_delete.Enable()
            self.button_deselect_all.Enable()
        else:
            self.button_move.Disable()
            self.button_delete.Disable()
            self.button_deselect_all.Disable()

    def add_physician_roi(self, evt):
        old_physician_rois = self.roi_map.get_physician_rois(self.physician)
        AddPhysicianROI(self.parent, self.physician, self.roi_map)
        self.update_physician_rois(old_physician_rois=old_physician_rois)

    def add_physician(self, evt):
        old_physicians = list(self.roi_map.physicians)
        AddPhysician(self.roi_map)
        self.update_physicians(old_physicians=old_physicians)
示例#15
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)
示例#16
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)
示例#17
0
class DVHAMainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.layout_set = False

        self.sizer_dvhs = wx.BoxSizer(wx.VERTICAL)

        set_msw_background_color(
            self)  # If windows, change the background color

        self.options = Options()

        # Initial DVH object and data
        self.dvh = None
        self.data = {key: None for key in ['Plans', 'Beams', 'Rxs']}
        self.stats_data = None
        self.save_data = {}

        self.toolbar_keys = [
            'Open', 'Close', 'Save', 'Export', 'Import', 'Database', 'ROI Map',
            'Settings'
        ]
        self.toolbar_ids = {
            key: i + 1000
            for i, key in enumerate(self.toolbar_keys)
        }

        # sql_columns.py contains dictionaries of all queryable variables along with their
        # SQL columns and tables. Numerical categories include their units as well.
        self.categorical_columns = sql_columns.categorical
        self.numerical_columns = sql_columns.numerical

        # Keep track of currently selected row in the query tables
        self.selected_index_categorical = None
        self.selected_index_numerical = None

        # Load ROI Map now and pass to other objects for continuity
        # TODO: Need a method to address multiple users editing roi_map at the same time
        self.roi_map = DatabaseROIs()

        self.__add_menubar()
        self.__add_tool_bar()
        self.__add_layout_objects()
        self.__bind_layout_objects()
        self.__set_properties()
        self.__set_tooltips()
        self.__add_notebook_frames()
        self.__do_layout()

        self.disable_query_buttons('categorical')
        self.disable_query_buttons('numerical')
        self.button_query_execute.Disable()
        self.__disable_notebook_tabs()

        columns = {
            'categorical': ['category_1', 'category_2', 'Filter Type'],
            'numerical': ['category', 'min', 'max', 'Filter Type']
        }
        self.data_table_categorical = DataTable(self.table_categorical,
                                                columns=columns['categorical'])
        self.data_table_numerical = DataTable(self.table_numerical,
                                              columns=columns['numerical'])

        if not echo_sql_db():
            self.__disable_add_filter_buttons()

    def __add_tool_bar(self):
        self.frame_toolbar = wx.ToolBar(self,
                                        -1,
                                        style=wx.TB_HORIZONTAL | wx.TB_TEXT)
        self.SetToolBar(self.frame_toolbar)

        description = {
            'Open': "Open previously queried data",
            'Close': "Clear queried data",
            'Save': "Save queried data",
            # 'Print': "Print a report",
            'Export': "Export data to CSV",
            'Import': "DICOM import wizard",
            'Settings': "User Settings",
            'Database': "Database Administrator Tools",
            'ROI Map': "Define ROI name aliases"
        }

        for key in self.toolbar_keys:
            bitmap = wx.Bitmap(ICONS[key], wx.BITMAP_TYPE_ANY)
            if is_windows() or is_linux():
                bitmap = scale_bitmap(bitmap, 30, 30)
            self.frame_toolbar.AddTool(self.toolbar_ids[key], key, bitmap,
                                       wx.NullBitmap, wx.ITEM_NORMAL,
                                       description[key], "")

            if key in {'Close', 'Export', 'ROI Map'}:
                self.frame_toolbar.AddSeparator()

        self.Bind(wx.EVT_TOOL, self.on_save, id=self.toolbar_ids['Save'])
        self.Bind(wx.EVT_TOOL, self.on_open, id=self.toolbar_ids['Open'])
        self.Bind(wx.EVT_TOOL, self.on_export, id=self.toolbar_ids['Export'])
        self.Bind(wx.EVT_TOOL,
                  self.on_toolbar_database,
                  id=self.toolbar_ids['Database'])
        self.Bind(wx.EVT_TOOL,
                  self.on_toolbar_settings,
                  id=self.toolbar_ids['Settings'])
        self.Bind(wx.EVT_TOOL,
                  self.on_toolbar_roi_map,
                  id=self.toolbar_ids['ROI Map'])
        self.Bind(wx.EVT_TOOL, self.on_close, id=self.toolbar_ids['Close'])
        self.Bind(wx.EVT_TOOL,
                  self.on_toolbar_import,
                  id=self.toolbar_ids['Import'])

    def __add_menubar(self):

        self.frame_menubar = wx.MenuBar()

        file_menu = wx.Menu()
        # file_menu.Append(wx.ID_NEW, '&New')
        menu_open = file_menu.Append(wx.ID_OPEN, '&Open\tCtrl+O')
        menu_save = file_menu.Append(wx.ID_ANY, '&Save\tCtrl+S')
        menu_close = file_menu.Append(wx.ID_ANY, '&Close')

        export_plot = wx.Menu()
        export_dvhs = export_plot.Append(wx.ID_ANY, 'DVHs')
        export_time_series = export_plot.Append(wx.ID_ANY, 'Time Series')
        export_regression = export_plot.Append(wx.ID_ANY, 'Regression')
        export_control_chart = export_plot.Append(wx.ID_ANY, 'Control Chart')

        export = wx.Menu()
        export_csv = export.Append(wx.ID_ANY, 'Data to csv\tCtrl+E')
        export.AppendSubMenu(export_plot, 'Plot to html')
        file_menu.AppendSeparator()

        qmi = file_menu.Append(wx.ID_ANY, '&Quit\tCtrl+Q')

        self.data_menu = wx.Menu()

        self.data_views = {
            key: None
            for key in ['DVHs', 'Plans', 'Rxs', 'Beams']
        }
        self.data_menu.AppendSubMenu(export, '&Export')
        self.data_menu_items = {
            'DVHs': self.data_menu.Append(wx.ID_ANY, 'Show DVHs\tCtrl+1'),
            'Plans': self.data_menu.Append(wx.ID_ANY, 'Show Plans\tCtrl+2'),
            'Rxs': self.data_menu.Append(wx.ID_ANY, 'Show Rxs\tCtrl+3'),
            'Beams': self.data_menu.Append(wx.ID_ANY, 'Show Beams\tCtrl+4')
        }

        settings_menu = wx.Menu()
        menu_pref = settings_menu.Append(wx.ID_PREFERENCES)
        menu_sql = settings_menu.Append(wx.ID_ANY,
                                        '&Database Connection\tCtrl+D')
        menu_user_settings = settings_menu.Append(wx.ID_ANY,
                                                  '&User Settings\tCtrl+,')

        help_menu = wx.Menu()
        menu_about = help_menu.Append(wx.ID_ANY, '&About')

        self.Bind(wx.EVT_MENU, self.on_quit, qmi)
        self.Bind(wx.EVT_MENU, self.on_open, menu_open)
        self.Bind(wx.EVT_MENU, self.on_close, menu_close)
        self.Bind(wx.EVT_MENU, self.on_export, export_csv)
        self.Bind(wx.EVT_MENU, self.on_save, menu_save)
        self.Bind(wx.EVT_MENU, self.on_pref, menu_pref)
        self.Bind(wx.EVT_MENU, self.on_pref, menu_user_settings)
        self.Bind(wx.EVT_MENU, self.on_about, menu_about)
        self.Bind(wx.EVT_MENU, self.on_sql, menu_sql)

        self.Bind(wx.EVT_MENU, self.on_save_plot_dvhs, export_dvhs)
        self.Bind(wx.EVT_MENU, self.on_save_plot_time_series,
                  export_time_series)
        self.Bind(wx.EVT_MENU, self.on_save_plot_regression, export_regression)
        self.Bind(wx.EVT_MENU, self.on_save_plot_control_chart,
                  export_control_chart)
        self.Bind(wx.EVT_MENU, self.on_view_dvhs, self.data_menu_items['DVHs'])
        self.Bind(wx.EVT_MENU, self.on_view_plans,
                  self.data_menu_items['Plans'])
        self.Bind(wx.EVT_MENU, self.on_view_rxs, self.data_menu_items['Rxs'])
        self.Bind(wx.EVT_MENU, self.on_view_beams,
                  self.data_menu_items['Beams'])

        self.frame_menubar.Append(file_menu, '&File')
        self.frame_menubar.Append(self.data_menu, '&Data')
        self.frame_menubar.Append(settings_menu, '&Settings')
        self.frame_menubar.Append(help_menu, '&Help')
        self.SetMenuBar(self.frame_menubar)

    def __add_layout_objects(self):
        self.button_categorical = {
            'add': wx.Button(self, wx.ID_ANY, "Add Filter"),
            'del': wx.Button(self, wx.ID_ANY, "Delete Selected"),
            'edit': wx.Button(self, wx.ID_ANY, "Edit Selected")
        }
        self.button_numerical = {
            'add': wx.Button(self, wx.ID_ANY, "Add Filter"),
            'del': wx.Button(self, wx.ID_ANY, "Delete Selected"),
            'edit': wx.Button(self, wx.ID_ANY, "Edit Selected")
        }

        self.table_categorical = wx.ListCtrl(self,
                                             wx.ID_ANY,
                                             style=wx.BORDER_SUNKEN
                                             | wx.LC_REPORT)
        self.table_numerical = wx.ListCtrl(self,
                                           wx.ID_ANY,
                                           style=wx.BORDER_SUNKEN
                                           | wx.LC_REPORT)

        self.button_query_execute = wx.Button(self, wx.ID_ANY,
                                              "Query and Retrieve")

        self.notebook_main_view = wx.Notebook(self, wx.ID_ANY)
        self.tab_keys = [
            'Welcome', 'DVHs', 'Endpoints', 'Rad Bio', 'Time Series',
            'Regression', 'Control Chart'
        ]
        self.notebook_tab = {
            key: wx.Panel(self.notebook_main_view, wx.ID_ANY)
            for key in self.tab_keys
        }

        self.text_summary = wx.StaticText(self,
                                          wx.ID_ANY,
                                          "",
                                          style=wx.ALIGN_LEFT)

    def __bind_layout_objects(self):
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_item_select_categorical,
                  self.table_categorical)
        self.Bind(wx.EVT_BUTTON,
                  self.add_row_categorical,
                  id=self.button_categorical['add'].GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.del_row_categorical,
                  id=self.button_categorical['del'].GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.edit_row_categorical,
                  id=self.button_categorical['edit'].GetId())
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.doubleclick_categorical,
                  self.table_categorical)

        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_item_select_numerical,
                  self.table_numerical)
        self.Bind(wx.EVT_BUTTON,
                  self.add_row_numerical,
                  id=self.button_numerical['add'].GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.del_row_numerical,
                  id=self.button_numerical['del'].GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.edit_row_numerical,
                  id=self.button_numerical['edit'].GetId())
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.doubleclick_numerical,
                  self.table_numerical)

        self.Bind(wx.EVT_BUTTON,
                  self.exec_query_button,
                  id=self.button_query_execute.GetId())

        self.Bind(wx.EVT_SIZE, self.on_resize)

    def __set_properties(self):
        self.SetTitle("DVH Analytics")

        self.frame_toolbar.Realize()

        self.table_categorical.AppendColumn("Category1",
                                            format=wx.LIST_FORMAT_LEFT,
                                            width=180)
        self.table_categorical.AppendColumn("Category2",
                                            format=wx.LIST_FORMAT_LEFT,
                                            width=150)
        self.table_categorical.AppendColumn("Filter Type",
                                            format=wx.LIST_FORMAT_LEFT,
                                            width=80)

        self.table_numerical.AppendColumn("Category",
                                          format=wx.LIST_FORMAT_LEFT,
                                          width=150)
        self.table_numerical.AppendColumn("Min",
                                          format=wx.LIST_FORMAT_LEFT,
                                          width=90)
        self.table_numerical.AppendColumn("Max",
                                          format=wx.LIST_FORMAT_LEFT,
                                          width=90)
        self.table_numerical.AppendColumn("Filter Type",
                                          format=wx.LIST_FORMAT_LEFT,
                                          width=80)

    def __set_tooltips(self):
        self.button_categorical['add'].SetToolTip(
            "Add a categorical data filter.")
        self.button_categorical['del'].SetToolTip(
            "Delete the currently selected category filter.")
        self.button_categorical['edit'].SetToolTip(
            "Edit the currently selected category filter.")
        self.button_numerical['add'].SetToolTip("Add a numerical data filter.")
        self.button_numerical['del'].SetToolTip(
            "Delete the currently selected numerical data filter.")
        self.button_numerical['edit'].SetToolTip(
            "Edit the currently selected data filter.")
        self.button_query_execute.SetToolTip(
            "Query the database with the filters entered below. At least one "
            "filter must be added.")

    def __add_notebook_frames(self):
        self.plot = PlotStatDVH(self.notebook_tab['DVHs'], self.dvh,
                                self.options)
        self.time_series = TimeSeriesFrame(self.notebook_tab['Time Series'],
                                           self.dvh, self.data, self.options)
        self.regression = RegressionFrame(self.notebook_tab['Regression'],
                                          self.stats_data, self.options)
        self.control_chart = ControlChartFrame(
            self.notebook_tab['Control Chart'], self.dvh, self.stats_data,
            self.options)
        self.radbio = RadBioFrame(self.notebook_tab['Rad Bio'], self.dvh,
                                  self.time_series, self.regression,
                                  self.control_chart)
        self.endpoint = EndpointFrame(self.notebook_tab['Endpoints'], self.dvh,
                                      self.time_series, self.regression,
                                      self.control_chart)

    def __do_layout(self):
        sizer_summary = wx.StaticBoxSizer(
            wx.StaticBox(self, wx.ID_ANY, "Summary"), wx.HORIZONTAL)
        sizer_query_numerical = wx.StaticBoxSizer(
            wx.StaticBox(self, wx.ID_ANY, "Query by Numerical Data"),
            wx.VERTICAL)

        sizer_query_categorical = wx.StaticBoxSizer(
            wx.StaticBox(self, wx.ID_ANY, "Query by Categorical Data"),
            wx.VERTICAL)

        sizer_categorical_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_numerical_buttons = wx.BoxSizer(wx.HORIZONTAL)

        for key in ['add', 'del', 'edit']:
            sizer_categorical_buttons.Add(self.button_categorical[key], 0,
                                          wx.ALL, 5)
            sizer_numerical_buttons.Add(self.button_numerical[key], 0, wx.ALL,
                                        5)

        sizer_query_categorical.Add(sizer_categorical_buttons, 0,
                                    wx.ALL | wx.EXPAND, 5)
        sizer_query_categorical.Add(self.table_categorical, 1,
                                    wx.ALL | wx.EXPAND, 10)

        sizer_query_numerical.Add(sizer_numerical_buttons, 0,
                                  wx.ALL | wx.EXPAND, 5)
        sizer_query_numerical.Add(self.table_numerical, 1, wx.ALL | wx.EXPAND,
                                  10)

        sizer_summary.Add(self.text_summary)

        panel_left = wx.BoxSizer(wx.VERTICAL)
        panel_left.Add(sizer_query_categorical, 0,
                       wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED | wx.TOP, 5)
        panel_left.Add(sizer_query_numerical, 0,
                       wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED,
                       5)
        panel_left.Add(self.button_query_execute, 0,
                       wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, 15)
        panel_left.Add(sizer_summary, 1, wx.ALL | wx.EXPAND, 5)

        bitmap_logo = wx.StaticBitmap(self.notebook_tab['Welcome'], wx.ID_ANY,
                                      wx.Bitmap(LOGO_PATH, wx.BITMAP_TYPE_ANY))
        text_welcome = wx.StaticText(
            self.notebook_tab['Welcome'],
            wx.ID_ANY,
            "\n\nWelcome to DVH Analytics.\nIf you already have a database built, design "
            "a query with the filters to the left.\n\n\n\nDVH Analytics is a software "
            "application to help radiation oncology departments build an in-house database "
            "of treatment planning data for the purpose of historical comparisons and "
            "statistical analysis. This code is still in development. Please contact the "
            "developer if you are interested in testing or collaborating.\n\nThe application "
            "builds a SQL database of DVHs and various planning parameters from DICOM files "
            "(i.e., Plan, Structure, Dose). Since the data is extracted directly from DICOM "
            "files, we intend to accommodate an array of treatment planning system vendors.",
            style=wx.ALIGN_CENTER)

        text_welcome_size = get_window_size(0.417, 0.476)
        text_welcome.SetMinSize(text_welcome_size)
        text_welcome.Wrap(text_welcome_size[1])

        sizer_welcome = wx.BoxSizer(wx.VERTICAL)
        sizer_welcome.Add(bitmap_logo, 0,
                          wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP, 100)
        sizer_welcome.Add(text_welcome, 0, wx.ALIGN_CENTER | wx.ALL, 25)
        self.notebook_tab['Welcome'].SetSizer(sizer_welcome)

        self.sizer_dvhs.Add(self.plot.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['DVHs'].SetSizer(self.sizer_dvhs)

        sizer_endpoint = wx.BoxSizer(wx.VERTICAL)
        sizer_endpoint.Add(self.endpoint.layout, 0, wx.ALL, 25)
        self.notebook_tab['Endpoints'].SetSizer(sizer_endpoint)

        sizer_rad_bio = wx.BoxSizer(wx.VERTICAL)
        sizer_rad_bio.Add(self.radbio.layout, 0, wx.ALL, 25)
        self.notebook_tab['Rad Bio'].SetSizer(sizer_rad_bio)

        sizer_time_series = wx.BoxSizer(wx.VERTICAL)
        sizer_time_series.Add(self.time_series.layout, 1, wx.EXPAND | wx.ALL,
                              25)
        self.notebook_tab['Time Series'].SetSizer(sizer_time_series)

        sizer_regression = wx.BoxSizer(wx.VERTICAL)
        sizer_regression.Add(self.regression.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Regression'].SetSizer(sizer_regression)

        sizer_control_chart = wx.BoxSizer(wx.VERTICAL)
        sizer_control_chart.Add(self.control_chart.layout, 1,
                                wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Control Chart'].SetSizer(sizer_control_chart)

        for key in self.tab_keys:
            self.notebook_main_view.AddPage(self.notebook_tab[key], key)

        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_main_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        hbox_main = wx.BoxSizer(wx.HORIZONTAL)
        hbox_main.Add(panel_left, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.TOP,
                      5)
        hbox_main.Add(self.notebook_main_view, 1,
                      wx.BOTTOM | wx.EXPAND | wx.RIGHT | wx.TOP, 5)
        sizer_main_wrapper.Add(hbox_main, 1, wx.EXPAND, 0)
        sizer_main.Add(sizer_main_wrapper, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_main)
        self.Layout()

        self.SetSize(get_window_size(0.833, 0.857))

        self.Center()

    def __enable_notebook_tabs(self):
        for key in self.tab_keys:
            if key in {'Regression', 'Control Chart'}:
                self.notebook_tab[key].Enable(self.dvh.count > 1)
            else:
                self.notebook_tab[key].Enable()
        self.__enable_initial_buttons_in_tabs()

    def __disable_notebook_tabs(self):
        for key in self.tab_keys:
            if key != 'Welcome':
                self.notebook_tab[key].Disable()

    def __enable_initial_buttons_in_tabs(self):
        self.endpoint.enable_initial_buttons()
        self.radbio.enable_initial_buttons()
        self.time_series.enable_initial_buttons()

    def __disable_add_filter_buttons(self):
        self.button_categorical['add'].Disable()
        self.button_numerical['add'].Disable()

    def __enable_add_filter_buttons(self):
        self.button_categorical['add'].Enable()
        self.button_numerical['add'].Enable()

    def enable_query_buttons(self, query_type):
        for key in ['del', 'edit']:
            {
                'categorical': self.button_categorical[key],
                'numerical': self.button_numerical[key]
            }[query_type].Enable()

    def disable_query_buttons(self, query_type):
        for key in ['del', 'edit']:
            {
                'categorical': self.button_categorical[key],
                'numerical': self.button_numerical[key]
            }[query_type].Disable()

    def update_all_query_buttons(self):
        tables = {
            'numerical': self.data_table_numerical,
            'categorical': self.data_table_categorical
        }
        for key, table in tables.items():
            if table.data is not None:
                [self.disable_query_buttons,
                 self.enable_query_buttons][table.row_count > 0](key)
            else:
                self.disable_query_buttons(key)

        if self.data_table_numerical.row_count + self.data_table_categorical.row_count > 0:
            self.button_query_execute.Enable()
        else:
            self.button_query_execute.Disable()

    # --------------------------------------------------------------------------------------------------------------
    # Menu bar event functions
    # --------------------------------------------------------------------------------------------------------------
    def on_save(self, evt):
        if self.save_data:
            dlg = wx.FileDialog(self,
                                "Save your downloaded data to file",
                                "",
                                wildcard='*.dvha',
                                style=wx.FD_SAVE)
            dlg.SetDirectory(DATA_DIR)
            if dlg.ShowModal() == wx.ID_OK:
                self.save_data_obj()
                save_object_to_file(self.save_data, dlg.GetPath())
            dlg.Destroy()
        else:
            wx.MessageBox(
                'There is no data to save. Please query/open some data first.',
                'Save Error', wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)

    def on_open(self, evt):
        dlg = wx.FileDialog(self,
                            "Open saved data",
                            "",
                            wildcard='*.dvha',
                            style=wx.FD_FILE_MUST_EXIST | wx.FD_OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.close()
            self.load_data_obj(dlg.GetPath())

        dlg.Destroy()

    def save_data_obj(self):
        self.save_data['dvh'] = self.dvh
        self.save_data['main_data'] = self.data
        self.save_data['time_stamp'] = datetime.now()
        self.save_data['version'] = self.options.VERSION
        # data_table_categorical and data_table_numerical saved after query to ensure these data reflect
        # the rest of the saved data
        self.save_data['endpoint'] = self.endpoint.get_save_data()
        self.save_data['time_series'] = self.time_series.get_save_data()
        self.save_data['radbio'] = self.radbio.get_save_data()
        self.save_data['regression'] = self.regression.get_save_data()

    def load_data_obj(self, abs_file_path):
        self.save_data = load_object_from_file(abs_file_path)
        self.dvh = self.save_data['dvh']
        self.data = self.save_data['main_data']

        # .load_save_data loses column widths?
        self.data_table_categorical.data = deepcopy(
            self.save_data['main_categorical']['data'])
        self.data_table_numerical.data = deepcopy(
            self.save_data['main_numerical']['data'])
        self.data_table_categorical.set_data_in_layout()
        self.data_table_numerical.set_data_in_layout()
        self.update_all_query_buttons()

        self.exec_query(load_saved_dvh_data=True)

        self.endpoint.load_save_data(self.save_data['endpoint'])
        if self.endpoint.has_data:
            self.endpoint.enable_buttons()

        self.radbio.load_save_data(self.save_data['radbio'])

        self.endpoint.update_endpoints_in_dvh()

        self.time_series.update_y_axis_options()
        self.time_series.load_save_data(self.save_data['time_series'])
        self.time_series.update_plot()

        if self.dvh.count > 1:
            self.regression.stats_data.update_endpoints_and_radbio()
            self.regression.update_combo_box_choices()
            self.regression.load_save_data(self.save_data['regression'])

            self.control_chart.update_combo_box_y_choices()

    def on_toolbar_settings(self, evt):
        self.on_pref()

    def on_toolbar_import(self, evt):
        self.check_db_then_call(ImportDicomFrame, self.roi_map, self.options)

    def on_toolbar_database(self, evt):
        self.check_db_then_call(DatabaseEditorFrame, self.roi_map)

    def on_toolbar_roi_map(self, evt):
        self.check_db_then_call(ROIMapFrame, self.roi_map)

    def check_db_then_call(self, func, *parameters):
        if not echo_sql_db():
            self.on_sql()

        if echo_sql_db():
            func(*parameters)
        else:
            wx.MessageBox(
                'Connection to SQL database could not be established.',
                'Connection Error', wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)

    # --------------------------------------------------------------------------------------------------------------
    # Query event functions
    # --------------------------------------------------------------------------------------------------------------
    def on_item_select_categorical(self, evt):
        self.selected_index_categorical = self.table_categorical.GetFirstSelected(
        )

    def on_item_select_numerical(self, evt):
        self.selected_index_numerical = self.table_numerical.GetFirstSelected()

    def add_row_categorical(self, evt):
        query_dlg(self, 'categorical')
        self.button_query_execute.Enable()
        self.update_all_query_buttons()

    def add_row_numerical(self, evt):
        query_dlg(self, 'numerical')
        self.update_all_query_buttons()

    def edit_row_categorical(self, *args):
        if self.selected_index_categorical is not None:
            query_dlg(self, 'categorical', set_values=True)

    def edit_row_numerical(self, *args):
        if self.selected_index_numerical is not None:
            query_dlg(self, 'numerical', set_values=True)

    def doubleclick_categorical(self, evt):
        self.selected_index_categorical = self.table_categorical.GetFirstSelected(
        )
        self.edit_row_categorical()

    def doubleclick_numerical(self, evt):
        self.selected_index_numerical = self.table_numerical.GetFirstSelected()
        self.edit_row_numerical()

    def del_row_categorical(self, evt):
        self.data_table_categorical.delete_row(self.selected_index_categorical)
        self.selected_index_categorical = None
        self.update_all_query_buttons()

    def del_row_numerical(self, evt):
        self.data_table_numerical.delete_row(self.selected_index_numerical)
        self.selected_index_numerical = None
        self.update_all_query_buttons()

    def exec_query_button(self, evt):
        # TODO: Thread this process
        self.exec_query()

    def exec_query(self, load_saved_dvh_data=False):
        wait = wx.BusyCursor()

        # self.dvh = None
        self.plot.clear_plot()
        self.endpoint.clear_data()
        self.time_series.clear_data()
        self.time_series.initialize_y_axis_options()
        self.regression.clear()
        self.control_chart.clear_data()
        self.control_chart.initialize_y_axis_options()
        self.radbio.clear_data()

        if not load_saved_dvh_data:
            uids, dvh_str = self.get_query()
            self.dvh = DVH(dvh_condition=dvh_str, uid=uids)

        if self.dvh.count:
            self.endpoint.update_dvh(self.dvh)
            self.text_summary.SetLabelText(self.dvh.get_summary())
            self.plot.update_plot(self.dvh)
            del wait
            self.notebook_main_view.SetSelection(1)
            self.update_data(load_saved_dvh_data=load_saved_dvh_data)
            self.time_series.update_data(self.dvh, self.data)
            if self.dvh.count > 1:
                self.control_chart.update_data(self.dvh, self.stats_data)

            self.radbio.update_dvh_data(self.dvh)

            self.__enable_notebook_tabs()

            self.save_data[
                'main_categorical'] = self.data_table_categorical.get_save_data(
                )
            self.save_data[
                'main_numerical'] = self.data_table_numerical.get_save_data()
        else:
            del wait
            wx.MessageBox(
                'No DVHs returned. Please modify query or import more data.',
                'Query Error', wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)
            self.dvh = None

    def get_query(self):

        # Used to accumulate lists of query strings for each table
        # Will assume each item in list is complete query for that SQL column
        queries = {'Plans': [], 'Rxs': [], 'Beams': [], 'DVHs': []}

        # Used to group queries by variable, will combine all queries of same variable with an OR operator
        # e.g., queries_by_sql_column['Plans'][key] = list of strings, where key is sql column
        queries_by_sql_column = {
            'Plans': {},
            'Rxs': {},
            'Beams': {},
            'DVHs': {}
        }

        # Categorical filter
        if self.data_table_categorical.row_count:
            for i, category in enumerate(
                    self.data_table_categorical.data['category_1']):
                table = self.categorical_columns[category]['table']
                col = self.categorical_columns[category]['var_name']
                value = self.data_table_categorical.data['category_2'][i]
                if col not in queries_by_sql_column[table]:
                    queries_by_sql_column[table][col] = []
                operator = ['=', '!='][{
                    'Include': 0,
                    'Exclude': 1
                }[self.data_table_categorical.data['Filter Type'][i]]]
                queries_by_sql_column[table][col].append(
                    "%s %s '%s'" % (col, operator, value))

        # Range filter
        if self.data_table_numerical.row_count:
            for i, category in enumerate(
                    self.data_table_numerical.data['category']):
                table = self.numerical_columns[category]['table']
                col = self.numerical_columns[category]['var_name']
                value_low = self.data_table_numerical.data['min'][i]
                value_high = self.data_table_numerical.data['max'][i]
                if 'date' in col:
                    value_low = "'%s'::date" % value_low
                    value_high = "'%s'::date" % value_high
                if col not in queries_by_sql_column[table]:
                    queries_by_sql_column[table][col] = []
                operator = ['BETWEEN', 'NOT BETWEEN'][{
                    'Include': 0,
                    'Exclude': 1
                }[self.data_table_numerical.data['Filter Type'][i]]]
                queries_by_sql_column[table][col].append(
                    "%s %s %s AND %s" % (col, operator, value_low, value_high))

        for table in queries:
            for col in queries_by_sql_column[table]:
                queries[table].append(
                    "(%s)" % ' OR '.join(queries_by_sql_column[table][col]))
            queries[table] = ' AND '.join(queries[table])

        uids = get_study_instance_uids(plans=queries['Plans'],
                                       rxs=queries['Rxs'],
                                       beams=queries['Beams'])['common']

        return uids, queries['DVHs']

    def update_data(self, load_saved_dvh_data=False):
        wait = wx.BusyCursor()
        tables = ['Plans', 'Rxs', 'Beams']
        if hasattr(self.dvh, 'study_instance_uid'):
            if not load_saved_dvh_data:
                condition_str = "study_instance_uid in ('%s')" % "','".join(
                    self.dvh.study_instance_uid)
                self.data = {
                    key: QuerySQL(key, condition_str)
                    for key in tables
                }
        else:
            self.data = {key: None for key in tables}
        del wait

        if hasattr(self.dvh, 'study_instance_uid'):
            wait = wx.BusyCursor()
            self.stats_data = StatsData(self.dvh, self.data)
            self.regression.stats_data = self.stats_data
            self.control_chart.stats_data = self.stats_data
            try:
                self.regression.update_combo_box_choices()
            except ValueError:
                # TODO: Print error in GUI
                pass
            self.control_chart.update_combo_box_y_choices()
            del wait

    # --------------------------------------------------------------------------------------------------------------
    # Menu bar event functions
    # --------------------------------------------------------------------------------------------------------------
    def on_quit(self, evt):
        self.Close()

    def on_close(self, evt):
        if self.dvh:
            dlg = wx.MessageDialog(self,
                                   "Clear all data and plots?",
                                   caption='Close',
                                   style=wx.YES | wx.NO | wx.NO_DEFAULT
                                   | wx.CENTER | wx.ICON_EXCLAMATION)
            dlg.Center()
            res = dlg.ShowModal()
            if res == wx.ID_YES:
                self.close()
            dlg.Destroy()

    def close(self):
        self.dvh = None
        self.data_table_categorical.delete_all_rows()
        self.data_table_numerical.delete_all_rows()
        self.plot.clear_plot()
        self.endpoint.clear_data()
        self.radbio.clear_data()
        self.time_series.clear_data()
        self.notebook_main_view.SetSelection(0)
        self.text_summary.SetLabelText("")
        self.__disable_notebook_tabs()
        self.disable_query_buttons('categorical')
        self.disable_query_buttons('numerical')
        self.button_query_execute.Disable()
        self.time_series.initialize_y_axis_options()
        self.regression.clear()
        self.control_chart.initialize_y_axis_options()
        self.control_chart.plot.clear_plot()

    def on_export(self, evt):
        if self.dvh is not None:
            ExportCSVDialog(self)
        else:
            wx.MessageBox(
                'There is no data to export! Please query some data first.',
                'Export Error', wx.OK | wx.ICON_WARNING)

    def on_about(self, evt):
        About()

    def on_pref(self, *args):
        UserSettings(self.options)

    def on_sql(self, *args):
        SQLSettingsDialog()
        [self.__disable_add_filter_buttons,
         self.__enable_add_filter_buttons][echo_sql_db()]()

    def on_save_plot_dvhs(self, evt):
        save_data_to_file(self,
                          'Save DVHs plot',
                          self.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_time_series(self, evt):
        save_data_to_file(self,
                          'Save Time Series plot',
                          self.time_series.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_regression(self, evt):
        save_data_to_file(self,
                          'Save Regression plot',
                          self.regression.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_control_chart(self, evt):
        save_data_to_file(self,
                          'Save Control Chart plot',
                          self.control_chart.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_view_dvhs(self, evt):
        self.view_table_data('DVHs')

    def on_view_plans(self, evt):
        self.view_table_data('Plans')

    def on_view_rxs(self, evt):
        self.view_table_data('Rxs')

    def on_view_beams(self, evt):
        self.view_table_data('Beams')

    def view_table_data(self, key):
        if key == 'DVHs':
            data = self.dvh
        else:
            data = self.data[key]

        if data:
            if self.get_menu_item_status(key) == 'Show':
                self.data_views[key] = QueriedDataFrame(
                    data, key, self.data_menu,
                    self.data_menu_items[key].GetId())
            else:
                self.data_views[key].on_close()
                self.data_views[key] = None
        else:
            dlg = wx.MessageDialog(self, 'Please query/open some data first.',
                                   'ERROR!', wx.ICON_ERROR | wx.OK_DEFAULT)
            dlg.ShowModal()
            dlg.Destroy()

    def get_menu_item_status(self, key):
        show_hide = [
            'Hide', 'Show'
        ]['Show' in self.data_menu.GetLabel(self.data_menu_items[key].GetId())]
        return show_hide

    def redraw_plots(self):
        if self.dvh:
            self.plot.redraw_plot()
            self.time_series.plot.redraw_plot()
            self.regression.plot.redraw_plot()
            self.control_chart.plot.redraw_plot()

    def on_resize(self, *evt):
        try:
            self.Refresh()
            self.Layout()
            wx.CallAfter(self.redraw_plots)
        except RuntimeError:
            pass
示例#18
0
class ROIMapFrame(wx.Frame):
    """
    Class to view and edit roi map
    """
    def __init__(self, roi_map):
        """
        :param roi_map: roi map object
        :type roi_map: DatabaseROIs
        """
        wx.Frame.__init__(self, None, title='ROI Map')
        set_frame_icon(self)

        self.roi_map = roi_map

        self.window_size = get_window_size(0.893, 0.762)
        self.SetSize(self.window_size)
        self.window = wx.SplitterWindow(self, wx.ID_ANY)
        self.window_tree = wx.Panel(self.window, wx.ID_ANY, style=wx.BORDER_SUNKEN)

        self.combo_box_tree_plot_data = wx.ComboBox(self.window_tree, wx.ID_ANY,
                                                    choices=['All', 'Linked', 'Unlinked', 'Branched'],
                                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.plot = PlotROIMap(self.window_tree, roi_map)
        self.window_editor = wx.Panel(self.window, wx.ID_ANY, style=wx.BORDER_SUNKEN)

        self.combo_box_physician = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=self.roi_map.get_physicians(),
                                               style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_physician_roi = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=[],
                                                   style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.list_ctrl_variations = wx.ListCtrl(self.window_editor, wx.ID_ANY,
                                                style=wx.LC_NO_HEADER | wx.LC_REPORT | wx.BORDER_SUNKEN)
        self.button_variation_select_all = wx.Button(self.window_editor, wx.ID_ANY, "Select All")
        self.button_variation_deselect_all = wx.Button(self.window_editor, wx.ID_ANY, "Deselect All")
        self.button_variation_add = wx.Button(self.window_editor, wx.ID_ANY, "Add")
        self.button_variation_delete = wx.Button(self.window_editor, wx.ID_ANY, "Delete")
        self.button_variation_move = wx.Button(self.window_editor, wx.ID_ANY, "Move")

        self.button_variation_move.Disable()
        self.button_variation_delete.Disable()
        self.button_variation_deselect_all.Disable()

        self.button_physician = {'add': wx.Button(self.window_editor, wx.ID_ANY, "+"),
                                 'del': wx.Button(self.window_editor, wx.ID_ANY, "-"),
                                 'edit': wx.Button(self.window_editor, wx.ID_ANY, "Δ")}
        self.button_physician_roi = {'add': wx.Button(self.window_editor, wx.ID_ANY, "+"),
                                     'del': wx.Button(self.window_editor, wx.ID_ANY, "-"),
                                     'edit': wx.Button(self.window_editor, wx.ID_ANY, "Δ")}

        self.button_link_physician_roi = wx.Button(self.window_editor, wx.ID_ANY, "Link")
        self.button_link_physician_roi.Disable()

        self.combo_box_uncategorized_ignored = wx.ComboBox(self.window_editor, wx.ID_ANY,
                                                           choices=["Uncategorized", "Ignored"],
                                                           style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_uncategorized_ignored_roi = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=[],
                                                               style=wx.CB_DROPDOWN)
        self.button_uncategorized_ignored_delete = wx.Button(self.window_editor, wx.ID_ANY, "Delete DVH")
        self.button_uncategorized_ignored_ignore = wx.Button(self.window_editor, wx.ID_ANY, "Ignore DVH")
        self.combo_box_physician_roi_merge = {'a': wx.ComboBox(self.window_editor, wx.ID_ANY, style=wx.CB_DROPDOWN),
                                              'b': wx.ComboBox(self.window_editor, wx.ID_ANY, style=wx.CB_DROPDOWN)}
        self.button_merge = wx.Button(self.window_editor, wx.ID_ANY, "Merge")

        self.button_save_and_update = wx.Button(self.window_editor, wx.ID_ANY, "Save and Update Database")
        self.button_cancel = wx.Button(self.window_editor, wx.ID_ANY, "Cancel Changes and Reload")

        self.uncategorized_variations = {}

        self.columns = ['Variations']
        self.data_table = DataTable(self.list_ctrl_variations, columns=self.columns, widths=[490])

        self.__set_properties()
        self.__do_bind()
        self.__do_layout()

        self.plot.update_roi_map_source_data(self.physician)

        self.run()

    def __set_properties(self):
        self.combo_box_uncategorized_ignored.SetSelection(0)
        self.button_uncategorized_ignored_ignore.SetMinSize((110, 20))

        self.combo_box_physician.SetValue('DEFAULT')
        self.update_physician_rois()
        self.update_variations()

        self.combo_box_physician.SetValue('DEFAULT')
        self.combo_box_tree_plot_data.SetValue('ALL')

        self.update_uncategorized_ignored_choices()

        self.window_tree.SetBackgroundColour('white')

        for button in self.button_physician.values():
            button.SetMaxSize((25, 25))
        for button in self.button_physician_roi.values():
            button.SetMaxSize((25, 25))

        self.update_physician_enable()
        self.update_merge_physician_rois()

    def __do_bind(self):
        self.window_tree.Bind(wx.EVT_COMBOBOX, self.on_plot_data_type_change, id=self.combo_box_tree_plot_data.GetId())

        self.window_editor.Bind(wx.EVT_COMBOBOX, self.update_uncategorized_ignored_choices,
                                id=self.combo_box_uncategorized_ignored.GetId())

        self.window_editor.Bind(wx.EVT_COMBOBOX, self.physician_ticker, id=self.combo_box_physician.GetId())
        self.window_editor.Bind(wx.EVT_COMBOBOX, self.physician_roi_ticker, id=self.combo_box_physician_roi.GetId())
        self.window_editor.Bind(wx.EVT_COMBOBOX, self.uncategorized_ticker, id=self.combo_box_uncategorized_ignored.GetId())

        self.window_editor.Bind(wx.EVT_BUTTON, self.add_physician, id=self.button_physician['add'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_delete_physician, id=self.button_physician['del'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_edit_physician, id=self.button_physician['edit'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_link_physician_roi, id=self.button_link_physician_roi.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.add_physician_roi, id=self.button_physician_roi['add'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_delete_physician_roi, id=self.button_physician_roi['del'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_edit_physician_roi, id=self.button_physician_roi['edit'].GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.select_all_variations, id=self.button_variation_select_all.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.deselect_all_variations, id=self.button_variation_deselect_all.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.add_variation, id=self.button_variation_add.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.move_variations, id=self.button_variation_move.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.delete_variations, id=self.button_variation_delete.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_delete_dvh, id=self.button_uncategorized_ignored_delete.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_ignore_dvh, id=self.button_uncategorized_ignored_ignore.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_merge, id=self.button_merge.GetId())

        self.window_editor.Bind(wx.EVT_BUTTON, self.save_and_update, id=self.button_save_and_update.GetId())
        self.window_editor.Bind(wx.EVT_BUTTON, self.on_cancel, id=self.button_cancel.GetId())
        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.window_editor.Bind(wx.EVT_COMBOBOX, self.update_merge_enable,
                                id=self.combo_box_physician_roi_merge['a'].GetId())
        self.window_editor.Bind(wx.EVT_COMBOBOX, self.update_merge_enable,
                                id=self.combo_box_physician_roi_merge['b'].GetId())
        self.window_editor.Bind(wx.EVT_LIST_ITEM_SELECTED, self.update_button_variation_enable,
                                id=self.list_ctrl_variations.GetId())
        self.window_editor.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.update_button_variation_enable,
                                id=self.list_ctrl_variations.GetId())

    def __do_layout(self):

        sizer_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        sizer_editor = wx.BoxSizer(wx.VERTICAL)
        sizer_physician_roi_merger = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician_roi_merger_merge = wx.BoxSizer(wx.VERTICAL)
        sizer_physician_roi_b = wx.BoxSizer(wx.VERTICAL)
        sizer_physician_roi_a = wx.BoxSizer(wx.VERTICAL)
        sizer_uncategorized_ignored = wx.StaticBoxSizer(
            wx.StaticBox(self.window_editor, wx.ID_ANY, "Uncategorized / Ignored"), wx.HORIZONTAL)
        sizer_uncategorized_ignored_ignore = wx.BoxSizer(wx.VERTICAL)
        sizer_uncategorized_ignored_delete = wx.BoxSizer(wx.VERTICAL)
        sizer_uncategorized_ignored_roi = wx.BoxSizer(wx.VERTICAL)
        sizer_uncategorized_ignored_type = wx.BoxSizer(wx.VERTICAL)
        sizer_tree = wx.BoxSizer(wx.VERTICAL)
        sizer_tree_input = wx.BoxSizer(wx.HORIZONTAL)
        sizer_tree_plot_data = wx.BoxSizer(wx.VERTICAL)
        sizer_roi_manager = wx.BoxSizer(wx.VERTICAL)
        sizer_variation_buttons = wx.BoxSizer(wx.VERTICAL)
        sizer_variation_table = wx.BoxSizer(wx.VERTICAL)
        sizer_map_editor = wx.StaticBoxSizer(wx.StaticBox(self.window_editor, wx.ID_ANY, "ROI Map Editor"), wx.VERTICAL)
        sizer_variations = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician_roi = wx.BoxSizer(wx.VERTICAL)
        sizer_physician = wx.BoxSizer(wx.VERTICAL)
        sizer_physician_row = wx.BoxSizer(wx.HORIZONTAL)
        sizer_physician_roi_row = wx.BoxSizer(wx.HORIZONTAL)
        sizer_save_cancel_buttons = wx.BoxSizer(wx.HORIZONTAL)

        label_physician = wx.StaticText(self.window_editor, wx.ID_ANY, "Physician:")
        sizer_physician.Add(label_physician, 0, 0, 0)
        sizer_physician_row.Add(self.combo_box_physician, 1, wx.EXPAND | wx.RIGHT, 5)
        sizer_physician_row.Add(self.button_physician['add'], 0, wx.LEFT | wx.RIGHT, 5)
        sizer_physician_row.Add(self.button_physician['del'], 0, wx.RIGHT, 5)
        sizer_physician_row.Add(self.button_physician['edit'], 0, wx.RIGHT, 10)
        sizer_physician.Add(sizer_physician_row, 1, wx.EXPAND, 0)

        self.label_physician_roi = wx.StaticText(self.window_editor, wx.ID_ANY, "Institutional ROI:")
        sizer_physician_roi.Add(self.label_physician_roi, 0, 0, 0)
        sizer_physician_roi_row.Add(self.combo_box_physician_roi, 1, wx.EXPAND | wx.RIGHT, 5)
        sizer_physician_roi_row.Add(self.button_link_physician_roi, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
        sizer_physician_roi_row.Add(self.button_physician_roi['add'], 0, wx.LEFT | wx.RIGHT, 5)
        sizer_physician_roi_row.Add(self.button_physician_roi['del'], 0, wx.RIGHT, 5)
        sizer_physician_roi_row.Add(self.button_physician_roi['edit'], 0, wx.RIGHT, 10)
        sizer_physician_roi.Add(sizer_physician_roi_row, 0, wx.EXPAND, 0)

        sizer_map_editor.Add(sizer_physician, 0, wx.ALL | wx.EXPAND, 5)
        sizer_map_editor.Add(sizer_physician_roi, 0, wx.ALL | wx.EXPAND, 5)

        label_variations = wx.StaticText(self.window_editor, wx.ID_ANY, "Variations:")
        label_variations_buttons = wx.StaticText(self.window_editor, wx.ID_ANY, " ")
        sizer_variation_table.Add(label_variations, 0, 0, 0)
        sizer_variation_table.Add(self.list_ctrl_variations, 1, wx.BOTTOM | wx.EXPAND, 15)
        sizer_variations.Add(sizer_variation_table, 1, wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(label_variations_buttons, 0, 0, 0)
        sizer_variation_buttons.Add(self.button_variation_add, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
        sizer_variation_buttons.Add(self.button_variation_delete, 0, wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(self.button_variation_move, 0, wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(self.button_variation_select_all, 0, wx.EXPAND | wx.ALL, 5)
        sizer_variation_buttons.Add(self.button_variation_deselect_all, 0, wx.EXPAND | wx.ALL, 5)
        sizer_variations.Add(sizer_variation_buttons, 0, wx.EXPAND | wx.ALL, 5)

        sizer_map_editor.Add(sizer_variations, 0, wx.EXPAND, 0)

        label_physician_roi_a = wx.StaticText(self.window_editor, wx.ID_ANY, "Merge Physician ROI A:")
        sizer_physician_roi_a.Add(label_physician_roi_a, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
        sizer_physician_roi_a.Add(self.combo_box_physician_roi_merge['a'], 1,
                                  wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        sizer_physician_roi_merger.Add(sizer_physician_roi_a, 1, wx.EXPAND, 0)
        label_physician_roi_b = wx.StaticText(self.window_editor, wx.ID_ANY, "Into Physician ROI B:")
        sizer_physician_roi_b.Add(label_physician_roi_b, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
        sizer_physician_roi_b.Add(self.combo_box_physician_roi_merge['b'], 1,
                                  wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        sizer_physician_roi_merger.Add(sizer_physician_roi_b, 1, wx.EXPAND, 0)
        sizer_physician_roi_merger_merge.Add((20, 16), 0, 0, 0)
        sizer_physician_roi_merger_merge.Add(self.button_merge, 0, wx.ALL, 5)
        sizer_physician_roi_merger.Add(sizer_physician_roi_merger_merge, 0, wx.ALL | wx.EXPAND, 0)
        sizer_map_editor.Add(sizer_physician_roi_merger, 0, wx.EXPAND, 0)

        sizer_save_cancel_buttons.Add(self.button_save_and_update, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 40)
        sizer_save_cancel_buttons.Add(self.button_cancel, 1, wx.EXPAND | wx.LEFT| wx.RIGHT, 40)
        sizer_map_editor.Add((10, 10), 0, 0, 0)
        sizer_map_editor.Add(sizer_save_cancel_buttons, 0, wx.EXPAND | wx.ALL, 10)

        sizer_roi_manager.Add(sizer_map_editor, 1, wx.EXPAND, 0)
        sizer_editor.Add(sizer_roi_manager, 0, wx.EXPAND | wx.ALL, 5)

        label_tree_plot_data = wx.StaticText(self.window_tree, wx.ID_ANY, 'Institutional Data to Display:')
        sizer_tree_plot_data.Add(label_tree_plot_data, 0, wx.LEFT | wx.RIGHT | wx.TOP, 5)
        sizer_tree_plot_data.Add(self.combo_box_tree_plot_data, 0, wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, 5)

        sizer_tree_input.Add(sizer_tree_plot_data, 0, wx.EXPAND | wx.LEFT | wx.TOP, 5)

        sizer_tree.Add(sizer_tree_input, 0, wx.EXPAND, 0)
        sizer_tree.Add(self.plot.layout, 1, wx.EXPAND, 0)
        self.window_tree.SetSizer(sizer_tree)

        label_uncategorized_ignored = wx.StaticText(self.window_editor, wx.ID_ANY, "Type:")
        label_uncategorized_ignored.SetMinSize((38, 16))
        sizer_uncategorized_ignored_type.Add(label_uncategorized_ignored, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
        sizer_uncategorized_ignored_type.Add(self.combo_box_uncategorized_ignored, 1, wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        sizer_uncategorized_ignored.Add(sizer_uncategorized_ignored_type, 1, wx.EXPAND, 0)
        label_uncategorized_ignored_roi = wx.StaticText(self.window_editor, wx.ID_ANY, "ROI:")
        label_uncategorized_ignored_roi.SetMinSize((30, 16))
        sizer_uncategorized_ignored_roi.Add(label_uncategorized_ignored_roi, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5)
        sizer_uncategorized_ignored_roi.Add(self.combo_box_uncategorized_ignored_roi, 1, wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        sizer_uncategorized_ignored.Add(sizer_uncategorized_ignored_roi, 1, wx.EXPAND, 0)
        sizer_uncategorized_ignored_delete.Add((20, 16), 0, 0, 0)
        sizer_uncategorized_ignored_delete.Add(self.button_uncategorized_ignored_delete, 0, wx.ALL, 5)
        sizer_uncategorized_ignored.Add(sizer_uncategorized_ignored_delete, 0, wx.EXPAND, 0)
        sizer_uncategorized_ignored_ignore.Add((20, 16), 0, 0, 0)
        sizer_uncategorized_ignored_ignore.Add(self.button_uncategorized_ignored_ignore, 0, wx.ALL, 5)
        sizer_uncategorized_ignored.Add(sizer_uncategorized_ignored_ignore, 0, wx.EXPAND, 0)
        sizer_editor.Add(sizer_uncategorized_ignored, 0, wx.ALL | wx.EXPAND, 5)

        self.window_editor.SetSizer(sizer_editor)
        self.window.SplitVertically(self.window_tree, self.window_editor)
        self.window.SetSashPosition(int(self.window_size[0] * 0.55))
        sizer_wrapper.Add(self.window, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_wrapper)
        self.Layout()
        self.Centre()

    def run(self):
        self.Show()

    @staticmethod
    def update_combo_box_choices(combo_box, choices, value):
        if not value:
            value = combo_box.GetValue()
        combo_box.Clear()
        combo_box.AppendItems(choices)
        combo_box.SetValue(value)

    def update_roi_map(self):
        self.plot.update_roi_map_source_data(self.physician, plot_type=self.plot_data_type)

    @property
    def physician(self):
        return self.combo_box_physician.GetValue()

    @property
    def physician_roi(self):
        return self.combo_box_physician_roi.GetValue()

    @property
    def plot_data_type(self):
        return self.combo_box_tree_plot_data.GetValue()

    def physician_ticker(self, evt):
        self.update_physician_roi_label()
        self.update_physician_enable()
        self.update_all(skip_physicians=True)

    def on_plot_data_type_change(self, evt):
        self.update_roi_map()

    def update_uncategorized_ignored_choices(self, *args):
        ignored_variations = self.combo_box_uncategorized_ignored.GetValue() == 'Ignored'
        self.uncategorized_variations = self.get_uncategorized_variations(self.physician,
                                                                          ignored_variations=ignored_variations)
        choices = list(self.uncategorized_variations)
        choices.sort()
        if not choices:
            choices = ['None']
            self.button_uncategorized_ignored_delete.Disable()
            self.button_uncategorized_ignored_ignore.Disable()
        else:
            self.button_uncategorized_ignored_delete.Enable()
            self.button_uncategorized_ignored_ignore.Enable()
        self.combo_box_uncategorized_ignored_roi.Clear()
        self.combo_box_uncategorized_ignored_roi.Append(choices)
        self.combo_box_uncategorized_ignored_roi.SetValue(choices[0])

    @staticmethod
    def get_uncategorized_variations(physician, ignored_variations=False):
        if echo_sql_db():
            with DVH_SQL() as cnx:
                physician = clean_name(physician).upper()
                condition = "physician_roi = '%s'" % ['uncategorized', 'ignored'][ignored_variations]
                cursor_rtn = cnx.query('dvhs', 'roi_name, study_instance_uid', condition)
                new_variations = {}
                for row in cursor_rtn:
                    variation = clean_name(str(row[0]))
                    study_instance_uid = str(row[1])
                    physician_db = cnx.get_unique_values('Plans', 'physician',
                                                         "study_instance_uid = '%s'" % study_instance_uid)
                    if physician_db and physician_db[0] == physician:
                        if variation not in list(new_variations):
                            new_variations[variation] = {'roi_name': variation,
                                                         'study_instance_uid': [study_instance_uid]}
                        else:
                            new_variations[variation]['study_instance_uid'].append(study_instance_uid)
                return new_variations

    @property
    def selected_indices(self):
        return get_selected_listctrl_items(self.list_ctrl_variations)

    def update_button_variation_enable(self, *args):
        if self.selected_indices:
            self.button_variation_move.Enable()
            self.button_variation_delete.Enable()
            self.button_variation_deselect_all.Enable()
        else:
            self.button_variation_move.Disable()
            self.button_variation_delete.Disable()
            self.button_variation_deselect_all.Disable()

        self.button_variation_select_all.Enable(self.variation_count > 0)

    def update_variations(self):
        self.data_table.set_data(self.variation_table_data, self.columns)
        self.update_button_variation_enable()

    def physician_roi_ticker(self, evt):
        self.update_variations()

    def update_physicians(self, old_physicians=None):

        choices = self.roi_map.get_physicians()
        new = choices[0]
        if old_physicians:
            new = list(set(choices) - set(old_physicians))
            if new:
                new = clean_name(new[0]).upper()

        self.update_combo_box_choices(self.combo_box_physician, choices, new)
        self.update_physician_roi_label()
        self.update_physician_enable()

    def update_physician_rois(self, old_physician_rois=None):
        choices = self.roi_map.get_physician_rois(self.physician)
        new = choices[0]
        if old_physician_rois:
            new = list(set(choices) - set(old_physician_rois))
            if new:
                new = clean_name(new[0])

        self.update_combo_box_choices(self.combo_box_physician_roi, choices, new)

    @property
    def variations(self):
        variations = self.roi_map.get_variations(self.physician, self.physician_roi)
        variations = list(set(variations) - {self.physician_roi})  # remove physician roi
        variations.sort()
        return variations

    @property
    def variation_table_data(self):
        return {'Variations': self.variations}

    def add_physician_roi(self, evt):
        old_physician_rois = self.roi_map.get_physician_rois(self.physician)
        dlg = AddPhysicianROI(self, self.physician, self.roi_map, institutional_mode=self.physician == 'DEFAULT')
        if dlg.res == wx.ID_OK:
            self.update_all(old_physician_rois=old_physician_rois)

    def add_physician(self, evt):
        old_physicians = self.roi_map.get_physicians()
        dlg = AddPhysician(self.roi_map)
        if dlg.res == wx.ID_OK:
            self.update_all(old_physicians=old_physicians)

    @property
    def variation_count(self):
        return len(self.variations)

    @property
    def selected_values(self):
        return [self.list_ctrl_variations.GetItem(i, 0).GetText() for i in self.selected_indices]

    def select_all_variations(self, evt):
        self.apply_global_selection()

    def deselect_all_variations(self, evt):
        self.apply_global_selection(on=0)

    def apply_global_selection(self, on=1):
        for i in range(self.variation_count):
            self.list_ctrl_variations.Select(i, on=on)

    def delete_variations(self, evt):
        self.roi_map.delete_variations(self.physician, self.physician_roi, self.selected_values)
        self.update_variations()

    def add_variation(self, evt):
        dlg = AddVariationDialog(self, self.physician, self.physician_roi, self.roi_map)
        res = dlg.ShowModal()
        if res == wx.ID_OK:
            try:
                self.roi_map.add_variation(self.physician, self.physician_roi, dlg.text_ctrl_variation.GetValue())
                self.update_variations()
            except ROIVariationError as e:
                ROIVariationErrorDialog(self, e)
        dlg.Destroy()

    def move_variations(self, evt):
        choices = [roi for roi in self.roi_map.get_physician_rois(self.physician) if roi != self.physician_roi]
        MoveVariationDialog(self, self.selected_values, self.physician, self.physician_roi, choices, self.roi_map)
        self.update_variations()

    def on_delete_physician(self, evt):
        MessageDialog(self, 'Delete Physician %s?' % self.physician, action_yes_func=self.delete_physician)

    def delete_physician(self):
        self.roi_map.delete_physician(self.physician)
        self.update_all()

    def on_delete_physician_roi(self, evt):
        if self.physician == 'DEFAULT':
            MessageDialog(self, "Delete Institutional ROI %s?" % self.physician_roi,
                          action_yes_func=self.delete_institutional_roi)
        else:
            MessageDialog(self, "Delete Physician ROI %s?" % self.physician_roi,
                          action_yes_func=self.delete_physician_roi)

    def delete_physician_roi(self):
        self.roi_map.delete_physician_roi(self.physician, self.physician_roi)
        self.update_all(skip_physicians=True)

    def delete_institutional_roi(self):
        self.roi_map.delete_institutional_roi(self.physician_roi)
        self.update_all(skip_physicians=True)

    def on_delete_dvh(self, evt):
        MessageDialog(self, "Delete all DVHs named %s for %s?" % (self.dvh, self.physician),
                      message="Are you sure? This cannot be undone!",
                      action_yes_func=self.delete_dvh)

    def delete_dvh(self):
        with DVH_SQL() as cnx:
            for uid in self.dvh_uids:
                cnx.delete_dvh(self.dvh, uid)
        self.update_uncategorized_ignored_choices()

    def on_ignore_dvh(self, evt):
        msg_type = ['Unignore', 'Ignore'][self.button_uncategorized_ignored_ignore.GetLabelText() == 'Ignore DVH']
        MessageDialog(self, "%s all DVHs named %s for %s?" % (msg_type, self.dvh, self.physician),
                      action_yes_func=self.ignore_dvh)

    def ignore_dvh(self):
        unignore = self.button_uncategorized_ignored_ignore.GetLabelText() == 'Unignore DVH'
        with DVH_SQL() as cnx:
            for uid in self.dvh_uids:
                cnx.ignore_dvh(self.dvh, uid, unignore=unignore)
        self.update_uncategorized_ignored_choices()

    @property
    def dvh(self):
        return self.combo_box_uncategorized_ignored_roi.GetValue()

    @property
    def dvh_uids(self):
        return self.uncategorized_variations[self.dvh]['study_instance_uid']

    def update_all(self, old_physicians=None, old_physician_rois=None, skip_physicians=False):
        if not skip_physicians:
            self.update_physicians(old_physicians=old_physicians)
        self.update_physician_rois(old_physician_rois=old_physician_rois)
        self.update_variations()
        self.update_uncategorized_ignored_choices()
        self.update_merge_physician_rois()
        self.update_roi_map()

    def on_edit_physician(self, evt):
        current_physicians = self.roi_map.get_physicians()
        dlg = RenamePhysicianDialog(self.physician, self.roi_map)
        if dlg.res == wx.ID_OK:
            self.update_all(old_physicians=current_physicians)

    def on_edit_physician_roi(self, evt):
        current_physician_rois = self.roi_map.get_physician_rois(self.physician)
        if self.physician == 'DEFAULT':
            dlg = RenameInstitutionalROIDialog(self.physician_roi, self.roi_map)
        else:
            dlg = RenamePhysicianROIDialog(self.physician, self.physician_roi, self.roi_map)
        if dlg.res == wx.ID_OK:
            self.update_all(old_physician_rois=current_physician_rois, skip_physicians=True)

    def update_physician_enable(self):
        self.button_physician['del'].Enable(self.physician != 'DEFAULT')
        self.button_physician['edit'].Enable(self.physician != 'DEFAULT')
        self.button_variation_add.Enable(self.physician != 'DEFAULT')

    def update_physician_roi_label(self):
        label_text = ['Physician ROI:', 'Institutional ROI:'][self.physician == 'DEFAULT']
        self.label_physician_roi.SetLabelText(label_text)
        self.button_link_physician_roi.Enable(self.physician != 'DEFAULT')

    def uncategorized_ticker(self, evt):
        if self.combo_box_uncategorized_ignored.GetValue() == 'Uncategorized':
            self.button_uncategorized_ignored_ignore.SetLabelText('Ignore DVH')
        else:
            self.button_uncategorized_ignored_ignore.SetLabelText('Unignore DVH')
        self.update_uncategorized_ignored_choices()

    def update_merge_physician_rois(self):
        options = []
        if self.physician != 'DEFAULT':
            options = self.roi_map.get_physician_rois(self.physician)
        if not options:
            options = ['']
        for combo_box in self.combo_box_physician_roi_merge.values():
            combo_box.Clear()
            combo_box.Append(options)
            combo_box.SetValue(options[0])
        self.update_merge_enable()

    @property
    def merge_a(self):
        return self.combo_box_physician_roi_merge['a'].GetValue()

    @property
    def merge_b(self):
        return self.combo_box_physician_roi_merge['b'].GetValue()

    def on_merge(self, evt):
        self.roi_map.merge_physician_rois(self.physician, [self.merge_a, self.merge_b], self.merge_b)
        self.update_all(skip_physicians=True)

    def update_merge_enable(self, *args):  # *args to catch wx.EVT_BUTTON
        self.combo_box_physician_roi_merge['a'].Enable(bool(self.merge_a and self.merge_b))
        self.combo_box_physician_roi_merge['b'].Enable(bool(self.merge_a and self.merge_b))
        self.button_merge.Enable(bool(self.merge_a) and bool(self.merge_b) and self.merge_a != self.merge_b)

    def on_link_physician_roi(self, evt):
        dlg = LinkPhysicianROI(self, self.physician, self.physician_roi, self.roi_map)
        if dlg.res == wx.ID_OK:
            self.update_roi_map()

    def save_and_update(self, evt):
        RemapROIFrame(self.roi_map)
        self.Destroy()

    def on_cancel(self, *args):
        self.roi_map.import_from_file()
        self.update_roi_map()

    def on_close(self, *args):
        self.Destroy()
        self.roi_map.import_from_file()
示例#19
0
class ExportCSVDialog(wx.Dialog):
    """
    Allow user to select available data for export to CSV into one file.
    This class leverages the get_csv functions built-into each of the data types / tabs
    """
    def __init__(self, app):
        """
        :param app: easier to pass main frame pointer than several links to each data type
        :type app: DVHAMainFrame
        """
        wx.Dialog.__init__(self, None)

        self.app = app

        # Each of these objects shoudl have a has_data property, if False, those UI elements in this class will
        # be disabled (i.e., don't allow user to export empty tables
        self.enabled = {
            'DVHs': self.app.dvh.has_data,
            'DVHs Summary': self.app.dvh.has_data,
            'Endpoints': self.app.endpoint.has_data,
            'Radbio': self.app.radbio.has_data,
            'Charting Variables': self.app.time_series.has_data
        }

        checkbox_keys = [
            'DVHs', 'DVHs Summary', 'Endpoints', 'Radbio', 'Charting Variables'
        ]
        self.checkbox = {
            key: wx.CheckBox(self, wx.ID_ANY, key)
            for key in checkbox_keys
        }

        # set to a dictionary because previous versions had a tree with Regression
        self.list_ctrl = {
            'Charting Variables':
            wx.ListCtrl(self,
                        wx.ID_ANY,
                        style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        }

        time_series_column = "Variables"
        time_series_variables = self.app.time_series.combo_box_y_axis.GetItems(
        )
        time_series_data = {time_series_column: time_series_variables}
        self.data_table_time_series = DataTable(
            self.list_ctrl['Charting Variables'],
            columns=[time_series_column],
            widths=[400])
        self.data_table_time_series.set_data(time_series_data,
                                             [time_series_column])

        # set to a dictionary because previous versions had a table with Regression
        self.button_select_data = {
            'Charting Variables': {
                'Select': wx.Button(self, wx.ID_ANY, "Select All"),
                'Deselect': wx.Button(self, wx.ID_ANY, "Deselect All")
            }
        }

        self.button_save = wx.Button(self, wx.ID_OK, "Save")
        self.button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.button_select_all = wx.Button(self, wx.ID_ANY, 'Select All')
        self.button_deselect_all = wx.Button(self, wx.ID_ANY, 'Deselect All')

        self.__set_properties()
        self.__do_bind()
        self.__do_layout()

        self.run()

    def __set_properties(self):
        self.SetTitle("Export Data to CSV")
        self.button_select_all.SetToolTip(
            'Only data objects with data will be enabled.')
        self.validate_ui_objects()

    def __do_bind(self):
        self.Bind(wx.EVT_BUTTON,
                  self.on_select_all,
                  id=self.button_select_all.GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.on_deselect_all,
                  id=self.button_deselect_all.GetId())
        self.Bind(
            wx.EVT_BUTTON,
            self.on_time_series_select_all,
            id=self.button_select_data['Charting Variables']['Select'].GetId())
        self.Bind(wx.EVT_BUTTON,
                  self.on_time_series_deselect_all,
                  id=self.button_select_data['Charting Variables']
                  ['Deselect'].GetId())

    def __do_layout(self):
        sizer_wrapper = wx.BoxSizer(wx.VERTICAL)
        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_main_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_data = wx.StaticBoxSizer(
            wx.StaticBox(self, wx.ID_ANY, "Data Selection"), wx.VERTICAL)
        sizer_time_series = wx.BoxSizer(wx.VERTICAL)
        sizer_time_series_listctrl = wx.BoxSizer(wx.HORIZONTAL)
        sizer_time_series_checkboxes = wx.BoxSizer(wx.HORIZONTAL)
        sizer_time_series_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_radbio = wx.BoxSizer(wx.VERTICAL)
        sizer_endpoints = wx.BoxSizer(wx.VERTICAL)
        sizer_dvhs = wx.BoxSizer(wx.VERTICAL)
        sizer_dvhs_checkboxes = wx.BoxSizer(wx.HORIZONTAL)

        keys = ['DVHs', 'Endpoints', 'Radbio']
        static_line = {key: wx.StaticLine(self, wx.ID_ANY) for key in keys}

        sizer_dvhs_checkboxes.Add(self.checkbox['DVHs'], 1, wx.ALL | wx.EXPAND,
                                  5)
        sizer_dvhs_checkboxes.Add(self.checkbox['DVHs Summary'], 1,
                                  wx.ALL | wx.EXPAND, 5)
        sizer_dvhs.Add(sizer_dvhs_checkboxes, 1, wx.EXPAND, 0)
        sizer_dvhs.Add(static_line['DVHs'], 0, wx.EXPAND | wx.TOP, 5)
        sizer_data.Add(sizer_dvhs, 0, wx.ALL | wx.EXPAND, 5)

        sizer_endpoints.Add(self.checkbox['Endpoints'], 0, wx.ALL, 5)
        sizer_endpoints.Add(static_line['Endpoints'], 0, wx.EXPAND | wx.TOP, 5)
        sizer_data.Add(sizer_endpoints, 0, wx.ALL | wx.EXPAND, 5)

        sizer_radbio.Add(self.checkbox['Radbio'], 0, wx.ALL, 5)
        sizer_radbio.Add(static_line['Radbio'], 0, wx.EXPAND | wx.TOP, 5)
        sizer_data.Add(sizer_radbio, 0, wx.ALL | wx.EXPAND, 5)

        sizer_time_series_checkboxes.Add(self.checkbox['Charting Variables'],
                                         1, wx.EXPAND, 0)
        sizer_time_series_buttons.Add(
            self.button_select_data['Charting Variables']['Select'], 0,
            wx.ALL | wx.EXPAND, 5)
        sizer_time_series_buttons.Add(
            self.button_select_data['Charting Variables']['Deselect'], 0,
            wx.ALL | wx.EXPAND, 5)
        sizer_time_series_checkboxes.Add(sizer_time_series_buttons, 1,
                                         wx.EXPAND, 0)
        sizer_time_series.Add(sizer_time_series_checkboxes, 0,
                              wx.ALL | wx.EXPAND, 5)
        sizer_time_series_listctrl.Add((20, 20), 0, 0, 0)
        sizer_time_series_listctrl.Add(self.list_ctrl['Charting Variables'], 1,
                                       wx.ALL | wx.EXPAND, 5)
        sizer_time_series.Add(sizer_time_series_listctrl, 0,
                              wx.ALL | wx.EXPAND, 5)
        sizer_data.Add(sizer_time_series, 0, wx.ALL | wx.EXPAND, 5)

        sizer_main.Add(sizer_data, 0, wx.ALL | wx.EXPAND, 5)

        sizer_main_buttons.Add(self.button_select_all, 0, wx.ALL, 5)
        sizer_main_buttons.Add(self.button_deselect_all, 0, wx.ALL, 5)

        sizer_main_buttons.Add(self.button_save, 0, wx.ALL, 5)
        sizer_main_buttons.Add(self.button_cancel, 0, wx.ALL, 5)
        sizer_main.Add(sizer_main_buttons, 0, wx.ALIGN_RIGHT | wx.ALL, 5)

        sizer_wrapper.Add(sizer_main, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_wrapper)
        self.Layout()
        self.Fit()
        self.Center()

    def set_checkbox_values(self, value):
        for checkbox in self.checkbox.values():
            checkbox.SetValue(value)

    def on_select_all(self, evt):
        self.set_checkbox_values(True)
        self.validate_ui_objects(allow_enable=False)

    def on_deselect_all(self, evt):
        self.set_checkbox_values(False)
        self.validate_ui_objects(allow_enable=False)

    def on_time_series_select_all(self, evt):
        self.data_table_time_series.apply_selection_to_all(1)

    def on_time_series_deselect_all(self, evt):
        self.data_table_time_series.apply_selection_to_all(0)

    def validate_ui_objects(self, allow_enable=True):
        tables = {'Charting Variables': self.data_table_time_series}
        for key, data_table in tables.items():
            state = data_table.has_data
            if not state or (state and allow_enable):
                self.list_ctrl[key].Enable(state)
                self.button_select_data[key]['Select'].Enable(state)
                self.button_select_data[key]['Deselect'].Enable(state)

        for key, value in self.enabled.items():
            if not value or (value and allow_enable):
                self.checkbox[key].SetValue(value)
                self.checkbox[key].Enable(value)

    def run(self):
        res = self.ShowModal()
        if res == wx.ID_OK:
            save_data_to_file(self, 'Export CSV Data', self.csv)
        self.Destroy()

    def is_checked(self, key):
        return self.checkbox[key].GetValue()

    def on_dvh_check(self, evt):
        if not self.is_checked('DVHs'):
            self.checkbox['DVHs Summary'].SetValue(False)

    @property
    def csv(self):
        csv_data = []

        csv_key = ['DVHs', 'Endpoints', 'Radbio', 'Charting Variables']
        csv_obj = [
            None, self.app.endpoint, self.app.radbio, self.app.time_series,
            self.app.control_chart
        ]
        for i, key in enumerate(csv_key):
            if self.is_checked(key):
                csv_data.append('%s\n' % key)
                if key == 'DVHs':  # DVHs has a summary and plot data for export
                    csv_data.append(
                        self.app.plot.get_csv(
                            include_summary=self.is_checked('DVHs Summary'),
                            include_dvhs=self.is_checked('DVHs')))
                else:
                    if key == 'Charting Variables':
                        selection_indices = get_selected_listctrl_items(
                            self.list_ctrl['Charting Variables'])
                        y_choices = self.app.time_series.combo_box_y_axis.GetItems(
                        )
                        selection = [
                            y for i, y in enumerate(y_choices)
                            if i in selection_indices
                        ]
                    else:
                        selection = None
                    csv_data.append(csv_obj[i].get_csv(selection=selection))
                csv_data.append('\n\n')

        return '\n'.join(csv_data)
示例#20
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)
示例#21
0
    def __init__(self, parent, group_data, time_series, regression,
                 control_chart):
        """
        :param parent:  notebook panel in main view
        :type parent: Panel
        :param group_data: dvh, table_data, and stats_data
        :type group_data: dict
        :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.group_data = group_data
        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.radio_box_query_group = wx.RadioBox(self.parent,
                                                 wx.ID_ANY,
                                                 'Query Group',
                                                 choices=['1', '2', 'Both'])
        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 = {
            grp: wx.ListCtrl(self.parent,
                             wx.ID_ANY,
                             style=wx.BORDER_SUNKEN | wx.LC_HRULES
                             | wx.LC_REPORT | wx.LC_VRULES)
            for grp in [1, 2]
        }
        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 = {
            grp: DataTable(self.table_rad_bio[grp],
                           columns=self.columns,
                           widths=self.width,
                           formats=formats)
            for grp in [1, 2]
        }

        self.__set_properties()
        self.__do_layout()
        self.__do_bind()

        self.disable_buttons()
示例#22
0
class DVHAMainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.layout_set = False

        self.sizer_dvhs = wx.BoxSizer(wx.VERTICAL)

        set_msw_background_color(self)  # If windows, change the background color

        self.options = Options()

        # Initial DVH object and data
        self.save_data = {}
        self.group_data = {1: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None},
                           2: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None}}

        self.toolbar_keys = ['Open', 'Close', 'Save', 'Export', 'Import', 'Database', 'ROI Map', 'Settings']
        self.toolbar_ids = {key: i + 1000 for i, key in enumerate(self.toolbar_keys)}

        # sql_columns.py contains dictionaries of all queryable variables along with their
        # SQL columns and tables. Numerical categories include their units as well.
        self.categorical_columns = sql_columns.categorical
        self.numerical_columns = sql_columns.numerical

        # Keep track of currently selected row in the query tables
        self.selected_index_categorical = None
        self.selected_index_numerical = None

        # Load ROI Map now and pass to other objects for continuity
        # TODO: Need a method to address multiple users editing roi_map at the same time
        self.roi_map = DatabaseROIs()

        self.query_filters = None
        self.reset_query_filters()

        self.__add_menubar()
        self.__add_tool_bar()
        self.__add_layout_objects()
        self.__bind_layout_objects()

        columns = {'categorical': ['category_1', 'category_2', 'Filter Type'],
                   'numerical': ['category', 'min', 'max', 'Filter Type']}
        self.data_table_categorical = DataTable(self.table_categorical, columns=columns['categorical'])
        self.data_table_numerical = DataTable(self.table_numerical, columns=columns['numerical'])

        self.__set_properties()
        self.__set_tooltips()
        self.__add_notebook_frames()
        self.__do_layout()

        self.disable_query_buttons('categorical')
        self.disable_query_buttons('numerical')
        self.button_query_execute.Disable()
        self.__disable_notebook_tabs()

        self.Bind(wx.EVT_CLOSE, self.on_quit)
        self.tool_bar_windows = {key: None for key in ['import', 'database', 'roi_map']}

        wx.CallAfter(self.__catch_failed_sql_connection_on_app_launch)

        self.__do_subscribe()

    def __add_tool_bar(self):
        self.frame_toolbar = wx.ToolBar(self, -1, style=wx.TB_HORIZONTAL | wx.TB_TEXT)
        self.SetToolBar(self.frame_toolbar)

        description = {'Open': "Open previously queried data",
                       'Close': "Clear queried data",
                       'Save': "Save queried data",
                       # 'Print': "Print a report",
                       'Export': "Export data to CSV",
                       'Import': "DICOM import wizard",
                       'Settings': "User Settings",
                       'Database': "Database Administrator Tools",
                       'ROI Map': "Define ROI name aliases"}

        for key in self.toolbar_keys:
            bitmap = wx.Bitmap(ICONS[key], wx.BITMAP_TYPE_ANY)
            if is_windows() or is_linux():
                bitmap = scale_bitmap(bitmap, 30, 30)
            self.frame_toolbar.AddTool(self.toolbar_ids[key], key, bitmap,
                                       wx.NullBitmap, wx.ITEM_NORMAL, description[key], "")

            if key in {'Close', 'Export', 'ROI Map'}:
                self.frame_toolbar.AddSeparator()

        self.Bind(wx.EVT_TOOL, self.on_save, id=self.toolbar_ids['Save'])
        self.Bind(wx.EVT_TOOL, self.on_open, id=self.toolbar_ids['Open'])
        self.Bind(wx.EVT_TOOL, self.on_export, id=self.toolbar_ids['Export'])
        self.Bind(wx.EVT_TOOL, self.on_toolbar_database, id=self.toolbar_ids['Database'])
        self.Bind(wx.EVT_TOOL, self.on_toolbar_settings, id=self.toolbar_ids['Settings'])
        self.Bind(wx.EVT_TOOL, self.on_toolbar_roi_map, id=self.toolbar_ids['ROI Map'])
        self.Bind(wx.EVT_TOOL, self.on_close, id=self.toolbar_ids['Close'])
        self.Bind(wx.EVT_TOOL, self.on_toolbar_import, id=self.toolbar_ids['Import'])

    def __add_menubar(self):

        self.frame_menubar = wx.MenuBar()

        file_menu = wx.Menu()
        # file_menu.Append(wx.ID_NEW, '&New')
        menu_open = file_menu.Append(wx.ID_OPEN, '&Open\tCtrl+O')
        menu_import = file_menu.Append(wx.ID_OPEN, '&Import DICOM\tCtrl+I')
        menu_save = file_menu.Append(wx.ID_ANY, '&Save\tCtrl+S')
        menu_close = file_menu.Append(wx.ID_ANY, '&Close')

        load_model = wx.Menu()
        load_model_mvr = load_model.Append(wx.ID_ANY, 'Multi-Variable Regression')
        load_model_ml = load_model.Append(wx.ID_ANY, 'Machine Learning')

        export_plot = wx.Menu()
        export_dvhs = export_plot.Append(wx.ID_ANY, 'DVHs')
        export_time_series = export_plot.Append(wx.ID_ANY, 'Time Series')
        export_correlation = export_plot.Append(wx.ID_ANY, 'Correlation')
        export_regression = export_plot.Append(wx.ID_ANY, 'Regression')
        export_control_chart = export_plot.Append(wx.ID_ANY, 'Control Chart')

        export = wx.Menu()
        export_csv = export.Append(wx.ID_ANY, 'Data to csv\tCtrl+E')
        export.AppendSubMenu(export_plot, 'Plot to html')
        file_menu.AppendSeparator()

        qmi = file_menu.Append(wx.ID_ANY, '&Quit\tCtrl+Q')

        self.data_menu = wx.Menu()

        self.data_views = {key: None for key in ['DVHs', 'Plans', 'Rxs', 'Beams']}
        menu_db_admin = self.data_menu.Append(wx.ID_ANY, 'Database Administrator')
        self.data_menu.AppendSubMenu(load_model, 'Load &Model')
        self.data_menu.AppendSubMenu(export, '&Export')
        self.data_menu_items = {'DVHs': self.data_menu.Append(wx.ID_ANY, 'Show DVHs\tCtrl+1'),
                                'Plans': self.data_menu.Append(wx.ID_ANY, 'Show Plans\tCtrl+2'),
                                'Rxs': self.data_menu.Append(wx.ID_ANY, 'Show Rxs\tCtrl+3'),
                                'Beams': self.data_menu.Append(wx.ID_ANY, 'Show Beams\tCtrl+4'),
                                'StatsData1': self.data_menu.Append(wx.ID_ANY, 'Show Stats Data: Group 1\tCtrl+5'),
                                'StatsData2': self.data_menu.Append(wx.ID_ANY, 'Show Stats Data: Group 2\tCtrl+6')}

        settings_menu = wx.Menu()
        menu_pref = settings_menu.Append(wx.ID_PREFERENCES)
        menu_sql = settings_menu.Append(wx.ID_ANY, '&Database Connection\tCtrl+D')
        menu_roi_map = settings_menu.Append(wx.ID_ANY, '&ROI Map\tCtrl+R')

        help_menu = wx.Menu()
        menu_github = help_menu.Append(wx.ID_ANY, 'GitHub Page')
        menu_report_issue = help_menu.Append(wx.ID_ANY, 'Report an Issue')
        menu_about = help_menu.Append(wx.ID_ANY, '&About')

        self.Bind(wx.EVT_MENU, self.on_quit, qmi)
        self.Bind(wx.EVT_MENU, self.on_open, menu_open)
        self.Bind(wx.EVT_MENU, self.on_toolbar_import, menu_import)
        self.Bind(wx.EVT_MENU, self.on_load_mvr_model, load_model_mvr)
        self.Bind(wx.EVT_MENU, self.on_load_ml_model, load_model_ml)
        self.Bind(wx.EVT_MENU, self.on_close, menu_close)
        self.Bind(wx.EVT_MENU, self.on_export, export_csv)
        self.Bind(wx.EVT_MENU, self.on_save, menu_save)
        self.Bind(wx.EVT_MENU, self.on_pref, menu_pref)
        self.Bind(wx.EVT_MENU, self.on_githubpage, menu_github)
        self.Bind(wx.EVT_MENU, self.on_report_issue, menu_report_issue)
        self.Bind(wx.EVT_MENU, self.on_about, menu_about)
        self.Bind(wx.EVT_MENU, self.on_sql, menu_sql)
        self.Bind(wx.EVT_MENU, self.on_toolbar_roi_map, menu_roi_map)
        if is_mac():
            menu_user_settings = settings_menu.Append(wx.ID_ANY, '&Preferences\tCtrl+,')
            self.Bind(wx.EVT_MENU, self.on_pref, menu_user_settings)

        self.Bind(wx.EVT_MENU, self.on_save_plot_dvhs, export_dvhs)
        self.Bind(wx.EVT_MENU, self.on_save_plot_time_series, export_time_series)
        self.Bind(wx.EVT_MENU, self.on_save_plot_correlation, export_correlation)
        self.Bind(wx.EVT_MENU, self.on_save_plot_regression, export_regression)
        self.Bind(wx.EVT_MENU, self.on_save_plot_control_chart, export_control_chart)
        self.Bind(wx.EVT_MENU, self.on_toolbar_database, menu_db_admin)
        self.Bind(wx.EVT_MENU, self.on_view_dvhs, self.data_menu_items['DVHs'])
        self.Bind(wx.EVT_MENU, self.on_view_plans, self.data_menu_items['Plans'])
        self.Bind(wx.EVT_MENU, self.on_view_rxs, self.data_menu_items['Rxs'])
        self.Bind(wx.EVT_MENU, self.on_view_beams, self.data_menu_items['Beams'])
        self.Bind(wx.EVT_MENU, self.on_view_stats_data_1, self.data_menu_items['StatsData1'])
        self.Bind(wx.EVT_MENU, self.on_view_stats_data_2, self.data_menu_items['StatsData2'])

        self.frame_menubar.Append(file_menu, '&File')
        self.frame_menubar.Append(self.data_menu, '&Data')
        self.frame_menubar.Append(settings_menu, '&Settings')
        self.frame_menubar.Append(help_menu, '&Help')
        self.SetMenuBar(self.frame_menubar)

    def __add_layout_objects(self):
        self.button_categorical = {'add': wx.Button(self, wx.ID_ANY, "Add Filter"),
                                   'del': wx.Button(self, wx.ID_ANY, "Delete Selected"),
                                   'edit': wx.Button(self, wx.ID_ANY, "Edit Selected")}
        self.button_numerical = {'add': wx.Button(self, wx.ID_ANY, "Add Filter"),
                                 'del': wx.Button(self, wx.ID_ANY, "Delete Selected"),
                                 'edit': wx.Button(self, wx.ID_ANY, "Edit Selected")}

        self.table_categorical = wx.ListCtrl(self, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_REPORT)
        self.table_numerical = wx.ListCtrl(self, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_REPORT)

        self.radio_button_query_group = wx.RadioBox(self, wx.ID_ANY, 'Query Group', choices=['1', '2'])
        self.button_query_execute = wx.Button(self, wx.ID_ANY, "Query and Retrieve Group 1")

        self.notebook_main_view = wx.Notebook(self, wx.ID_ANY)
        self.tab_keys = ['Welcome', 'DVHs', 'Endpoints', 'Rad Bio', 'Time Series',
                         'Correlation', 'Regression', 'Control Chart']
        self.notebook_tab = {key: wx.Panel(self.notebook_main_view, wx.ID_ANY) for key in self.tab_keys}

        self.text_summary = wx.StaticText(self, wx.ID_ANY, "", style=wx.ALIGN_LEFT)

    def __bind_layout_objects(self):
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_item_select_categorical, self.table_categorical)
        self.Bind(wx.EVT_BUTTON, self.add_row_categorical, id=self.button_categorical['add'].GetId())
        self.Bind(wx.EVT_BUTTON, self.del_row_categorical, id=self.button_categorical['del'].GetId())
        self.Bind(wx.EVT_BUTTON, self.edit_row_categorical, id=self.button_categorical['edit'].GetId())
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.doubleclick_categorical, self.table_categorical)

        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_item_select_numerical, self.table_numerical)
        self.Bind(wx.EVT_BUTTON, self.add_row_numerical, id=self.button_numerical['add'].GetId())
        self.Bind(wx.EVT_BUTTON, self.del_row_numerical, id=self.button_numerical['del'].GetId())
        self.Bind(wx.EVT_BUTTON, self.edit_row_numerical, id=self.button_numerical['edit'].GetId())
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.doubleclick_numerical, self.table_numerical)

        self.Bind(wx.EVT_RADIOBOX, self.on_group_select, id=self.radio_button_query_group.GetId())
        self.Bind(wx.EVT_BUTTON, self.exec_query_button, id=self.button_query_execute.GetId())

        self.Bind(wx.EVT_SIZE, self.on_resize)

    def __set_properties(self):
        self.SetTitle("DVH Analytics")

        self.frame_toolbar.Realize()

        widths = [180, 150, 80]
        self.table_categorical.AppendColumn("Category1", format=wx.LIST_FORMAT_LEFT, width=widths[0])
        self.table_categorical.AppendColumn("Category2", format=wx.LIST_FORMAT_LEFT, width=widths[1])
        self.table_categorical.AppendColumn("Filter Type", format=wx.LIST_FORMAT_LEFT, width=widths[2])
        self.data_table_categorical.widths = widths

        widths = [150, 90, 90, 80]
        self.table_numerical.AppendColumn("Category", format=wx.LIST_FORMAT_LEFT, width=widths[0])
        self.table_numerical.AppendColumn("Min", format=wx.LIST_FORMAT_LEFT, width=widths[1])
        self.table_numerical.AppendColumn("Max", format=wx.LIST_FORMAT_LEFT, width=widths[2])
        self.table_numerical.AppendColumn("Filter Type", format=wx.LIST_FORMAT_LEFT, width=widths[3])
        self.data_table_numerical.widths = widths

    def __set_tooltips(self):
        self.button_categorical['add'].SetToolTip("Add a categorical data filter.")
        self.button_categorical['del'].SetToolTip("Delete the currently selected category filter.")
        self.button_categorical['edit'].SetToolTip("Edit the currently selected category filter.")
        self.button_numerical['add'].SetToolTip("Add a numerical data filter.")
        self.button_numerical['del'].SetToolTip("Delete the currently selected numerical data filter.")
        self.button_numerical['edit'].SetToolTip("Edit the currently selected data filter.")
        self.button_query_execute.SetToolTip("Query the database with the filters entered above. At least one "
                                             "filter must be added.")

    def __add_notebook_frames(self):
        self.plot = PlotStatDVH(self.notebook_tab['DVHs'], self.group_data, self.options)
        self.time_series = TimeSeriesFrame(self.notebook_tab['Time Series'], self.group_data, self.options)
        self.correlation = CorrelationFrame(self.notebook_tab['Correlation'], self.group_data, self.options)
        self.regression = RegressionFrame(self.notebook_tab['Regression'], self.group_data, self.options)
        self.control_chart = ControlChartFrame(self.notebook_tab['Control Chart'], self.group_data, self.options)
        self.radbio = RadBioFrame(self.notebook_tab['Rad Bio'], self.group_data, self.time_series, self.regression,
                                  self.control_chart)
        self.endpoint = EndpointFrame(self.notebook_tab['Endpoints'], self.group_data, self.time_series, self.regression,
                                      self.control_chart)

    def __do_layout(self):
        sizer_summary = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Summary"), wx.HORIZONTAL)
        sizer_query_numerical = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Query by Numerical Data"),
                                                  wx.VERTICAL)

        sizer_query_categorical = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Query by Categorical Data"),
                                                    wx.VERTICAL)

        sizer_categorical_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_numerical_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_query_exec_buttons = wx.BoxSizer(wx.HORIZONTAL)

        for key in ['add', 'del', 'edit']:
            sizer_categorical_buttons.Add(self.button_categorical[key], 0, wx.ALL, 5)
            sizer_numerical_buttons.Add(self.button_numerical[key], 0, wx.ALL, 5)

        sizer_query_categorical.Add(sizer_categorical_buttons, 0, wx.ALL | wx.EXPAND, 5)
        sizer_query_categorical.Add(self.table_categorical, 1, wx.ALL | wx.EXPAND, 10)

        sizer_query_numerical.Add(sizer_numerical_buttons, 0, wx.ALL | wx.EXPAND, 5)
        sizer_query_numerical.Add(self.table_numerical, 1, wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, 10)

        sizer_summary.Add(self.text_summary)

        panel_left = wx.BoxSizer(wx.VERTICAL)
        panel_left.Add(sizer_query_categorical, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED | wx.TOP, 5)
        panel_left.Add(sizer_query_numerical, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED, 5)
        sizer_query_exec_buttons.Add(self.radio_button_query_group, 0, 0, 0)
        sizer_query_exec_buttons.Add(self.button_query_execute, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER, 10)
        panel_left.Add(sizer_query_exec_buttons, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.RIGHT | wx.LEFT, 5)
        panel_left.Add(sizer_summary, 1, wx.ALL | wx.EXPAND, 5)

        bitmap_logo = wx.StaticBitmap(self.notebook_tab['Welcome'], wx.ID_ANY,
                                      wx.Bitmap(LOGO_PATH, wx.BITMAP_TYPE_ANY))
        text_welcome = wx.StaticText(self.notebook_tab['Welcome'], wx.ID_ANY,
                                     "\n\nWelcome to DVH Analytics.\nIf you already have a database built, design "
                                     "a query with the filters to the left.\n\n\n\nDVH Analytics is a software "
                                     "application to help radiation oncology departments build an in-house database "
                                     "of treatment planning data for the purpose of historical comparisons and "
                                     "statistical analysis. This code is still in development. Please contact the "
                                     "developer if you are interested in testing or collaborating.\n\nThe application "
                                     "builds a SQL database of DVHs and various planning parameters from DICOM files "
                                     "(i.e., Plan, Structure, Dose). Since the data is extracted directly from DICOM "
                                     "files, we intend to accommodate an array of treatment planning system vendors.",
                                     style=wx.ALIGN_CENTER)

        text_welcome_size = get_window_size(0.417, 0.476)
        text_welcome.SetMinSize(text_welcome_size)
        text_welcome.Wrap(text_welcome_size[1])

        sizer_welcome = wx.BoxSizer(wx.VERTICAL)
        sizer_welcome.Add(bitmap_logo, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP, 100)
        sizer_welcome.Add(text_welcome, 0, wx.ALIGN_CENTER | wx.ALL, 25)
        self.notebook_tab['Welcome'].SetSizer(sizer_welcome)

        self.sizer_dvhs.Add(self.plot.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['DVHs'].SetSizer(self.sizer_dvhs)

        sizer_endpoint = wx.BoxSizer(wx.VERTICAL)
        sizer_endpoint.Add(self.endpoint.layout, 0, wx.ALL, 25)
        self.notebook_tab['Endpoints'].SetSizer(sizer_endpoint)

        sizer_rad_bio = wx.BoxSizer(wx.VERTICAL)
        sizer_rad_bio.Add(self.radbio.layout, 0, wx.ALL, 25)
        self.notebook_tab['Rad Bio'].SetSizer(sizer_rad_bio)

        sizer_time_series = wx.BoxSizer(wx.VERTICAL)
        sizer_time_series.Add(self.time_series.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Time Series'].SetSizer(sizer_time_series)

        sizer_correlation = wx.BoxSizer(wx.VERTICAL)
        sizer_correlation.Add(self.correlation.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Correlation'].SetSizer(sizer_correlation)

        sizer_regression = wx.BoxSizer(wx.VERTICAL)
        sizer_regression.Add(self.regression.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Regression'].SetSizer(sizer_regression)

        sizer_control_chart = wx.BoxSizer(wx.VERTICAL)
        sizer_control_chart.Add(self.control_chart.layout, 1, wx.EXPAND | wx.ALL, 25)
        self.notebook_tab['Control Chart'].SetSizer(sizer_control_chart)

        for key in self.tab_keys:
            self.notebook_main_view.AddPage(self.notebook_tab[key], key)

        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_main_wrapper = wx.BoxSizer(wx.HORIZONTAL)
        hbox_main = wx.BoxSizer(wx.HORIZONTAL)
        hbox_main.Add(panel_left, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.TOP, 5)
        hbox_main.Add(self.notebook_main_view, 1, wx.BOTTOM | wx.EXPAND | wx.RIGHT | wx.TOP, 5)
        sizer_main_wrapper.Add(hbox_main, 1, wx.EXPAND, 0)
        sizer_main.Add(sizer_main_wrapper, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_main)
        self.Layout()

        self.SetSize(get_window_size(0.833, 0.857))

        self.Center()

    def __enable_notebook_tabs(self):
        for key in self.tab_keys:
            self.notebook_tab[key].Enable()
        self.__enable_initial_buttons_in_tabs()

    def __disable_notebook_tabs(self):
        for key in self.tab_keys:
            if key != 'Welcome':
                self.notebook_tab[key].Disable()

    def __enable_initial_buttons_in_tabs(self):
        self.endpoint.enable_initial_buttons()
        self.radbio.enable_initial_buttons()
        self.time_series.enable_initial_buttons()

    def __disable_add_filter_buttons(self):
        self.button_categorical['add'].Disable()
        self.button_numerical['add'].Disable()

    def __enable_add_filter_buttons(self):
        self.button_categorical['add'].Enable()
        self.button_numerical['add'].Enable()

    def enable_query_buttons(self, query_type):
        for key in ['del', 'edit']:
            {'categorical': self.button_categorical[key],
             'numerical': self.button_numerical[key]}[query_type].Enable()

    def disable_query_buttons(self, query_type):
        for key in ['del', 'edit']:
            {'categorical': self.button_categorical[key],
             'numerical': self.button_numerical[key]}[query_type].Disable()

    def update_all_query_buttons(self):
        tables = {'numerical': self.data_table_numerical, 'categorical': self.data_table_categorical}
        for key, table in tables.items():
            if table.data is not None:
                [self.disable_query_buttons, self.enable_query_buttons][table.row_count > 0](key)
            else:
                self.disable_query_buttons(key)

        if self.data_table_numerical.row_count + self.data_table_categorical.row_count > 0:
            self.button_query_execute.Enable()
        else:
            self.button_query_execute.Disable()

        # Force user to populate group 1 first
        if self.selected_group == 2 and self.group_data[1]['dvh'] is None:
            self.button_query_execute.Disable()

    def __catch_failed_sql_connection_on_app_launch(self):
        if self.options.DB_TYPE == 'pgsql':
            if not echo_sql_db():
                wx.MessageBox('Invalid credentials!', 'Echo SQL Database', wx.OK | wx.ICON_WARNING)
                self.on_sql()
        else:  # if using sqlite
            initialize_db()

    def __do_subscribe(self):
        pub.subscribe(self.raise_error_dialog, "import_status_raise_error")

    def raise_error_dialog(self, msg):
        MemoryErrorDialog(self, msg)

    # --------------------------------------------------------------------------------------------------------------
    # Menu bar event functions
    # --------------------------------------------------------------------------------------------------------------
    def on_save(self, evt):
        if self.save_data:
            dlg = wx.FileDialog(self, "Save your session data to file", "", wildcard='*.dvha',
                                style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
            dlg.SetDirectory(DATA_DIR)
            if dlg.ShowModal() == wx.ID_OK:
                self.save_data_obj()
                save_object_to_file(self.save_data, dlg.GetPath())
            dlg.Destroy()
        else:
            wx.MessageBox('There is no data to save. Please query/open some data first.', 'Save Error',
                          wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)

    def on_open(self, evt):
        dlg = wx.FileDialog(self, "Open saved data", "", wildcard='*.dvha',
                            style=wx.FD_FILE_MUST_EXIST | wx.FD_OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.close()
            self.load_data_obj(dlg.GetPath())

        dlg.Destroy()

    def on_load_mvr_model(self, *evt):
        with wx.FileDialog(self, "Load a multi-variable linear regression model", "", wildcard='*.mvr',
                           style=wx.FD_FILE_MUST_EXIST | wx.FD_OPEN) as dlg:
            dlg.SetDirectory(MODELS_DIR)
            if dlg.ShowModal() == wx.ID_OK:
                model_file_path = dlg.GetPath()
                dlg.Destroy()
                LoadMultiVarModelFrame(model_file_path, self.group_data, self.selected_group, self.options)

    def on_load_ml_model(self, *evt):
        if self.group_data[self.selected_group]['stats_data']:
            MachineLearningModelViewer(self, self.group_data, self.selected_group, self.options)
        else:
            wx.MessageBox('No data as been queried for Group %s.' % self.selected_group, 'Error',
                          wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)

    @property
    def selected_group(self):
        return self.radio_button_query_group.GetSelection() + 1

    def save_data_obj(self):
        self.save_data['group_data'] = self.group_data
        self.save_data['query_filters'] = self.query_filters
        self.save_data['time_stamp'] = datetime.now()
        self.save_data['version'] = DefaultOptions().VERSION
        # data_table_categorical and data_table_numerical saved after query to ensure these data reflect
        # the rest of the saved data
        self.save_data['endpoint'] = self.endpoint.get_save_data()
        self.save_data['time_series'] = self.time_series.get_save_data()
        self.save_data['radbio'] = self.radbio.get_save_data()
        self.save_data['regression'] = self.regression.get_save_data()
        self.save_data['control_chart'] = self.control_chart.get_save_data()

    def load_data_obj(self, abs_file_path):
        self.save_data = load_object_from_file(abs_file_path)
        self.group_data = self.save_data['group_data']

        # .load_save_data loses column widths?
        self.radio_button_query_group.SetSelection(0)
        self.data_table_categorical.load_save_data(self.save_data['main_categorical_1'])
        self.data_table_numerical.load_save_data(self.save_data['main_numerical_1'])

        self.control_chart.load_save_data(self.save_data['control_chart'])

        self.radio_button_query_group.SetSelection(0)
        self.exec_query(load_saved_dvh_data=True, group=1)

        if 'main_categorical_2' in self.save_data.keys():
            self.radio_button_query_group.SetSelection(1)
            self.on_group_select()
            self.data_table_categorical.load_save_data(self.save_data['main_categorical_2'])
            self.data_table_numerical.load_save_data(self.save_data['main_numerical_2'])
            self.update_all_query_buttons()

            self.exec_query(load_saved_dvh_data=True, group=2)

        self.update_all_query_buttons()

        self.endpoint.load_save_data(self.save_data['endpoint'])
        if self.endpoint.has_data:
            self.endpoint.enable_buttons()

        self.radbio.load_save_data(self.save_data['radbio'])

        self.endpoint.update_endpoints_in_dvh()

        self.group_data[1]['stats_data'].update_endpoints_and_radbio()
        if self.group_data[2]['stats_data']:
            self.group_data[2]['stats_data'].update_endpoints_and_radbio()

        self.time_series.load_save_data(self.save_data['time_series'])
        self.time_series.update_plot()

        self.regression.update_combo_box_choices()
        self.regression.load_save_data(self.save_data['regression'])

        self.control_chart.update_combo_box_y_choices()

    def on_toolbar_settings(self, evt):
        self.on_pref()

    def on_toolbar_import(self, evt):
        self.check_db_then_call(ImportDicomFrame, 'import', self.roi_map, self.options)

    def on_toolbar_database(self, evt):
        self.check_db_then_call(DatabaseEditorFrame, 'database', self.roi_map, self.options)

    def on_toolbar_roi_map(self, evt):
        self.check_db_then_call(ROIMapFrame, 'roi_map', self.roi_map)

    def check_db_then_call(self, func, window_type, *parameters):
        if not echo_sql_db():
            self.on_sql()

        if echo_sql_db():
            if self.tool_bar_windows[window_type]:
                self.tool_bar_windows[window_type].Raise()
            else:
                self.tool_bar_windows[window_type] = func(*parameters)
        else:
            wx.MessageBox('Connection to SQL database could not be established.', 'Connection Error',
                          wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)

    # --------------------------------------------------------------------------------------------------------------
    # Query event functions
    # --------------------------------------------------------------------------------------------------------------
    def on_item_select_categorical(self, evt):
        self.selected_index_categorical = self.table_categorical.GetFirstSelected()

    def on_item_select_numerical(self, evt):
        self.selected_index_numerical = self.table_numerical.GetFirstSelected()

    def add_row_categorical(self, evt):
        query_dlg(self, 'categorical')
        self.button_query_execute.Enable()
        self.update_all_query_buttons()

    def add_row_numerical(self, evt):
        query_dlg(self, 'numerical')
        self.update_all_query_buttons()

    def edit_row_categorical(self, *args):
        if self.selected_index_categorical is not None:
            query_dlg(self, 'categorical', set_values=True)

    def edit_row_numerical(self, *args):
        if self.selected_index_numerical is not None:
            query_dlg(self, 'numerical', set_values=True)

    def doubleclick_categorical(self, evt):
        self.selected_index_categorical = self.table_categorical.GetFirstSelected()
        self.edit_row_categorical()

    def doubleclick_numerical(self, evt):
        self.selected_index_numerical = self.table_numerical.GetFirstSelected()
        self.edit_row_numerical()

    def del_row_categorical(self, evt):
        self.data_table_categorical.delete_row(self.selected_index_categorical)
        self.selected_index_categorical = None
        self.update_all_query_buttons()

    def del_row_numerical(self, evt):
        self.data_table_numerical.delete_row(self.selected_index_numerical)
        self.selected_index_numerical = None
        self.update_all_query_buttons()

    def exec_query_button(self, evt):
        self.exec_query()

    def exec_query(self, load_saved_dvh_data=False, group=None):
        wx.BeginBusyCursor()
        if group is not None:
            self.radio_button_query_group.SetSelection(group - 1)
        group = self.selected_group

        # TODO: retain group 1 endpoint defs after query of group 2
        self.endpoint.clear_data()

        if group == 1:
            self.plot.clear_plot()
            self.time_series.clear_data()
            self.regression.clear(self.group_data)
            self.control_chart.clear_data()
            self.radbio.clear_data()

        if not load_saved_dvh_data:
            try:
                uids, dvh_str = self.get_query()
                self.group_data[group]['dvh'] = \
                    DVH(dvh_condition=dvh_str, uid=uids, dvh_bin_width=self.options.dvh_bin_width)
            except MemoryError:
                msg = "Querying memory error. Try querying less data. At least %s DVHs returned.\n"\
                      "NOTE: Threshold of this error is dependent on your computer." % self.group_data[group]['dvh'].count
                MemoryErrorDialog(self, msg)
                self.close()
                return

        count = self.group_data[group]['dvh'].count
        if count > 1:
            try:
                self.endpoint.update_dvh(self.group_data)
                self.set_summary_text(group)
                self.plot.update_plot(self.group_data[1]['dvh'], dvh_2=self.group_data[2]['dvh'])
                wx.EndBusyCursor()
                self.update_data(load_saved_dvh_data=load_saved_dvh_data, group_2_only=bool(group-1))

                if group == 1:
                    self.notebook_main_view.SetSelection(1)
                    self.__enable_notebook_tabs()

                self.save_data['main_categorical_%s' % group] = self.data_table_categorical.get_save_data()
                self.save_data['main_numerical_%s' % group] = self.data_table_numerical.get_save_data()

                if group == 2:
                    self.regression.group = 2
                    self.control_chart.group = 2
                    self.update_stats_data_plots()

            except PlottingMemoryError as e:
                wx.EndBusyCursor()
                self.on_plotting_memory_error(str(e))
        else:
            wx.EndBusyCursor()
            msg = "%s DVHs returned. Please modify query or import more data." % ['Less than 2', 'No'][count == 0]
            wx.MessageBox(msg, 'Query Error', wx.OK | wx.OK_DEFAULT | wx.ICON_WARNING)
            self.group_data[group]['dvh'] = None

    def get_query(self):

        # Used to accumulate lists of query strings for each table
        # Will assume each item in list is complete query for that SQL column
        queries = {'Plans': [], 'Rxs': [], 'Beams': [], 'DVHs': []}

        # Used to group queries by variable, will combine all queries of same variable with an OR operator
        # e.g., queries_by_sql_column['Plans'][key] = list of strings, where key is sql column
        queries_by_sql_column = {'Plans': {}, 'Rxs': {}, 'Beams': {}, 'DVHs': {}}

        # Categorical filter
        if self.data_table_categorical.row_count:
            for i, category in enumerate(self.data_table_categorical.data['category_1']):
                table = self.categorical_columns[category]['table']
                col = self.categorical_columns[category]['var_name']
                value = self.data_table_categorical.data['category_2'][i]
                if col not in queries_by_sql_column[table]:
                    queries_by_sql_column[table][col] = []
                operator = ['=', '!='][{'Include': 0, 'Exclude': 1}[self.data_table_categorical.data['Filter Type'][i]]]
                queries_by_sql_column[table][col].append("%s %s '%s'" % (col, operator, value))

        # Range filter
        if self.data_table_numerical.row_count:
            for i, category in enumerate(self.data_table_numerical.data['category']):
                table = self.numerical_columns[category]['table']
                col = self.numerical_columns[category]['var_name']
                value_low = self.data_table_numerical.data['min'][i]
                value_high = self.data_table_numerical.data['max'][i]
                if 'date' in col:
                    value_low = "'%s'" % value_low
                    value_high = "'%s'" % value_high
                    if self.options.DB_TYPE == 'pgsql':
                        value_low = value_low + '::date'
                        value_high = value_high + '::date'
                if col not in queries_by_sql_column[table]:
                    queries_by_sql_column[table][col] = []
                operator = ['BETWEEN', 'NOT BETWEEN'][
                    {'Include': 0, 'Exclude': 1}[self.data_table_numerical.data['Filter Type'][i]]]
                queries_by_sql_column[table][col].append("%s %s %s AND %s" % (col, operator, value_low, value_high))

        for table in queries:
            for col in queries_by_sql_column[table]:
                queries[table].append("(%s)" % ' OR '.join(queries_by_sql_column[table][col]))
            queries[table] = ' AND '.join(queries[table])

        uids = get_study_instance_uids(plans=queries['Plans'], rxs=queries['Rxs'], beams=queries['Beams'])['common']

        return uids, queries['DVHs']

    def update_data(self, load_saved_dvh_data=False, group_2_only=False):
        wx.BeginBusyCursor()
        tables = ['Plans', 'Rxs', 'Beams']
        for grp, grp_data in self.group_data.items():
            if not(grp == 1 and group_2_only) or grp == 2:
                if hasattr(grp_data['dvh'], 'study_instance_uid'):
                    if not load_saved_dvh_data:
                        condition_str = "study_instance_uid in ('%s')" % "','".join(
                           grp_data['dvh'].study_instance_uid)
                        grp_data['data'] = {key: QuerySQL(key, condition_str) for key in tables}
                        grp_data['stats_data'] = StatsData(grp_data['dvh'], grp_data['data'], group=grp)
                else:
                    grp_data['data'] = {key: None for key in tables}
                    grp_data['stats_data'] = None

        if self.group_data[2]['stats_data']:
            sync_variables_in_stats_data_objects(self.group_data[1]['stats_data'],
                                                 self.group_data[2]['stats_data'])
        self.time_series.update_data(self.group_data)
        self.control_chart.update_data(self.group_data)
        self.correlation.set_data(self.group_data)
        self.regression.update_combo_box_choices()
        self.radbio.update_dvh_data(self.group_data)

        wx.EndBusyCursor()

    # --------------------------------------------------------------------------------------------------------------
    # Menu bar event functions
    # --------------------------------------------------------------------------------------------------------------
    def close_windows(self):
        for view in self.data_views.values():
            if hasattr(view, 'Destroy'):
                try:
                    view.Destroy()
                except RuntimeError:
                    pass

        for window in self.tool_bar_windows.values():
            if window and hasattr(window, 'Destroy'):
                window.Destroy()

        self.regression.close_mvr_frames()

    def on_quit(self, evt):
        self.close_windows()
        self.Destroy()

    def on_close(self, *evt):
        if self.group_data[1]['dvh']:
            dlg = wx.MessageDialog(self, "Clear all data and plots?", caption='Close',
                                   style=wx.YES | wx.NO | wx.NO_DEFAULT | wx.CENTER | wx.ICON_EXCLAMATION)
            dlg.Center()
            res = dlg.ShowModal()
            if res == wx.ID_YES:
                self.close()
                self.radio_button_query_group.SetSelection(0)
            dlg.Destroy()

    def close(self):
        self.group_data = {1: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None},
                           2: {'dvh': None,
                               'data': {key: None for key in ['Plans', 'Beams', 'Rxs']},
                               'stats_data': None}}
        self.data_table_categorical.delete_all_rows()
        self.data_table_numerical.delete_all_rows()
        self.plot.clear_plot()
        self.endpoint.clear_data()
        self.radbio.clear_data()
        self.time_series.clear_data()
        self.notebook_main_view.SetSelection(0)
        self.text_summary.SetLabelText("")
        self.__disable_notebook_tabs()
        self.disable_query_buttons('categorical')
        self.disable_query_buttons('numerical')
        self.button_query_execute.Disable()
        self.time_series.initialize_y_axis_options()
        self.regression.clear(self.group_data)
        self.control_chart.initialize_y_axis_options()
        self.control_chart.plot.clear_plot()
        self.control_chart.group = 1
        self.close_windows()
        self.reset_query_filters()

    def on_export(self, evt):
        if self.group_data[1]['dvh'] is not None:
            ExportCSVDialog(self)
        else:
            wx.MessageBox('There is no data to export! Please query some data first.', 'Export Error',
                          wx.OK | wx.ICON_WARNING)

    @staticmethod
    def on_githubpage(evt):
        webbrowser.open_new_tab("http://dvhanalytics.com/")

    @staticmethod
    def on_report_issue(evt):
        webbrowser.open_new_tab("https://github.com/cutright/DVH-Analytics/issues")

    @staticmethod
    def on_about(evt):
        About()

    def on_pref(self, *args):
        UserSettings(self.options)

    def on_sql(self, *args):
        SQLSettingsDialog(self.options)
        [self.__disable_add_filter_buttons, self.__enable_add_filter_buttons][echo_sql_db()]()

    def on_save_plot_dvhs(self, evt):
        save_data_to_file(self, 'Save DVHs plot', self.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_time_series(self, evt):
        save_data_to_file(self, 'Save Time Series plot', self.time_series.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_correlation(self, evt):
        save_data_to_file(self, 'Save Correlation plot', self.correlation.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_regression(self, evt):
        save_data_to_file(self, 'Save Regression plot', self.regression.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_save_plot_control_chart(self, evt):
        save_data_to_file(self, 'Save Control Chart plot', self.control_chart.plot.html_str,
                          wildcard="HTML files (*.html)|*.html")

    def on_view_dvhs(self, evt):
        self.view_table_data('DVHs')

    def on_view_plans(self, evt):
        self.view_table_data('Plans')

    def on_view_rxs(self, evt):
        self.view_table_data('Rxs')

    def on_view_beams(self, evt):
        self.view_table_data('Beams')

    def on_view_stats_data_1(self, evt):
        self.view_table_data('StatsData1')

    def on_view_stats_data_2(self, evt):
        self.view_table_data('StatsData2')

    def view_table_data(self, key):
        if key == 'DVHs':
            data = {grp: self.group_data[grp]['dvh'] for grp in [1, 2]}
        elif 'StatsData' in key:
            data = {grp: self.group_data[grp]['stats_data'] for grp in [1, 2]}
        else:
            data = {grp: self.group_data[grp]['data'][key] for grp in [1, 2]}

        if data[1]:

            if self.get_menu_item_status(key) == 'Show':
                if 'StatsData' in key:
                    group = int(key[-1])
                    if group == 1 or data[2] is not None:
                        self.data_views[key] = StatsDataEditor(self.group_data, group, self.data_menu,
                                                               self.data_menu_items[key].GetId(), self.time_series,
                                                               self.regression, self.control_chart)
                    else:
                        self.no_queried_data_dlg()
                else:
                    if key == 'DVHs':
                        columns = [c for c in data[1].keys]
                    elif key == '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'] == key]

                    for starter_column in ['study_instance_uid', 'mrn']:
                        if starter_column in columns:
                            columns.pop(columns.index(starter_column))
                        columns.insert(0, starter_column)

                    self.data_views[key] = QueriedDataFrame(data, columns, key,
                                                            self.data_menu, self.data_menu_items[key].GetId())
            else:
                self.data_views[key].on_close()
                self.data_views[key] = None
        else:
            self.no_queried_data_dlg()

    def no_queried_data_dlg(self):
        dlg = wx.MessageDialog(self, 'Please query/open some data first.', 'ERROR!', wx.ICON_ERROR | wx.OK_DEFAULT)
        dlg.ShowModal()
        dlg.Destroy()

    def get_menu_item_status(self, key):
        show_hide = ['Hide', 'Show']['Show' in self.data_menu.GetLabel(self.data_menu_items[key].GetId())]
        return show_hide

    def redraw_plots(self):
        if self.group_data[1]['dvh']:
            self.plot.redraw_plot()
            self.time_series.plot.redraw_plot()
            self.correlation.plot.redraw_plot()
            self.regression.plot.redraw_plot()
            self.control_chart.plot.redraw_plot()

    def update_stats_data_plots(self):
        if self.group_data[1]['dvh']:
            self.time_series.update_plot()
            self.time_series.update_y_axis_options()
            self.regression.update_plot()
            self.control_chart.update_plot()

    def on_resize(self, *evt):
        try:
            self.Refresh()
            self.Layout()
            wx.CallAfter(self.redraw_plots)
        except RuntimeError:
            pass

    def on_plotting_memory_error(self, plot_type):
        plot_type = [' (Plot type: %s)' % plot_type, ''][plot_type is None]
        msg = "Plotting memory error%s. Try querying less data. At least %s DVHs returned.\n" \
              "NOTE: Threshold of this error is dependent on your computer." % (plot_type, self.group_data[1]['dvh'].count)
        MemoryErrorDialog(self, msg)
        self.close()

    def reset_query_filters(self):
        self.query_filters = {key: None for key in [1, 2]}

    def on_group_select(self, *evt):
        group = self.selected_group
        other = 3 - group

        self.button_query_execute.SetLabelText('Query and Retrieve Group %s' % group)

        self.query_filters[other] = {'main_categorical': self.data_table_categorical.get_save_data(),
                                     'main_numerical': self.data_table_numerical.get_save_data()}
        if self.query_filters[group] is not None:
            self.data_table_categorical.load_save_data(self.query_filters[group]['main_categorical'])
            self.data_table_numerical.load_save_data(self.query_filters[group]['main_numerical'])
        else:
            self.data_table_categorical.delete_all_rows()
            self.data_table_numerical.delete_all_rows()

        self.update_all_query_buttons()

        self.set_summary_text(group)

        if self.group_data[2]['stats_data']:
            self.regression.group = group
            self.regression.update_plot()
            self.control_chart.group = group
            self.control_chart.update_plot()

    def set_summary_text(self, group):
        if self.group_data[group]['dvh']:
            text = self.group_data[group]['dvh'].get_summary()
        else:
            text = ''
        self.text_summary.SetLabelText(text)
示例#23
0
    def __init__(self, roi_map):
        """
        :param roi_map: roi map object
        :type roi_map: DatabaseROIs
        """
        wx.Frame.__init__(self, None, title='ROI Map')
        set_frame_icon(self)

        self.roi_map = roi_map

        self.window_size = get_window_size(0.893, 0.762)
        self.SetSize(self.window_size)
        self.window = wx.SplitterWindow(self, wx.ID_ANY)
        self.window_tree = wx.Panel(self.window, wx.ID_ANY, style=wx.BORDER_SUNKEN)

        self.combo_box_tree_plot_data = wx.ComboBox(self.window_tree, wx.ID_ANY,
                                                    choices=['All', 'Linked', 'Unlinked', 'Branched'],
                                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.plot = PlotROIMap(self.window_tree, roi_map)
        self.window_editor = wx.Panel(self.window, wx.ID_ANY, style=wx.BORDER_SUNKEN)

        self.combo_box_physician = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=self.roi_map.get_physicians(),
                                               style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_physician_roi = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=[],
                                                   style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.list_ctrl_variations = wx.ListCtrl(self.window_editor, wx.ID_ANY,
                                                style=wx.LC_NO_HEADER | wx.LC_REPORT | wx.BORDER_SUNKEN)
        self.button_variation_select_all = wx.Button(self.window_editor, wx.ID_ANY, "Select All")
        self.button_variation_deselect_all = wx.Button(self.window_editor, wx.ID_ANY, "Deselect All")
        self.button_variation_add = wx.Button(self.window_editor, wx.ID_ANY, "Add")
        self.button_variation_delete = wx.Button(self.window_editor, wx.ID_ANY, "Delete")
        self.button_variation_move = wx.Button(self.window_editor, wx.ID_ANY, "Move")

        self.button_variation_move.Disable()
        self.button_variation_delete.Disable()
        self.button_variation_deselect_all.Disable()

        self.button_physician = {'add': wx.Button(self.window_editor, wx.ID_ANY, "+"),
                                 'del': wx.Button(self.window_editor, wx.ID_ANY, "-"),
                                 'edit': wx.Button(self.window_editor, wx.ID_ANY, "Δ")}
        self.button_physician_roi = {'add': wx.Button(self.window_editor, wx.ID_ANY, "+"),
                                     'del': wx.Button(self.window_editor, wx.ID_ANY, "-"),
                                     'edit': wx.Button(self.window_editor, wx.ID_ANY, "Δ")}

        self.button_link_physician_roi = wx.Button(self.window_editor, wx.ID_ANY, "Link")
        self.button_link_physician_roi.Disable()

        self.combo_box_uncategorized_ignored = wx.ComboBox(self.window_editor, wx.ID_ANY,
                                                           choices=["Uncategorized", "Ignored"],
                                                           style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_box_uncategorized_ignored_roi = wx.ComboBox(self.window_editor, wx.ID_ANY, choices=[],
                                                               style=wx.CB_DROPDOWN)
        self.button_uncategorized_ignored_delete = wx.Button(self.window_editor, wx.ID_ANY, "Delete DVH")
        self.button_uncategorized_ignored_ignore = wx.Button(self.window_editor, wx.ID_ANY, "Ignore DVH")
        self.combo_box_physician_roi_merge = {'a': wx.ComboBox(self.window_editor, wx.ID_ANY, style=wx.CB_DROPDOWN),
                                              'b': wx.ComboBox(self.window_editor, wx.ID_ANY, style=wx.CB_DROPDOWN)}
        self.button_merge = wx.Button(self.window_editor, wx.ID_ANY, "Merge")

        self.button_save_and_update = wx.Button(self.window_editor, wx.ID_ANY, "Save and Update Database")
        self.button_cancel = wx.Button(self.window_editor, wx.ID_ANY, "Cancel Changes and Reload")

        self.uncategorized_variations = {}

        self.columns = ['Variations']
        self.data_table = DataTable(self.list_ctrl_variations, columns=self.columns, widths=[490])

        self.__set_properties()
        self.__do_bind()
        self.__do_layout()

        self.plot.update_roi_map_source_data(self.physician)

        self.run()
示例#24
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'])