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 __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 __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 __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 __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()
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()
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 __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 __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()
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
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()
class DatabaseEditorFrame(wx.Frame): """ Various viewing and editing tools for the SQL database. This object is called on Database toolbar click. """ def __init__(self, roi_map, options): """ :param roi_map: roi_map object :type roi_map: DatabaseROIs """ wx.Frame.__init__(self, None, title='Database Administrator') set_frame_icon(self) set_msw_background_color( self) # If windows, change the background color self.roi_map = roi_map self.options = options self.db_tree = self.get_db_tree() self.SetSize(get_window_size(0.792, 0.781)) self.window_db_editor = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D) self.window_pane_db_tree = wx.ScrolledWindow(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.tree_ctrl_db = wx.TreeCtrl(self.window_pane_db_tree, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_MULTIPLE) self.window_pane_query = wx.Panel(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.text_ctrl_condition = wx.TextCtrl(self.window_pane_query, wx.ID_ANY, "") self.list_ctrl_query_results = wx.ListCtrl( self.window_pane_query, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.data_query_results = DataTable( self.list_ctrl_query_results, columns=['mrn', 'study_instance_uid']) self.combo_box_query_table = wx.ComboBox(self.window_pane_query, wx.ID_ANY, choices=list(self.db_tree), style=wx.CB_DROPDOWN | wx.CB_READONLY) self.button = { 'delete_all_data': wx.Button(self, wx.ID_ANY, "Delete All Data"), 'rebuild_db': wx.Button(self, wx.ID_ANY, "Rebuild Database"), 'calculations': wx.Button(self, wx.ID_ANY, "Calculations"), 'edit_db': wx.Button(self, wx.ID_ANY, "Edit Database"), 'reimport': wx.Button(self, wx.ID_ANY, "Reimport from DICOM"), 'delete_study': wx.Button(self, wx.ID_ANY, "Delete Study"), 'change_mrn_uid': wx.Button(self, wx.ID_ANY, "Change MRN/UID"), 'query': wx.Button(self.window_pane_query, wx.ID_ANY, "Query"), 'clear': wx.Button(self.window_pane_query, wx.ID_ANY, "Clear"), 'export_csv': wx.Button(self.window_pane_query, wx.ID_ANY, "Export"), 'remap_roi_names': wx.Button(self, wx.ID_ANY, "Remap ROI Names"), 'update_from_csv': wx.Button(self, wx.ID_ANY, "Update from CSV"), # 'plan_complexity': wx.Button(self, wx.ID_ANY, "Recalc Plan Complexities"), 'auto_fit_columns': wx.Button(self.window_pane_query, wx.ID_ANY, "Auto-fit Columns") } self.checkbox_auto_backup = wx.CheckBox( self, wx.ID_ANY, "Auto Backup SQLite DB After Import") self.__set_properties() self.__do_layout() self.__do_bind() self.selected_columns = { table: {c: False for c in list(self.db_tree[table])} for table in list(self.db_tree) } self.selected_tables = {table: False for table in list(self.db_tree)} self.window_db_editor.SetSashPosition(250) self.tree_ctrl_db.Expand(self.db_tree_root) self.allow_tree_select_change = True self.Show() def __set_properties(self): self.SetTitle("Database Administrator") self.window_pane_db_tree.SetScrollRate(10, 10) self.window_db_editor.SetMinimumPaneSize(20) self.db_tree_root = self.tree_ctrl_db.AddRoot('DVH Analytics') self.table_nodes = { table: self.tree_ctrl_db.AppendItem(self.db_tree_root, table) for table in self.db_tree } self.column_nodes = {table: {} for table in self.db_tree} for table in self.db_tree: for column in self.db_tree[table]: self.column_nodes[table][ column] = self.tree_ctrl_db.AppendItem( self.table_nodes[table], column) self.combo_box_query_table.SetValue('Plans') self.checkbox_auto_backup.SetFont( wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, "")) self.checkbox_auto_backup.SetValue(self.options.AUTO_SQL_DB_BACKUP) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_query = wx.BoxSizer(wx.VERTICAL) sizer_query_table = wx.StaticBoxSizer( wx.StaticBox(self.window_pane_query, wx.ID_ANY, "Query Results"), wx.VERTICAL) sizer_condition_buttons = wx.BoxSizer(wx.HORIZONTAL) sizer_download_button = wx.BoxSizer(wx.VERTICAL) sizer_clear_button = wx.BoxSizer(wx.VERTICAL) sizer_query_button = wx.BoxSizer(wx.VERTICAL) sizer_auto_fit_columns_button = wx.BoxSizer(wx.VERTICAL) sizer_condition = wx.BoxSizer(wx.VERTICAL) sizer_combo_box = wx.BoxSizer(wx.VERTICAL) sizer_db_tree = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons = wx.BoxSizer(wx.VERTICAL) sizer_dialog_buttons_1 = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons_1.Add(self.button['calculations'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['edit_db'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['reimport'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['delete_study'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['change_mrn_uid'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['rebuild_db'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['delete_all_data'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['remap_roi_names'], 0, wx.ALL, 5) sizer_dialog_buttons_1.Add(self.button['update_from_csv'], 0, wx.ALL, 5) # sizer_dialog_buttons.Add(self.button['plan_complexity'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(sizer_dialog_buttons_1, 0, 0, 0) sizer_dialog_buttons.Add(self.checkbox_auto_backup, 0, wx.LEFT, 5) sizer_wrapper.Add(sizer_dialog_buttons, 0, wx.ALL, 5) sizer_db_tree.Add(self.tree_ctrl_db, 1, wx.EXPAND, 0) self.window_pane_db_tree.SetSizer(sizer_db_tree) label_table = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Table:") sizer_combo_box.Add(label_table, 0, wx.BOTTOM | wx.TOP, 5) sizer_combo_box.Add(self.combo_box_query_table, 0, 0, 0) sizer_condition_buttons.Add(sizer_combo_box, 0, wx.ALL | wx.EXPAND, 5) label_condition = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Condition:") sizer_condition.Add(label_condition, 0, wx.BOTTOM | wx.TOP, 5) sizer_condition.Add(self.text_ctrl_condition, 0, wx.ALL | wx.EXPAND, 0) sizer_condition_buttons.Add(sizer_condition, 1, wx.ALL | wx.EXPAND, 5) label_spacer_1 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_query_button.Add(label_spacer_1, 0, wx.BOTTOM, 5) sizer_query_button.Add(self.button['query'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_query_button, 0, wx.ALL, 5) label_spacer_2 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_download_button.Add(label_spacer_2, 0, wx.BOTTOM, 5) sizer_download_button.Add(self.button['export_csv'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_download_button, 0, wx.ALL, 5) label_spacer_3 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_clear_button.Add(label_spacer_3, 0, wx.BOTTOM, 5) sizer_clear_button.Add(self.button['clear'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_clear_button, 0, wx.ALL, 5) label_spacer_4 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_auto_fit_columns_button.Add(label_spacer_4, 0, wx.BOTTOM, 5) sizer_auto_fit_columns_button.Add(self.button['auto_fit_columns'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_auto_fit_columns_button, 0, wx.ALL, 5) sizer_query.Add(sizer_condition_buttons, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT, 5) sizer_query_table.Add(self.list_ctrl_query_results, 1, wx.EXPAND, 0) sizer_query.Add(sizer_query_table, 1, wx.ALL | wx.EXPAND, 5) self.window_pane_query.SetSizer(sizer_query) self.window_db_editor.SplitVertically(self.window_pane_db_tree, self.window_pane_query) sizer_wrapper.Add(self.window_db_editor, 1, wx.EXPAND, 0) self.SetSizer(sizer_wrapper) self.Layout() self.Center() def __do_bind(self): # self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeAdd, self.tree_ctrl_db, id=self.tree_ctrl_db.GetId()) # All buttons are bound to a function based on their key prepended with 'on_' # For example, query button calls on_query when clicked for key, button in self.button.items(): self.Bind(wx.EVT_BUTTON, getattr(self, 'on_' + key), id=button.GetId()) self.Bind(wx.EVT_CHECKBOX, self.on_auto_backup, id=self.checkbox_auto_backup.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_query_results, self.list_ctrl_query_results) def on_auto_fit_columns(self, *evt): self.data_query_results.set_column_widths(auto=True) def on_tree_add(self, evt): self.update_selected_tree_items() # def UnselectOtherTables(self, selected_table): # self.allow_tree_select_change = False # for table, node in self.table_nodes.items(): # if table != selected_table: # self.tree_ctrl_db.UnselectItem(node) # self.allow_tree_select_change = True def on_query(self, evt): self.update_selected_tree_items() table = self.combo_box_query_table.GetValue() columns = [ c for c, sel in self.selected_columns[table].items() if sel and c not in {'mrn', 'study_instance_uid'} ] columns.sort() if not columns: columns = [ c for c in self.db_tree[table] if c not in {'mrn', 'study_instance_uid'} ] columns.insert(0, 'study_instance_uid') columns.insert(0, 'mrn') condition = self.text_ctrl_condition.GetValue() with DVH_SQL() as cnx: try: data = cnx.query(table, ','.join(columns), condition, bokeh_cds=True) self.data_query_results.set_data(data, columns) except SQLError as e: SQLErrorDialog(self, e) self.data_query_results.clear() def on_clear(self, evt): self.data_query_results.clear() @staticmethod def get_db_tree(): tree = get_database_tree() for table in list(tree): tree[table] = [ column for column in tree[table] if 'string' not in column ] return tree def update_selected_tree_items(self): for table, table_item in self.table_nodes.items(): self.selected_tables[table] = self.tree_ctrl_db.IsSelected( table_item) for column, column_item in self.column_nodes[table].items(): self.selected_columns[table][ column] = self.tree_ctrl_db.IsSelected(column_item) def on_change_mrn_uid(self, evt): self.change_or_delete_dlg(ChangePatientIdentifierDialog) def on_delete_study(self, evt): self.change_or_delete_dlg(DeletePatientDialog) def change_or_delete_dlg(self, class_type): selected_data = self.data_query_results.selected_row_data if selected_data: class_type(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: class_type() def on_reimport(self, evt): selected_data = self.data_query_results.selected_row_data if selected_data: ReimportDialog(self.roi_map, self.options, mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: ReimportDialog(self.roi_map, self.options) @staticmethod def on_edit_db(evt): EditDatabaseDialog() @staticmethod def on_calculations(evt): CalculationsDialog() def on_rebuild_db(self, evt): RebuildDB(self, self.roi_map, self.options) def on_delete_all_data(self, evt): DeleteAllData(self, self.options) def on_export_csv(self, evt): save_data_to_file(self, "Export Data Table to CSV", self.data_query_results.get_csv()) def on_remap_roi_names(self, evt): RemapROIFrame(self.roi_map, remap_all=True) def sort_query_results(self, evt): self.data_query_results.sort_table(evt) def on_auto_backup(self, *evt): self.options.AUTO_SQL_DB_BACKUP = self.checkbox_auto_backup.GetValue() # @staticmethod # def on_plan_complexity(*evt): # with wx.BusyCursor() as busy: # recalculate_plan_complexities_from_beams() def on_update_from_csv(self, *evt): msg = "Load a CSV file and update your database. Please see manual for " \ "more information. Formatting is critical, this can not be undone!" caption = "USE WITH EXTREME CAUTION" ErrorDialog(self, msg, caption, flags=wx.ICON_WARNING | wx.OK | wx.OK_DEFAULT) with wx.FileDialog(self, "Load CSV into Database", style=wx.FD_FILE_MUST_EXIST | wx.FD_OPEN) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return pathname = fileDialog.GetPath() with open(pathname, 'r') as fp: lines = fp.readlines() header_row = "table,column,value,condition" if lines and header_row in lines[0].strip(): params = [csv_to_list(line.strip()) for line in lines[1:]] ProgressFrame(params, update_db_with_csv, title="Update Database from CSV file", action_msg="Processing row", star_map=True) else: msg = "The selected file was not recognized. " \ "Be sure you have a header row of: %s" % header_row caption = "CSV load failure" ErrorDialog(self, msg, caption, flags=wx.ICON_WARNING | wx.OK | wx.OK_DEFAULT)
class 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
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)
class QueriedDataFrame(wx.Frame): """ Generate a simple table to view data of the current query for a specified SQL table """ def __init__(self, data_obj, sql_table, menu, menu_item_id): """ :param data_obj: object containing data to be viewed :param sql_table: either 'DVHs', 'Plans', 'Beams', or 'Rxs' :type sql_table: str :param menu: a link to the main app menu, used to toggle Show/Hide status :type menu: Menu :param menu_item_id: the ID of the menu item associated with the specified data_obj """ wx.Frame.__init__(self, None, title='%s Data' % sql_table[0:-1]) self.data = data_obj self.sql_table = sql_table self.menu = menu self.menu_item_id = menu_item_id self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) # self.data_table = DataTable(self.list_ctrl, data=self.table_data, columns=self.columns) self.data_table = DataTable(self.list_ctrl) self.data_table.set_data(self.table_data, self.columns) self.button_export = wx.Button(self, wx.ID_ANY, "Export to CSV") self.__do_bind() self.__set_properties() self.__do_layout() self.run() def __set_properties(self): self.SetSize(get_window_size(0.714, 0.762)) def __do_bind(self): self.Bind(wx.EVT_BUTTON, self.on_export, id=self.button_export.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_table, self.list_ctrl) self.Bind(wx.EVT_CLOSE, self.on_close) def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_wrapper.Add(self.button_export, 0, wx.ALL, 10) sizer_wrapper.Add(self.list_ctrl, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 10) self.SetSizer(sizer_wrapper) self.Center() @property def columns(self): if self.sql_table == 'DVHs': columns = self.data.keys elif self.sql_table == 'Rxs': columns = [ 'plan_name', 'fx_dose', 'rx_percent', 'fxs', 'rx_dose', 'fx_grp_number', 'fx_grp_count', 'fx_grp_name', 'normalization_method', 'normalization_object' ] else: columns = [ obj['var_name'] for obj in sql_column_info.values() if obj['table'] == self.sql_table ] for starter_column in ['study_instance_uid', 'mrn']: if starter_column in columns: columns.pop(columns.index(starter_column)) columns.insert(0, starter_column) return columns @property def table_data(self): return {column: getattr(self.data, column) for column in self.columns} def run(self): self.toggle_data_menu_item() self.Show() def on_close(self, *args): self.toggle_data_menu_item() self.Destroy() def on_export(self, *args): save_data_to_file(self, "Export %s to CSV" % self.sql_table, self.data_table.get_csv()) def toggle_data_menu_item(self): short_cut = ['DVHs', 'Plans', 'Rxs', 'Beams'].index(self.sql_table) + 1 show_hide = ['Show', 'Hide']['Show' in self.menu.GetLabel(self.menu_item_id)] self.menu.SetLabel( self.menu_item_id, '%s %s\tCtrl+%s' % (show_hide, self.sql_table, short_cut)) def sort_table(self, evt): self.data_table.sort_table(evt)
class DatabaseEditorFrame(wx.Frame): """ Various viewing and editing tools for the SQL database. This object is called on Database toolbar click. """ def __init__(self, roi_map): """ :param roi_map: roi_map object :type roi_map: DatabaseROIs """ wx.Frame.__init__(self, None, title='Database Administrator') set_msw_background_color(self) # If windows, change the background color self.roi_map = roi_map self.db_tree = self.get_db_tree() self.SetSize(get_window_size(0.792, 0.781)) self.window_db_editor = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_3D) self.window_pane_db_tree = wx.ScrolledWindow(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.tree_ctrl_db = wx.TreeCtrl(self.window_pane_db_tree, wx.ID_ANY, style=wx.TR_HAS_BUTTONS | wx.TR_MULTIPLE) self.window_pane_query = wx.Panel(self.window_db_editor, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.TAB_TRAVERSAL) self.text_ctrl_condition = wx.TextCtrl(self.window_pane_query, wx.ID_ANY, "") self.list_ctrl_query_results = wx.ListCtrl(self.window_pane_query, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES) self.data_query_results = DataTable(self.list_ctrl_query_results, columns=['mrn', 'study_instance_uid']) self.combo_box_query_table = wx.ComboBox(self.window_pane_query, wx.ID_ANY, choices=list(self.db_tree), style=wx.CB_DROPDOWN | wx.CB_READONLY) self.button = {'delete_all_data': wx.Button(self, wx.ID_ANY, "Delete All Data"), 'rebuild_db': wx.Button(self, wx.ID_ANY, "Rebuild Database"), # 'calculations': wx.Button(self, wx.ID_ANY, "Calculations"), 'edit_db': wx.Button(self, wx.ID_ANY, "Edit Database"), 'reimport': wx.Button(self, wx.ID_ANY, "Reimport from DICOM"), 'delete_study': wx.Button(self, wx.ID_ANY, "Delete Study"), 'change_mrn_uid': wx.Button(self, wx.ID_ANY, "Change MRN/UID"), 'query': wx.Button(self.window_pane_query, wx.ID_ANY, "Query"), 'clear': wx.Button(self.window_pane_query, wx.ID_ANY, "Clear"), 'export_csv': wx.Button(self.window_pane_query, wx.ID_ANY, "Export"), 'remap_roi_names': wx.Button(self, wx.ID_ANY, "Remap ROI Names")} self.__set_properties() self.__do_layout() self.__do_bind() self.selected_columns = {table: {c: False for c in list(self.db_tree[table])} for table in list(self.db_tree)} self.selected_tables = {table: False for table in list(self.db_tree)} self.window_db_editor.SetSashPosition(250) self.tree_ctrl_db.Expand(self.db_tree_root) self.allow_tree_select_change = True self.Show() def __set_properties(self): self.SetTitle("Database Administrator") self.window_pane_db_tree.SetScrollRate(10, 10) self.window_db_editor.SetMinimumPaneSize(20) self.db_tree_root = self.tree_ctrl_db.AddRoot('DVH Analytics') self.table_nodes = {table: self.tree_ctrl_db.AppendItem(self.db_tree_root, table) for table in self.db_tree} self.column_nodes = {table: {} for table in self.db_tree} for table in self.db_tree: for column in self.db_tree[table]: self.column_nodes[table][column] = self.tree_ctrl_db.AppendItem(self.table_nodes[table], column) self.combo_box_query_table.SetValue('Plans') def __do_layout(self): sizer_wrapper = wx.BoxSizer(wx.VERTICAL) sizer_query = wx.BoxSizer(wx.VERTICAL) sizer_query_table = wx.StaticBoxSizer(wx.StaticBox(self.window_pane_query, wx.ID_ANY, "Query Results"), wx.VERTICAL) sizer_condition_buttons = wx.BoxSizer(wx.HORIZONTAL) sizer_download_button = wx.BoxSizer(wx.VERTICAL) sizer_clear_button = wx.BoxSizer(wx.VERTICAL) sizer_query_button = wx.BoxSizer(wx.VERTICAL) sizer_condition = wx.BoxSizer(wx.VERTICAL) sizer_combo_box = wx.BoxSizer(wx.VERTICAL) sizer_db_tree = wx.BoxSizer(wx.HORIZONTAL) sizer_dialog_buttons = wx.BoxSizer(wx.HORIZONTAL) # sizer_dialog_buttons.Add(self.button['calculations'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['edit_db'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['reimport'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['delete_study'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['change_mrn_uid'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['rebuild_db'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['delete_all_data'], 0, wx.ALL, 5) sizer_dialog_buttons.Add(self.button['remap_roi_names'], 0, wx.ALL, 5) sizer_wrapper.Add(sizer_dialog_buttons, 0, wx.ALL, 5) sizer_db_tree.Add(self.tree_ctrl_db, 1, wx.EXPAND, 0) self.window_pane_db_tree.SetSizer(sizer_db_tree) label_table = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Table:") sizer_combo_box.Add(label_table, 0, wx.BOTTOM | wx.TOP, 5) sizer_combo_box.Add(self.combo_box_query_table, 0, 0, 0) sizer_condition_buttons.Add(sizer_combo_box, 0, wx.ALL | wx.EXPAND, 5) label_condition = wx.StaticText(self.window_pane_query, wx.ID_ANY, "Condition:") sizer_condition.Add(label_condition, 0, wx.BOTTOM | wx.TOP, 5) sizer_condition.Add(self.text_ctrl_condition, 0, wx.ALL | wx.EXPAND, 0) sizer_condition_buttons.Add(sizer_condition, 1, wx.ALL | wx.EXPAND, 5) label_spacer_1 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_query_button.Add(label_spacer_1, 0, wx.BOTTOM, 5) sizer_query_button.Add(self.button['query'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_query_button, 0, wx.ALL, 5) label_spacer_2 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_download_button.Add(label_spacer_2, 0, wx.BOTTOM, 5) sizer_download_button.Add(self.button['export_csv'], 0, wx.TOP | wx.BOTTOM, 5) sizer_condition_buttons.Add(sizer_download_button, 0, wx.ALL, 5) label_spacer_3 = wx.StaticText(self.window_pane_query, wx.ID_ANY, "") sizer_clear_button.Add(label_spacer_3, 0, wx.BOTTOM, 5) sizer_clear_button.Add(self.button['clear'], 0, wx.ALL, 5) sizer_condition_buttons.Add(sizer_clear_button, 0, wx.ALL, 5) sizer_query.Add(sizer_condition_buttons, 0, wx.BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT, 5) sizer_query_table.Add(self.list_ctrl_query_results, 1, wx.EXPAND, 0) sizer_query.Add(sizer_query_table, 1, wx.ALL | wx.EXPAND, 5) self.window_pane_query.SetSizer(sizer_query) self.window_db_editor.SplitVertically(self.window_pane_db_tree, self.window_pane_query) sizer_wrapper.Add(self.window_db_editor, 1, wx.EXPAND, 0) self.SetSizer(sizer_wrapper) self.Layout() self.Center() def __do_bind(self): # self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeAdd, self.tree_ctrl_db, id=self.tree_ctrl_db.GetId()) # All buttons are bound to a function based on their key prepended with 'on_' # For example, query button calls on_query when clicked for key, button in self.button.items(): self.Bind(wx.EVT_BUTTON, getattr(self, 'on_' + key), id=button.GetId()) self.Bind(wx.EVT_LIST_COL_CLICK, self.sort_query_results, self.list_ctrl_query_results) def on_tree_add(self, evt): self.update_selected_tree_items() # def UnselectOtherTables(self, selected_table): # self.allow_tree_select_change = False # for table, node in self.table_nodes.items(): # if table != selected_table: # self.tree_ctrl_db.UnselectItem(node) # self.allow_tree_select_change = True def on_query(self, evt): self.update_selected_tree_items() table = self.combo_box_query_table.GetValue() columns = [c for c, sel in self.selected_columns[table].items() if sel and c not in {'mrn', 'study_instance_uid'}] columns.sort() if not columns: columns = [c for c in self.db_tree[table] if c not in {'mrn', 'study_instance_uid'}] columns.insert(0, 'study_instance_uid') columns.insert(0, 'mrn') condition = self.text_ctrl_condition.GetValue() wait = wx.BusyInfo("Querying data\nPlease wait...") with DVH_SQL() as cnx: try: data = cnx.query(table, ','.join(columns), condition, bokeh_cds=True) self.data_query_results.set_data(data, columns) except SQLError as e: SQLErrorDialog(self, e) self.data_query_results.clear() del wait def on_clear(self, evt): self.data_query_results.clear() @staticmethod def get_db_tree(): tree = get_database_tree() for table in list(tree): tree[table] = [column for column in tree[table] if 'string' not in column] return tree def update_selected_tree_items(self): for table, table_item in self.table_nodes.items(): self.selected_tables[table] = self.tree_ctrl_db.IsSelected(table_item) for column, column_item in self.column_nodes[table].items(): self.selected_columns[table][column] = self.tree_ctrl_db.IsSelected(column_item) def on_change_mrn_uid(self, evt): self.change_or_delete_dlg(ChangePatientIdentifierDialog) def on_delete_study(self, evt): # TODO: Needs an Are you sure? dialog self.change_or_delete_dlg(DeletePatientDialog) def change_or_delete_dlg(self, class_type): selected_data = self.data_query_results.selected_row_data if selected_data: class_type(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: class_type() def on_reimport(self, evt): selected_data = self.data_query_results.selected_row_data if selected_data: ReimportDialog(mrn=selected_data[0][0], study_instance_uid=selected_data[0][1]) else: ReimportDialog() @staticmethod def on_edit_db(evt): EditDatabaseDialog() @staticmethod def on_calculations(evt): CalculationsDialog() def on_rebuild_db(self, evt): RebuildDB(self) def on_delete_all_data(self, evt): DeleteAllData(self) def on_export_csv(self, evt): save_data_to_file(self, "Export Data Table to CSV", self.data_query_results.get_csv()) def on_remap_roi_names(self, evt): RemapROIFrame(self.roi_map, remap_all=True) def sort_query_results(self, evt): self.data_query_results.sort_table(evt)
class 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
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()
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)
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)
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()
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)
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()
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'])