class ProgressDialog(wx.Dialog): """Dialog to show progress for certain long-running events.""" def __init__(self): wx.Dialog.__init__(self) def Init(self, res, title=None): """Method called after the dialog has been initialized.""" # Initialize controls self.SetTitle(title) self.lblProgressLabel = XRCCTRL(self, 'lblProgressLabel') self.lblProgress = XRCCTRL(self, 'lblProgress') self.gaugeProgress = XRCCTRL(self, 'gaugeProgress') self.lblProgressPercent = XRCCTRL(self, 'lblProgressPercent') def OnUpdateProgress(self, num, length, message=''): """Update the process interface elements.""" if not length: percentDone = 0 else: percentDone = int(100 * (num) / length) self.gaugeProgress.SetValue(percentDone) self.lblProgressPercent.SetLabel(str(percentDone)) self.lblProgress.SetLabel(message) # End the dialog since we are done with the import process if (message == 'Done'): self.EndModal(wx.ID_OK)
class Panel(ObjectPanel.Panel): """ A wx.Panel for displaying and editing Categories """ def __init__(self, parent, id=wx.ID_ANY, style=wx.EXPAND): #load from XRC, need to use two-stage create pre = wx.PrePanel() res = gui.XrcUtilities.XmlResource('./gui/xrc/CategoryPanel.xrc') res.LoadOnPanel(pre, parent, "CategoryPanel") self.PostCreate(pre) ObjectPanel.Panel.Setup(self) self.OnCreate() def OnCreate(self): self.name_field = XRCCTRL(self, "name_field") self.desc_field = XRCCTRL(self, "desc_field") self.desc_field.Bind(wx.EVT_TEXT, self.CreateAttributeMonitor('description')) self.loaded = False def LoadObject(self, node): self.loading = True self.node = node self.object = node.GetObject() self.node.visible = True self.name_field.SetLabel(str(self.object.name)) self.desc_field.SetValue(str(self.object.description)) self.desc_field.attr_name = 'description' if self.object.errors.has_key('description'): print "Error in description!" self.SetErrorLabel('description', self.object.errors['description']) self.loaded = True self.loading = False self.Show() return self def cleanup(self): self.node.visible = False self.Hide() self.node.ClearObject() self.object = None self.loaded = False
class TripExportDialog(wx.Dialog): def __init__(self): pre = wx.PreDialog() self.PostCreate(pre) def Init(self, plan): self.output_path = '' self.drop_type = XRCCTRL(self, "drop_type") self.txt_prefix = XRCCTRL(self, "txt_prefix") self.txt_prefix.SetValue(plan.get_name()) self.label_folder = XRCCTRL(self, "label_folder") wx.EVT_BUTTON(self, XRCID("btn_cancel"), self.close) wx.EVT_BUTTON(self, XRCID("btn_browse"), self.browse_folder) wx.EVT_BUTTON(self, XRCID("btn_generate"), self.generate) self.plan = plan pub.subscribe(self.on_patient_updated, "patient.loaded") pub.subscribe(self.on_export_voxelplan_changed, "general.export.voxelplan") pub.sendMessage("settings.value.request", "general.export.voxelplan") pub.sendMessage("patient.request", None) def on_export_voxelplan_changed(self, msg): if not msg.data is None: self.output_path = msg.data self.label_folder.SetLabel(self.output_path) def on_patient_updated(self, msg): self.data = msg.data def browse_folder(self, evt): dlg = wx.DirDialog(self, defaultPath=self.output_path, message="Choose where the plan should be placed") if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.output_path = path pub.sendMessage("settings.value.updated", {"general.export.voxelplan": path}) self.label_folder.SetLabel(path) def generate(self, evt): idx = self.drop_type.GetSelection() file_prefix = self.txt_prefix.GetValue() if len(file_prefix) == 0: raise InputError("File Prefix should be specified") if not hasattr(self, "output_path"): raise InputError("Output folder should be specified") path = os.path.join(self.output_path, file_prefix) exec_path = path + ".exec" ctx = self.data.get_images().get_voxelplan() if idx == 0: self.plan.save_data(ctx, path) self.plan.save_exec(ctx, exec_path) elif idx == 1: self.plan.save_exec(ctx, exec_path) elif idx == 2: self.plan.save_data(ctx, path) self.Close() def close(self, evt): self.Close()
class Panel(ObjectPanel.Panel): """ A wx.Panel for displaying and editing Properties """ def __init__(self, parent, id=wx.ID_ANY, style=wx.EXPAND): #load from XRC, need to use two-stage create pre = wx.PrePanel() res = gui.XrcUtilities.XmlResource('./gui/xrc/PropertyPanel.xrc') res.LoadOnPanel(pre, parent, "PropertyPanel") self.PostCreate(pre) ObjectPanel.Panel.Setup(self) self.OnCreate() def OnCreate(self): self.name_field = XRCCTRL(self, "name_field") self.rank_field = XRCCTRL(self, "rank_field") self.desc_field = XRCCTRL(self, "desc_field") self.disp_field = XRCCTRL(self, "disp_field") self.tpcl_disp_stc = XRCCTRL(self, "tpcl_disp_stc") self.tpcl_req_stc = XRCCTRL(self, "tpcl_req_stc") self.cat_choice = XRCCTRL(self, "cat_choice") self.desc_field.Bind(wx.EVT_TEXT, self.CreateAttributeMonitor('description')) self.rank_field.Bind(wx.EVT_TEXT, self.CreateAttributeMonitor('rank')) self.disp_field.Bind(wx.EVT_TEXT, self.CreateAttributeMonitor('display_text')) self.tpcl_disp_stc.Bind(wx.stc.EVT_STC_CHANGE, self.CreateAttributeMonitor('tpcl_display')) self.tpcl_req_stc.Bind(wx.stc.EVT_STC_CHANGE, self.CreateAttributeMonitor('tpcl_requires')) self.cat_choice.Bind(wx.EVT_CHECKLISTBOX, self.CreateAttributeMonitor('categories')) self.loaded = False def LoadObject(self, node): self.loading = True self.node = node self.object = node.GetObject() self.node.visible = True self.name_field.SetLabel(str(self.object.name)) self.rank_field.SetValue(str(self.object.rank)) if self.object.errors.has_key('rank'): print "Error in rank!" self.SetErrorLabel('rank', self.object.errors['rank']) self.desc_field.SetValue(str(self.object.description)) if self.object.errors.has_key('description'): print "Error in description!" self.SetErrorLabel('description', self.object.errors['description']) self.disp_field.SetValue(str(self.object.display_text)) if self.object.errors.has_key('display_text'): print "Error in display_text!" self.SetErrorLabel('display_text', self.object.errors['display_text']) self.tpcl_disp_stc.SetText(str(self.object.tpcl_display)) if self.object.errors.has_key('tpcl_display'): print "Error in tpcl_display!" self.SetErrorLabel('tpcl_display', self.object.errors['tpcl_display']) self.tpcl_req_stc.SetText(str(self.object.tpcl_requires)) if self.object.errors.has_key('tpcl_requires'): print "Error in tpcl_requires!" self.SetErrorLabel('tpcl_requires', self.object.errors['tpcl_requires']) #fill the category choice box self.cat_choice.Clear() for catnode in self.node.object_database.getObjectsOfType('Category'): idx = self.cat_choice.Append(catnode.name) if catnode.name in self.object.categories: self.cat_choice.Check(idx) if self.object.errors.has_key('categories'): print "Error in categories!" self.SetErrorLabel('categories', self.object.errors['categories']) self.loaded = True self.loading = False self.Show() return self def Destroy(self): self.Hide() def ReallyDestroy(self): wx.Panel.Destroy(self) def cleanup(self): self.CleanupErrorLabels() self.node.visible = False self.Hide() self.node.ClearObject() self.object = None self.loaded = False
class MainFrame(wx.Frame): def __init__(self, parent, id, title, res): # Initialize logging logger = logging.getLogger('dicompyler') # Configure the exception hook to process threads as well self.InstallThreadExcepthook() # Remap the exception hook so that we can log and display exceptions def LogExcepthook(*exc_info): # Log the exception text = "".join(traceback.format_exception(*exc_info)) logger.error("Unhandled exception: %s", text) pub.sendMessage('logging.exception', text) sys.excepthook = LogExcepthook # Modify the logging system from pydicom to capture important messages pydicom_logger = logging.getLogger('pydicom') for l in pydicom_logger.handlers: pydicom_logger.removeHandler(l) # Add file logger logpath = os.path.join(guiutil.get_data_dir(), 'logs') if not os.path.exists(logpath): os.makedirs(logpath) self.fh = logging.handlers.RotatingFileHandler(os.path.join( logpath, 'dicompyler.log'), maxBytes=524288, backupCount=7) self.fh.setFormatter( logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s')) self.fh.setLevel(logging.WARNING) logger.addHandler(self.fh) pydicom_logger.addHandler(self.fh) # Add console logger if not frozen if not util.main_is_frozen(): self.ch = logging.StreamHandler() self.ch.setFormatter( logging.Formatter('%(levelname)s: %(message)s')) self.ch.setLevel(logging.WARNING) logger.addHandler(self.ch) pydicom_logger.addHandler(self.ch) # Otherwise if frozen, send stdout/stderror to /dev/null since # logging the messages seems to cause instability due to recursion else: devnull = open(os.devnull, 'w') sys.stdout = devnull sys.stderr = devnull # Set the window size if guiutil.IsMac(): size = (900, 700) else: size = (850, 625) wx.Frame.__init__(self, parent, id, title, pos=wx.DefaultPosition, size=size, style=wx.DEFAULT_FRAME_STYLE) # Set up the status bar self.sb = self.CreateStatusBar(3) # set up resource file and config file self.res = res # Set window icon if not guiutil.IsMac(): self.SetIcon(guiutil.get_icon()) # Load the main panel for the program self.panelGeneral = self.res.LoadPanel(self, 'panelGeneral') # Initialize the General panel controls self.notebook = XRCCTRL(self, 'notebook') self.notebookTools = XRCCTRL(self, 'notebookTools') self.lblPlanName = XRCCTRL(self, 'lblPlanName') self.lblRxDose = XRCCTRL(self, 'lblRxDose') self.lblPatientName = XRCCTRL(self, 'lblPatientName') self.lblPatientID = XRCCTRL(self, 'lblPatientID') self.lblPatientGender = XRCCTRL(self, 'lblPatientGender') self.lblPatientDOB = XRCCTRL(self, 'lblPatientDOB') self.choiceStructure = XRCCTRL(self, 'choiceStructure') self.lblStructureVolume = XRCCTRL(self, 'lblStructureVolume') self.lblStructureMinDose = XRCCTRL(self, 'lblStructureMinDose') self.lblStructureMaxDose = XRCCTRL(self, 'lblStructureMaxDose') self.lblStructureMeanDose = XRCCTRL(self, 'lblStructureMeanDose') self.cclbStructures = guiutil.ColorCheckListBox( self.notebookTools, 'structure') self.cclbIsodoses = guiutil.ColorCheckListBox(self.notebookTools, 'isodose') # Modify the control and font size on Mac controls = [self.notebookTools, self.choiceStructure] if guiutil.IsMac(): font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetPointSize(10) for control in controls: control.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) control.SetFont(font) # Setup the layout for the frame mainGrid = wx.BoxSizer(wx.VERTICAL) hGrid = wx.BoxSizer(wx.HORIZONTAL) if guiutil.IsMac(): hGrid.Add(self.panelGeneral, 1, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTRE, border=4) else: hGrid.Add(self.panelGeneral, 1, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTRE) mainGrid.Add(hGrid, 1, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTRE) # Load the menu for the frame menuMain = self.res.LoadMenuBar('menuMain') # If we are running on Mac OS X, alter the menu location if guiutil.IsMac(): wx.App_SetMacAboutMenuItemId(XRCID('menuAbout')) wx.App_SetMacPreferencesMenuItemId(XRCID('menuPreferences')) wx.App_SetMacExitMenuItemId(XRCID('menuExit')) # Set the menu as the default menu for this frame self.SetMenuBar(menuMain) # Setup Tools menu self.menuShowLogs = menuMain.FindItemById( XRCID('menuShowLogs')).GetMenu() self.menuPlugins = menuMain.FindItemById( XRCID('menuPluginManager')).GetMenu() # Setup Import menu self.menuImport = menuMain.FindItemById( XRCID('menuImportPlaceholder')).GetMenu() self.menuImport.Delete( menuMain.FindItemById(XRCID('menuImportPlaceholder')).GetId()) self.menuImportItem = menuMain.FindItemById(XRCID('menuImport')) self.menuImportItem.Enable(False) # Setup Export menu self.menuExport = menuMain.FindItemById( XRCID('menuExportPlaceholder')).GetMenu() self.menuExport.Delete( menuMain.FindItemById(XRCID('menuExportPlaceholder')).GetId()) self.menuExportItem = menuMain.FindItemById(XRCID('menuExport')) self.menuExportItem.Enable(False) # Bind menu events to the proper methods wx.EVT_MENU(self, XRCID('menuOpen'), self.OnOpenPatient) wx.EVT_MENU(self, XRCID('menuExit'), self.OnClose) wx.EVT_MENU(self, XRCID('menuPreferences'), self.OnPreferences) wx.EVT_MENU(self, XRCID('menuShowLogs'), self.OnShowLogs) wx.EVT_MENU(self, XRCID('menuPluginManager'), self.OnPluginManager) wx.EVT_MENU(self, XRCID('menuAbout'), self.OnAbout) wx.EVT_MENU(self, XRCID('menuHomepage'), self.OnHomepage) wx.EVT_MENU(self, XRCID('menuLicense'), self.OnLicense) # Load the toolbar for the frame toolbarMain = self.res.LoadToolBar(self, 'toolbarMain') self.SetToolBar(toolbarMain) self.toolbar = self.GetToolBar() # Setup main toolbar controls folderbmp = wx.Bitmap(util.GetResourcePath('folder_user.png')) self.maintools = [{ 'label': "Open Patient", 'bmp': folderbmp, 'shortHelp': "Open Patient...", 'eventhandler': self.OnOpenPatient }] for m, tool in enumerate(self.maintools): self.toolbar.AddLabelTool(m + 1, tool['label'], tool['bmp'], shortHelp=tool['shortHelp']) self.Bind(wx.EVT_TOOL, tool['eventhandler'], id=m + 1) self.toolbar.Realize() # Bind interface events to the proper methods wx.EVT_CHOICE(self, XRCID('choiceStructure'), self.OnStructureSelect) self.Bind(wx.EVT_ACTIVATE, self.OnActivate) self.Bind(wx.EVT_CLOSE, self.OnClose) self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged) # Events to work around a focus bug in Windows self.notebook.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.notebook.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) self.notebookTools.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.notebookTools.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) self.SetSizer(mainGrid) self.Layout() #Set the Minumum size self.SetMinSize(size) self.Centre(wx.BOTH) # Initialize the welcome notebook tab panelWelcome = self.res.LoadPanel(self.notebook, 'panelWelcome') self.notebook.AddPage(panelWelcome, 'Welcome') # Set the version on the welcome notebook tab XRCCTRL(self, 'lblVersion').SetLabel('Version ' + __version__) # Initialize the tools notebook self.notebookTools.AddPage(self.cclbStructures, 'Structures') self.notebookTools.AddPage(self.cclbIsodoses, 'Isodoses') # Create the data folder datapath = guiutil.get_data_dir() if not os.path.exists(datapath): os.mkdir(datapath) # Initialize the preferences if guiutil.IsMac(): self.prefmgr = preferences.PreferencesManager(parent=None, appname='dicompyler') else: self.prefmgr = preferences.PreferencesManager(parent=None, appname='dicompyler', name='Options') sp = wx.StandardPaths.Get() self.generalpreftemplate = [{ 'DICOM Import Settings': [{ 'name': 'Import Location', 'type': 'choice', 'values': ['Remember Last Used', 'Always Use Default'], 'default': 'Remember Last Used', 'callback': 'general.dicom.import_location_setting' }, { 'name': 'Default Location', 'type': 'directory', 'default': unicode(sp.GetDocumentsDir()), 'callback': 'general.dicom.import_location' }] }, { 'Plugin Settings': [{ 'name': 'User Plugins Location', 'type': 'directory', 'default': unicode(os.path.join(datapath, 'plugins')), 'callback': 'general.plugins.user_plugins_location', 'restart': True }] }, { 'Calculation Settings': [{ 'name': 'DVH Calculation', 'type': 'choice', 'values': ['Use RT Dose DVH if Present', 'Always Recalculate DVH'], 'default': 'Use RT Dose DVH if Present', 'callback': 'general.calculation.dvh_recalc' }] }, { 'Advanced Settings': [{ 'name': 'Enable Detailed Logging', 'type': 'checkbox', 'default': False, 'callback': 'general.advanced.detailed_logging' }] }] self.preftemplate = [{'General': self.generalpreftemplate}] pub.sendMessage('preferences.updated.template', self.preftemplate) # Initialize variables self.ptdata = {} # Set up pubsub pub.subscribe(self.OnLoadPatientData, 'patient.updated.raw_data') pub.subscribe(self.OnStructureCheck, 'colorcheckbox.checked.structure') pub.subscribe(self.OnStructureUncheck, 'colorcheckbox.unchecked.structure') pub.subscribe(self.OnIsodoseCheck, 'colorcheckbox.checked.isodose') pub.subscribe(self.OnIsodoseUncheck, 'colorcheckbox.unchecked.isodose') pub.subscribe(self.OnUpdatePlugins, 'general.plugins.user_plugins_location') pub.subscribe(self.OnUpdatePreferences, 'general') pub.subscribe(self.OnUpdateStatusBar, 'main.update_statusbar') pub.subscribe(self.OnOpenPatient, 'dicomgui.show') # Send a message to the logging system to turn on/off detailed logging pub.sendMessage('preferences.requested.value', 'general.advanced.detailed_logging') # Create the default user plugin path self.userpluginpath = os.path.join(datapath, 'plugins') if not os.path.exists(self.userpluginpath): os.mkdir(self.userpluginpath) # Load and initialize plugins self.plugins = [] self.pluginsDisabled = [] pub.sendMessage('preferences.requested.value', 'general.plugins.user_plugins_location') pub.sendMessage('preferences.requested.value', 'general.calculation.dvh_recalc') pub.sendMessage('preferences.requested.value', 'general.plugins.disabled_list') pub.sendMessage('preferences.requested.values', 'general.window') ########################### Patient Loading Functions ########################## def OnOpenPatient(self, evt): """Load and show the Dicom RT Importer dialog box.""" dicomgui.ImportDicom(self) def OnLoadPatientData(self, msg): """Update and load the patient data.""" # Skip loading if the dataset is empty or if the dataset is the same if (not len(msg.data)) or (self.ptdata == msg.data): return else: self.ptdata = msg.data # Delete the previous notebook pages self.notebook.DeleteAllPages() # Delete the previous toolbar items for t in range(0, self.toolbar.GetToolsCount()): # Only delete the plugin toolbar items if (t >= len(self.maintools)): self.toolbar.DeleteToolByPos(len(self.maintools)) # Delete the previous plugin menus if len(self.menuDict): self.menuPlugins.Delete(wx.ID_SEPARATOR) for menuid, menu in self.menuDict.iteritems(): self.menuPlugins.Delete(menuid) self.menuDict = {} # Delete the previous export menus if len(self.menuExportDict): self.menuExportItem.Enable(False) for menuid, menu in self.menuExportDict.iteritems(): self.menuExport.Delete(menuid) self.menuExportDict = {} # Reset the preferences template self.preftemplate = [{'General': self.generalpreftemplate}] # Initialize the list of subplugins subplugins = [] # Set up the plugins for each plugin entry point of dicompyler for i, plugin in enumerate(self.plugins): # Skip plugin if it doesn't contain the required dictionary # or actually is a proper Python module p = plugin['plugin'] if not hasattr(p, 'pluginProperties') or \ (p.__name__ in self.pluginsDisabled): continue props = p.pluginProperties() # Only load plugin versions that are qualified if (props['plugin_version'] == 1): # Check whether the plugin can view the loaded DICOM data add = False if len(props['min_dicom']): for key in props['min_dicom']: if (key == 'rxdose'): pass elif key in self.ptdata.keys(): add = True else: add = False break # Plugin can view all DICOM data so always load it else: add = True # Initialize the plugin if add: # Load the main panel plugins if (props['plugin_type'] == 'main'): plugin = p.pluginLoader(self.notebook) self.notebook.AddPage(plugin, props['name']) # Load the menu plugins elif (props['plugin_type'] == 'menu'): if not len(self.menuDict): self.menuPlugins.AppendSeparator() self.menuDict[100 + i] = self.menuPlugins.Append( 100 + i, props['name'] + '...') plugin = p.plugin(self) wx.EVT_MENU(self, 100 + i, plugin.pluginMenu) # Load the export menu plugins elif (props['plugin_type'] == 'export'): if not len(self.menuExportDict): self.menuExportItem.Enable(True) self.menuExportDict[200 + i] = self.menuExport.Append( 200 + i, props['menuname']) plugin = p.plugin(self) wx.EVT_MENU(self, 200 + i, plugin.pluginMenu) # If a sub-plugin, mark it to be initialized later else: subplugins.append(p) continue # Add the plugin preferences if they exist if hasattr(plugin, 'preferences'): self.preftemplate.append( {props['name']: plugin.preferences}) pub.sendMessage('preferences.updated.template', self.preftemplate) # Load the subplugins and notify the parent plugins for s in subplugins: props = s.pluginProperties() msg = 'plugin.loaded.' + props['plugin_type'] + '.' + s.__name__ pub.sendMessage(msg, s) dlgProgress = guiutil.get_progress_dialog(self, "Loading Patient Data...") self.t = threading.Thread(target=self.LoadPatientDataThread, args=(self, self.ptdata, dlgProgress.OnUpdateProgress, self.OnUpdatePatientData)) self.t.start() dlgProgress.ShowModal() if dlgProgress: dlgProgress.Destroy() def LoadPatientDataThread(self, parent, ptdata, progressFunc, updateFunc): """Thread to load the patient data.""" # Call the progress function to update the gui wx.CallAfter(progressFunc, 0, 0, 'Processing patient data...') patient = {} if not ptdata.has_key('images'): patient.update(dp(ptdata.values()[0]).GetDemographics()) if ptdata.has_key('rtss'): wx.CallAfter(progressFunc, 20, 100, 'Processing RT Structure Set...') patient['structures'] = dp(ptdata['rtss']).GetStructures() if ptdata.has_key('rtplan'): wx.CallAfter(progressFunc, 40, 100, 'Processing RT Plan...') patient['plan'] = dp(ptdata['rtplan']).GetPlan() if ptdata.has_key('rtdose'): wx.CallAfter(progressFunc, 60, 100, 'Processing RT Dose...') patient['dvhs'] = dp(ptdata['rtdose']).GetDVHs() patient['dose'] = dp(ptdata['rtdose']) if ptdata.has_key('images'): wx.CallAfter(progressFunc, 80, 100, 'Processing Images...') if not patient.has_key('id'): patient.update(dp(ptdata['images'][0]).GetDemographics()) patient['images'] = [] for image in ptdata['images']: patient['images'].append(dp(image)) if ptdata.has_key('rxdose'): if not patient.has_key('plan'): patient['plan'] = {} patient['plan']['rxdose'] = ptdata['rxdose'] # if the min/max/mean dose was not present, calculate it and save it for each structure wx.CallAfter(progressFunc, 90, 100, 'Processing DVH data...') if ('dvhs' in patient) and ('structures' in patient): # If the DVHs are not present, calculate them i = 0 for key, structure in patient['structures'].iteritems(): # Only calculate DVHs if they are not present for the structure # or recalc all DVHs if the preference is set if ((not (key in patient['dvhs'].keys())) or (self.dvhRecalc == 'Always Recalculate DVH')): # Only calculate DVHs for structures, not applicators # and only if the dose grid is present if ((structure['name'].startswith('Applicator')) or (not "PixelData" in patient['dose'].ds)): continue wx.CallAfter( progressFunc, 10 * i / len(patient['structures']) + 90, 100, 'Calculating DVH for ' + structure['name'] + '...') # Limit DVH bins to 500 Gy due to high doses in brachy dvh = dvhcalc.get_dvh(structure, patient['dose'], 50000) if len(dvh['data']): patient['dvhs'][key] = dvh i += 1 for key, dvh in patient['dvhs'].iteritems(): self.CalculateDoseStatistics(dvh, ptdata['rxdose']) wx.CallAfter(progressFunc, 100, 100, 'Done') wx.CallAfter(updateFunc, patient) def CalculateDoseStatistics(self, dvh, rxdose): """Calculate the dose statistics for the given DVH and rx dose.""" sfdict = { 'min': dvhdoses.get_dvh_min, 'mean': dvhdoses.get_dvh_mean, 'max': dvhdoses.get_dvh_max } for stat, func in sfdict.iteritems(): # Only calculate stat if the stat was not calculated previously (-1) if dvh[stat] == -1: dvh[stat] = 100 * func(dvh['data'] * dvh['scaling']) / rxdose return dvh def OnUpdatePatientData(self, patient): """Update the patient data in the main program interface.""" self.PopulateDemographics(patient) self.structures = {} if patient.has_key('structures'): self.structures = patient['structures'] self.PopulateStructures() if patient.has_key('plan'): self.PopulatePlan(patient['plan']) else: self.PopulatePlan({}) if (patient.has_key('dose') and patient.has_key('plan')): self.PopulateIsodoses(patient.has_key('images'), patient['plan'], patient['dose']) else: self.PopulateIsodoses(patient.has_key('images'), {}, {}) if patient.has_key('dvhs'): self.dvhs = patient['dvhs'] else: self.dvhs = {} # Re-publish the raw data pub.sendMessage('patient.updated.raw_data', self.ptdata) # Publish the parsed data pub.sendMessage('patient.updated.parsed_data', patient) def PopulateStructures(self): """Populate the structure list.""" self.cclbStructures.Clear() self.structureList = {} for id, structure in iter(sorted(self.structures.iteritems())): # Only append structures, don't include applicators if not (structure['name'].startswith('Applicator')): self.cclbStructures.Append(structure['name'], structure, structure['color'], refresh=False) # Refresh the structure list manually since we didn't want it to refresh # after adding each structure self.cclbStructures.Layout() self.choiceStructure.Clear() self.choiceStructure.Enable(False) self.OnStructureUnselect() def PopulateIsodoses(self, has_images, plan, dose): """Populate the isodose list.""" self.cclbIsodoses.Clear() self.isodoseList = {} if (has_images and len(plan) and "PixelData" in dose.ds): dosedata = dose.GetDoseData() dosemax = int(dosedata['dosemax'] * dosedata['dosegridscaling'] * 10000 / plan['rxdose']) self.isodoses = [{ 'level': dosemax, 'color': wx.Colour(120, 0, 0), 'name': 'Max' }, { 'level': 102, 'color': wx.Colour(170, 0, 0) }, { 'level': 100, 'color': wx.Colour(238, 69, 0) }, { 'level': 98, 'color': wx.Colour(255, 165, 0) }, { 'level': 95, 'color': wx.Colour(255, 255, 0) }, { 'level': 90, 'color': wx.Colour(0, 255, 0) }, { 'level': 80, 'color': wx.Colour(0, 139, 0) }, { 'level': 70, 'color': wx.Colour(0, 255, 255) }, { 'level': 50, 'color': wx.Colour(0, 0, 255) }, { 'level': 30, 'color': wx.Colour(0, 0, 128) }] for isodose in self.isodoses: # Calculate the absolute dose value name = ' / ' + unicode("%.6g" % (float(isodose['level']) * float(plan['rxdose']) / 100)) + \ ' cGy' if isodose.has_key('name'): name = name + ' [' + isodose['name'] + ']' self.cclbIsodoses.Append(str(isodose['level']) + ' %' + name, isodose, isodose['color'], refresh=False) # Refresh the isodose list manually since we didn't want it to refresh # after adding each isodose self.cclbIsodoses.Layout() def PopulateDemographics(self, demographics): """Populate the patient demographics.""" self.lblPatientName.SetLabel(demographics['name']) self.lblPatientID.SetLabel(demographics['id']) self.lblPatientGender.SetLabel(demographics['gender']) self.lblPatientDOB.SetLabel(demographics['dob']) def PopulatePlan(self, plan): """Populate the patient's plan information.""" if (len(plan) and not plan['rxdose'] == 1): if plan.has_key('name'): if len(plan['name']): self.lblPlanName.SetLabel(plan['name']) elif len(plan['label']): self.lblPlanName.SetLabel(plan['label']) self.lblRxDose.SetLabel(unicode("%.6g" % plan['rxdose'])) else: self.lblPlanName.SetLabel('-') self.lblRxDose.SetLabel('-') ############################## Structure Functions ############################# def OnStructureCheck(self, msg): """Load the properties of the currently checked structures.""" structure = msg.data # Get the structure number id = structure['data']['id'] structure['data']['color'] = structure['color'].Get() # Make sure that the volume has been calculated for each structure # before setting it if not self.structures[id].has_key('volume'): # Use the volume units from the DVH if they are absolute volume if self.dvhs.has_key(id) and (self.dvhs[id]['volumeunits'] == 'CM3'): self.structures[id]['volume'] = self.dvhs[id]['data'][0] # Otherwise calculate the volume from the structure data else: self.structures[id]['volume'] = dvhdata.CalculateVolume( self.structures[id]) structure['data']['volume'] = self.structures[id]['volume'] self.structureList[id] = structure['data'] # Populate the structure choice box with the checked structures self.choiceStructure.Enable() i = self.choiceStructure.Append(structure['data']['name']) self.choiceStructure.SetClientData(i, id) # Select the first structure self.OnStructureSelect() pub.sendMessage('structures.checked', self.structureList) def OnStructureUncheck(self, msg): """Remove the unchecked structures.""" structure = msg.data # Get the structure number id = structure['data']['id'] # Remove the structure from the structure list if self.structureList.has_key(id): del self.structureList[id] # Remove the structure from the structure choice box for n in range(self.choiceStructure.GetCount()): if (id == self.choiceStructure.GetClientData(n)): # Save if the currently selected item's position currSelection = self.choiceStructure.GetSelection() self.choiceStructure.Delete(n) break # If the currently selected item will be deleted, # select the last item instead if (n == currSelection): if (self.choiceStructure.GetCount() >= 1): self.OnStructureSelect() # Disable the control if it is the last item if (self.choiceStructure.GetCount() == 0): self.choiceStructure.Enable(False) self.OnStructureUnselect() pub.sendMessage('structures.checked', self.structureList) def OnStructureSelect(self, evt=None): """Load the properties of the currently selected structure.""" if (evt == None): self.choiceStructure.SetSelection(0) choiceItem = 0 else: choiceItem = evt.GetInt() # Load the structure id chosen from the choice control id = self.choiceStructure.GetClientData(choiceItem) pub.sendMessage('structure.selected', {'id': id}) self.lblStructureVolume.SetLabel( str(self.structures[id]['volume'])[0:7]) # make sure that the dvh has been calculated for each structure # before setting it if self.dvhs.has_key(id): self.lblStructureMinDose.SetLabel("%.3f" % self.dvhs[id]['min']) self.lblStructureMaxDose.SetLabel("%.3f" % self.dvhs[id]['max']) self.lblStructureMeanDose.SetLabel("%.3f" % self.dvhs[id]['mean']) else: self.lblStructureMinDose.SetLabel('-') self.lblStructureMaxDose.SetLabel('-') self.lblStructureMeanDose.SetLabel('-') def OnStructureUnselect(self): """Clear the properties of the selected structure.""" pub.sendMessage('structures.selected', {'id': None}) self.lblStructureVolume.SetLabel('-') self.lblStructureMinDose.SetLabel('-') self.lblStructureMaxDose.SetLabel('-') self.lblStructureMeanDose.SetLabel('-') ############################### Isodose Functions ############################## def OnIsodoseCheck(self, msg): """Load the properties of the currently checked isodoses.""" isodose = msg.data self.isodoseList[isodose['data']['level']] = isodose pub.sendMessage('isodoses.checked', self.isodoseList) def OnIsodoseUncheck(self, msg): """Remove the unchecked isodoses.""" isodose = msg.data id = isodose['data']['level'] # Remove the isodose from the isodose list if self.isodoseList.has_key(id): del self.isodoseList[id] pub.sendMessage('isodoses.checked', self.isodoseList) ################################ Other Functions ############################### def InstallThreadExcepthook(self): """Workaround for sys.excepthook thread bug from Jonathan Ellis (http://bugs.python.org/issue1230540). Call once from __main__ before creating any threads. If using psyco, call psyco.cannotcompile(threading.Thread.run) since this replaces a new-style class method.""" run_old = threading.Thread.run def Run(*args, **kwargs): try: run_old(*args, **kwargs) except (KeyboardInterrupt, SystemExit): raise except: sys.excepthook(*sys.exc_info()) threading.Thread.run = Run def OnUpdatePreferences(self, msg): """When the preferences change, update the values.""" if (msg.topic[1] == 'calculation') and (msg.topic[2] == 'dvh_recalc'): self.dvhRecalc = msg.data elif (msg.topic[1] == 'advanced') and \ (msg.topic[2] == 'detailed_logging'): # Enable logging at the debug level if the value is set if msg.data: self.fh.setLevel(logging.DEBUG) if not util.main_is_frozen(): self.ch.setLevel(logging.DEBUG) else: self.fh.setLevel(logging.WARNING) if not util.main_is_frozen(): self.ch.setLevel(logging.WARNING) elif (msg.topic[1] == 'plugins') and (msg.topic[2] == 'disabled_list'): self.pluginsDisabled = msg.data elif (msg.topic[1] == 'window'): if msg.topic[2] == 'maximized': self.Maximize(msg.data) elif msg.topic[2] == 'size': if not self.IsMaximized(): self.SetSize(tuple(msg.data)) elif msg.topic[2] == 'position': if not self.IsMaximized(): self.SetPosition(tuple(msg.data)) def OnUpdatePlugins(self, msg): """Update the location of the user plugins and load all plugins.""" self.userpluginpath = msg.data # Load the plugins only if they haven't been loaded previously if not len(self.plugins): self.plugins = plugin.import_plugins(self.userpluginpath) self.menuDict = {} self.menuImportDict = {} self.menuExportDict = {} # Set up the import plugins for dicompyler for i, pg in enumerate(self.plugins): # Skip plugin if it doesn't contain the required dictionary # or actually is a proper Python module or is disabled p = pg['plugin'] if not hasattr(p, 'pluginProperties') or \ (p.__name__ in self.pluginsDisabled): continue props = p.pluginProperties() # Only load plugin versions that are qualified if ((props['plugin_version'] == 1) and (props['plugin_type'] == 'import')): self.menuImportItem.Enable(True) # Load the import menu plugins if not len(self.menuImportDict): self.menuImportItem.Enable(True) self.menuImportDict[300 + i] = self.menuImport.Append( 300 + i, props['menuname']) pi = p.plugin(self) self.Bind(wx.EVT_MENU, pi.pluginMenu, id=300 + i) # If the import plugin has toolbar items, display them if hasattr(pi, 'tools'): for t, tool in enumerate(pi.tools): self.maintools.append(tool) self.toolbar.AddLabelTool( (300 + i) * 10 + t, tool['label'], tool['bmp'], shortHelp=tool['shortHelp']) self.Bind(wx.EVT_TOOL, tool['eventhandler'], id=(300 + i) * 10 + t) self.toolbar.Realize() def OnUpdateStatusBar(self, msg): """Update the status bar text.""" for k, v in msg.data.iteritems(): self.sb.SetStatusText(unicode(v), k) def OnPageChanged(self, evt): """Notify each notebook tab whether it has the focus or not.""" # Determine the new tab new = evt.GetSelection() page = self.notebook.GetPage(new) # Notify the new tab that it has focus if hasattr(page, 'OnFocus'): page.OnFocus() # If the tab has toolbar items, display them if hasattr(page, 'tools'): for t, tool in enumerate(page.tools): self.toolbar.AddLabelTool((new + 1) * 10 + t, tool['label'], tool['bmp'], shortHelp=tool['shortHelp']) self.Bind(wx.EVT_TOOL, tool['eventhandler'], id=(new + 1) * 10 + t) self.toolbar.Realize() # For all other tabs, notify that they don't have focus anymore for i in range(self.notebook.GetPageCount()): if not (new == i): page = self.notebook.GetPage(i) if hasattr(page, 'OnUnfocus'): page.OnUnfocus() # Delete all other toolbar items if hasattr(page, 'tools'): for t, tool in enumerate(page.tools): self.toolbar.DeleteTool((i + 1) * 10 + t) evt.Skip() def OnKeyDown(self, evt): """Capture the keypress when the notebook tab is focused. Currently this is used to workaround a bug in Windows since the notebook tab instead of the panel receives focus.""" if guiutil.IsMSWindows(): pub.sendMessage('main.key_down', evt) def OnMouseWheel(self, evt): """Capture the mousewheel event when the notebook tab is focused. Currently this is used to workaround a bug in Windows since the notebook tab instead of the panel receives focus.""" if guiutil.IsMSWindows(): pub.sendMessage('main.mousewheel', evt) def OnPreferences(self, evt): """Load and show the Preferences dialog box.""" self.prefmgr.Show() def OnShowLogs(self, evt): """Open and display the logs folder.""" util.open_path(os.path.join(guiutil.get_data_dir(), 'logs')) def OnPluginManager(self, evt): """Load and show the Plugin Manager dialog box.""" self.pm = plugin.PluginManager(self, self.plugins, self.pluginsDisabled) def OnAbout(self, evt): # First we create and fill the info object info = wx.AboutDialogInfo() info.Name = "dicompyler" info.Version = __version__ info.Copyright = u"© 2009-2014 Aditya Panchal" credits = util.get_credits() info.Developers = credits['developers'] info.Artists = credits['artists'] desc = "Extensible radiation therapy research platform and viewer for DICOM and DICOM RT." + \ "\n\ndicompyler is released under a BSD license.\n" + \ "See the Help menu for license information." info.Description = desc if guiutil.IsGtk(): info.WebSite = "http://code.google.com/p/dicompyler/" # Then we call wx.AboutBox giving it that info object wx.AboutBox(info) def OnHomepage(self, evt): """Show the homepage for dicompyler.""" webbrowser.open_new_tab("http://code.google.com/p/dicompyler/") def OnLicense(self, evt): """Show the license document in a new dialog.""" f = open(util.get_text_resources("license.txt"), "rU") msg = f.read() f.close() if guiutil.IsMSWindows(): dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "dicompyler License") else: dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "dicompyler License", size=(650, 550)) dlg.ShowModal() dlg.Destroy() def OnActivate(self, evt): """Show the import dialog if command-line arguments have been passed.""" # Check if a folder is provided via command-line arguments if (len(sys.argv) == 2): path = sys.argv[1] if not os.path.isdir(path): path = os.path.split(path)[0] pub.sendMessage('preferences.updated.value', {'general.dicom.import_location': path}) sys.argv.pop() self.OnOpenPatient(None) evt.Skip() def OnClose(self, _): pub.sendMessage('preferences.updated.value', {'general.window.maximized': self.IsMaximized()}) if not self.IsMaximized(): pub.sendMessage('preferences.updated.value', {'general.window.size': tuple(self.GetSize())}) pub.sendMessage( 'preferences.updated.value', {'general.window.position': tuple(self.GetPosition())}) self.Destroy()
class PluginManagerDialog(wx.Dialog): """Manage the available plugins.""" def __init__(self): pre = wx.PreDialog() # the Create step is done by XRC. self.PostCreate(pre) def Init(self, plugins, pluginsDisabled): """Method called after the panel has been initialized.""" # Set window icon if not guiutil.IsMac(): self.SetIcon(guiutil.get_icon()) # Initialize controls self.tcPlugins = XRCCTRL(self, 'tcPlugins') self.panelTreeView = XRCCTRL(self, 'panelTreeView') self.panelProperties = XRCCTRL(self, 'panelProperties') self.lblName = XRCCTRL(self, 'lblName') self.lblAuthor = XRCCTRL(self, 'lblAuthor') self.lblPluginType = XRCCTRL(self, 'lblPluginType') self.lblVersion = XRCCTRL(self, 'lblVersion') self.lblVersionNumber = XRCCTRL(self, 'lblVersionNumber') self.lblDescription = XRCCTRL(self, 'lblDescription') self.checkEnabled = XRCCTRL(self, 'checkEnabled') self.lblMessage = XRCCTRL(self, 'lblMessage') self.btnGetMorePlugins = XRCCTRL(self, 'btnGetMorePlugins') self.btnDeletePlugin = XRCCTRL(self, 'btnDeletePlugin') self.plugins = plugins self.pluginsDisabled = set(pluginsDisabled) # Bind interface events to the proper methods # wx.EVT_BUTTON(self, XRCID('btnDeletePlugin'), self.DeletePlugin) wx.EVT_CHECKBOX(self, XRCID('checkEnabled'), self.OnEnablePlugin) wx.EVT_TREE_ITEM_ACTIVATED(self, XRCID('tcPlugins'), self.OnEnablePlugin) wx.EVT_TREE_SEL_CHANGED(self, XRCID('tcPlugins'), self.OnSelectTreeItem) wx.EVT_TREE_SEL_CHANGING(self, XRCID('tcPlugins'), self.OnSelectRootItem) # Modify the control and font size as needed font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) if guiutil.IsMac(): children = list(self.Children) + \ list(self.panelTreeView.Children) + \ list(self.panelProperties.Children) for control in children: control.SetFont(font) control.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) XRCCTRL(self, 'wxID_OK').SetWindowVariant(wx.WINDOW_VARIANT_NORMAL) font.SetWeight(wx.FONTWEIGHT_BOLD) if guiutil.IsMSWindows(): self.tcPlugins.SetPosition((0, 3)) self.panelTreeView.SetWindowStyle(wx.STATIC_BORDER) if (guiutil.IsMac() or guiutil.IsGtk()): self.tcPlugins.SetPosition((-30, 0)) self.panelTreeView.SetWindowStyle(wx.SUNKEN_BORDER) self.lblName.SetFont(font) self.lblMessage.SetFont(font) self.Layout() self.InitPluginList() self.LoadPlugins() def InitPluginList(self): """Initialize the plugin list control.""" iSize = (16, 16) iList = wx.ImageList(iSize[0], iSize[1]) iList.Add( wx.Bitmap(util.GetResourcePath('bricks.png'), wx.BITMAP_TYPE_PNG)) iList.Add( wx.Bitmap(util.GetResourcePath('plugin.png'), wx.BITMAP_TYPE_PNG)) iList.Add( wx.Bitmap(util.GetResourcePath('plugin_disabled.png'), wx.BITMAP_TYPE_PNG)) self.tcPlugins.AssignImageList(iList) self.root = self.tcPlugins.AddRoot('Plugins') self.baseroot = self.tcPlugins.AppendItem(self.root, "Built-In Plugins", 0) self.userroot = self.tcPlugins.AppendItem(self.root, "User Plugins", 0) font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.FONTWEIGHT_BOLD) self.tcPlugins.SetItemFont(self.baseroot, font) self.tcPlugins.SetItemFont(self.userroot, font) def LoadPlugins(self): """Update and load the data for the plugin list control.""" # Set up the plugins for each plugin entry point of dicompyler for n, plugin in enumerate(self.plugins): # Skip plugin if it doesn't contain the required dictionary # or actually is a proper Python module p = plugin['plugin'] if not hasattr(p, 'pluginProperties'): continue props = p.pluginProperties() root = self.userroot if (plugin['location'] == 'base'): root = self.baseroot else: root = self.userroot i = self.tcPlugins.AppendItem(root, props['name'], 1) if (p.__name__ in self.pluginsDisabled): self.tcPlugins.SetItemImage(i, 2) self.tcPlugins.SetItemTextColour(i, wx.Colour(169, 169, 169)) self.tcPlugins.SetPyData(i, n) self.tcPlugins.SelectItem(i) self.tcPlugins.ExpandAll() wx.EVT_TREE_ITEM_COLLAPSING(self, XRCID('tcPlugins'), self.OnExpandCollapseTree) wx.EVT_TREE_ITEM_EXPANDING(self, XRCID('tcPlugins'), self.OnExpandCollapseTree) def OnSelectTreeItem(self, evt): """Update the interface when the selected item has changed.""" item = evt.GetItem() n = self.tcPlugins.GetPyData(item) if (n == None): self.panelProperties.Hide() return self.panelProperties.Show() plugin = self.plugins[n] p = plugin['plugin'] props = p.pluginProperties() self.lblName.SetLabel(props['name']) self.lblAuthor.SetLabel(props['author'].replace('&', '&&')) self.lblVersionNumber.SetLabel(str(props['version'])) ptype = props['plugin_type'] self.lblPluginType.SetLabel(ptype[0].capitalize() + ptype[1:]) self.lblDescription.SetLabel(props['description'].replace('&', '&&')) self.checkEnabled.SetValue(not (p.__name__ in self.pluginsDisabled)) self.Layout() self.panelProperties.Layout() def OnSelectRootItem(self, evt): """Block the root items from being selected.""" item = evt.GetItem() n = self.tcPlugins.GetPyData(item) if (n == None): evt.Veto() def OnExpandCollapseTree(self, evt): """Block the tree from expanding or collapsing.""" evt.Veto() def OnEnablePlugin(self, evt=None): """Publish the enabled/disabled state of the plugin.""" item = self.tcPlugins.GetSelection() n = self.tcPlugins.GetPyData(item) plugin = self.plugins[n] p = plugin['plugin'] # Set the checkbox to the appropriate state if the event # comes from the treeview if (evt.EventType == wx.EVT_TREE_ITEM_ACTIVATED.typeId): self.checkEnabled.SetValue(not self.checkEnabled.IsChecked()) if self.checkEnabled.IsChecked(): self.tcPlugins.SetItemImage(item, 1) self.tcPlugins.SetItemTextColour(item, wx.BLACK) self.pluginsDisabled.remove(p.__name__) logger.debug("%s enabled", p.__name__) else: self.tcPlugins.SetItemImage(item, 2) self.tcPlugins.SetItemTextColour(item, wx.Colour(169, 169, 169)) self.pluginsDisabled.add(p.__name__) logger.debug("%s disabled", p.__name__) pub.sendMessage( 'preferences.updated.value', {'general.plugins.disabled_list': list(self.pluginsDisabled)})
class pluginDVH(wx.Panel): """Plugin to display DVH data with adjustable constraints.""" def __init__(self): wx.Panel.__init__(self) def Init(self, res): """Method called after the panel has been initialized.""" self.guiDVH = guidvh.guiDVH(self) res.AttachUnknownControl("panelDVH", self.guiDVH.panelDVH, self) # Initialize the Constraint selector controls self.lblType = XRCCTRL(self, "lblType") self.choiceConstraint = XRCCTRL(self, "choiceConstraint") self.txtConstraint = XRCCTRL(self, "txtConstraint") self.sliderConstraint = XRCCTRL(self, "sliderConstraint") self.lblResultType = XRCCTRL(self, "lblResultType") self.lblConstraintUnits = XRCCTRL(self, "lblConstraintUnits") self.lblConstraintTypeUnits = XRCCTRL(self, "lblConstraintTypeUnits") # Initialize the result labels self.lblConstraintType = XRCCTRL(self, "lblConstraintType") self.lblResultDivider = XRCCTRL(self, "lblResultDivider") self.lblConstraintPercent = XRCCTRL(self, "lblConstraintPercent") # Modify the control and font size on Mac controls = [ self.lblType, self.choiceConstraint, self.sliderConstraint, self.lblResultType, self.lblConstraintUnits, self.lblConstraintPercent, self.lblConstraintType, self.lblConstraintTypeUnits, self.lblResultDivider, ] # Add children of composite controls to modification list compositecontrols = [self.txtConstraint] for control in compositecontrols: for child in control.GetChildren(): controls.append(child) # Add the constraint static box to the modification list controls.append(self.lblType.GetContainingSizer().GetStaticBox()) if guiutil.IsMac(): for control in controls: control.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) # Adjust the control size for the result value labels te = self.lblType.GetTextExtent("0") self.lblConstraintUnits.SetMinSize((te[0] * 10, te[1])) self.lblConstraintPercent.SetMinSize((te[0] * 6, te[1])) self.Layout() # Bind ui events to the proper methods self.Bind(wx.EVT_CHOICE, self.OnToggleConstraints, id=XRCID("choiceConstraint")) self.Bind(wx.EVT_SPINCTRL, self.OnChangeConstraint, id=XRCID("txtConstraint")) self.Bind( wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.OnChangeConstraint, id=XRCID("sliderConstraint"), ) self.Bind( wx.EVT_COMMAND_SCROLL_CHANGED, self.OnChangeConstraint, id=XRCID("sliderConstraint"), ) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) # Initialize variables self.structures = {} # structures from initial DICOM data self.checkedstructures = {} # structures that need to be shown self.dvhs = {} # raw dvhs from initial DICOM data self.dvharray = {} # dict of dvh data processed from dvhdata self.dvhscaling = {} # dict of dvh scaling data self.plan = {} # used for rx dose self.structureid = 1 # used to indicate current constraint structure # Set up pubsub pub.subscribe(self.OnUpdatePatient, "patient.updated.parsed_data") pub.subscribe(self.OnStructureCheck, "structures.checked") pub.subscribe(self.OnStructureSelect, "structure.selected") def OnUpdatePatient(self, msg): """Update and load the patient data.""" self.structures = msg["structures"] self.dvhs = msg["dvhs"] self.plan = msg["plan"] # show an empty plot when (re)loading a patient self.guiDVH.Replot() self.EnableConstraints(False) def OnDestroy(self, evt): """Unbind to all events before the plugin is destroyed.""" pub.unsubscribe(self.OnUpdatePatient, "patient.updated.parsed_data") pub.unsubscribe(self.OnStructureCheck, "structures.checked") pub.unsubscribe(self.OnStructureSelect, "structure.selected") def OnStructureCheck(self, msg): """When a structure changes, update the interface and plot.""" # Make sure that the volume has been calculated for each structure # before setting it self.checkedstructures = msg for id, structure in self.checkedstructures.items(): if not "volume" in self.structures[id]: self.structures[id]["volume"] = structure["volume"] # make sure that the dvh has been calculated for each structure # before setting it if id in self.dvhs: self.EnableConstraints(True) self.dvharray[id] = self.dvhs[id].relative_volume.counts # Create an instance of the dvh scaling data for guidvh self.dvhscaling[id] = 1 # self.dvhs[id]['scaling'] # 'Toggle' the choice box to refresh the dose data self.OnToggleConstraints(None) if not len(self.checkedstructures): self.EnableConstraints(False) # Make an empty plot on the DVH self.guiDVH.Replot() def OnStructureSelect(self, msg): """Load the constraints for the currently selected structure.""" if msg["id"] == None: self.EnableConstraints(False) else: self.structureid = msg["id"] if self.structureid in self.dvhs: # Create an instance of the dvh scaling data for guidvh self.dvhscaling[ self. structureid] = 1 # self.dvhs[self.structureid]['scaling'] # 'Toggle' the choice box to refresh the dose data self.OnToggleConstraints(None) else: self.EnableConstraints(False) self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures) def EnableConstraints(self, value): """Enable or disable the constraint selector.""" self.choiceConstraint.Enable(value) self.txtConstraint.Enable(value) self.sliderConstraint.Enable(value) if not value: self.lblConstraintUnits.SetLabel("- ") self.lblConstraintPercent.SetLabel("- ") self.txtConstraint.SetValue(0) self.sliderConstraint.SetValue(0) def OnToggleConstraints(self, evt): """Switch between different constraint modes.""" # Replot the remaining structures and disable the constraints # if a structure that has no DVH calculated is selected if not self.structureid in self.dvhs: self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures) self.EnableConstraints(False) return else: self.EnableConstraints(True) dvh = self.dvhs[self.structureid] # Check if the function was called via an event or not if not (evt == None): constrainttype = evt.GetInt() else: constrainttype = self.choiceConstraint.GetSelection() constraintrange = 0 # Volume constraint if constrainttype == 0: self.lblConstraintType.SetLabel(" Dose:") self.lblConstraintTypeUnits.SetLabel("% ") self.lblResultType.SetLabel("Volume:") constraintrange = dvh.relative_dose().max # Volume constraint in Gy elif constrainttype == 1: self.lblConstraintType.SetLabel(" Dose:") self.lblConstraintTypeUnits.SetLabel("Gy ") self.lblResultType.SetLabel("Volume:") constraintrange = round(dvh.max) # Dose constraint elif constrainttype == 2: self.lblConstraintType.SetLabel("Volume:") self.lblConstraintTypeUnits.SetLabel("% ") self.lblResultType.SetLabel(" Dose:") constraintrange = 100 # Dose constraint in cc elif constrainttype == 3: self.lblConstraintType.SetLabel("Volume:") self.lblConstraintTypeUnits.SetLabel("cm\u00B3") self.lblResultType.SetLabel(" Dose:") constraintrange = dvh.volume self.sliderConstraint.SetRange(0, constraintrange) self.sliderConstraint.SetValue(constraintrange) self.txtConstraint.SetRange(0, constraintrange) self.txtConstraint.SetValue(constraintrange) self.OnChangeConstraint(None) def OnChangeConstraint(self, evt): """Update the results when the constraint value changes.""" # Check if the function was called via an event or not if not (evt == None): slidervalue = evt.GetInt() else: slidervalue = self.sliderConstraint.GetValue() self.txtConstraint.SetValue(slidervalue) self.sliderConstraint.SetValue(slidervalue) id = self.structureid dvh = self.dvhs[self.structureid] constrainttype = self.choiceConstraint.GetSelection() # Volume constraint if constrainttype == 0: absDose = dvh.rx_dose * slidervalue cc = dvh.volume_constraint(slidervalue) constraint = dvh.relative_volume.volume_constraint(slidervalue) self.lblConstraintUnits.SetLabel(str(cc)) self.lblConstraintPercent.SetLabel(str(constraint)) self.guiDVH.Replot( [self.dvharray], [self.dvhscaling], self.checkedstructures, ([absDose], [constraint.value]), id, ) # Volume constraint in Gy elif constrainttype == 1: absDose = slidervalue * 100 cc = dvh.volume_constraint(slidervalue, dvh.dose_units) constraint = dvh.relative_volume.volume_constraint( slidervalue, dvh.dose_units) self.lblConstraintUnits.SetLabel(str(cc)) self.lblConstraintPercent.SetLabel(str(constraint)) self.guiDVH.Replot( [self.dvharray], [self.dvhscaling], self.checkedstructures, ([absDose], [constraint.value]), id, ) # Dose constraint elif constrainttype == 2: dose = dvh.dose_constraint(slidervalue) relative_dose = dvh.relative_dose().dose_constraint(slidervalue) self.lblConstraintUnits.SetLabel(str(dose)) self.lblConstraintPercent.SetLabel(str(relative_dose)) self.guiDVH.Replot( [self.dvharray], [self.dvhscaling], self.checkedstructures, ([dose.value * 100], [slidervalue]), id, ) # Dose constraint in cc elif constrainttype == 3: volumepercent = slidervalue * 100 / self.structures[id]["volume"] dose = dvh.dose_constraint(slidervalue, dvh.volume_units) relative_dose = dvh.relative_dose().dose_constraint( slidervalue, dvh.volume_units) self.lblConstraintUnits.SetLabel(str(dose)) self.lblConstraintPercent.SetLabel(str(relative_dose)) self.guiDVH.Replot( [self.dvharray], [self.dvhscaling], self.checkedstructures, ([dose.value * 100], [volumepercent]), id, )
class TripVoiDialog(wx.Dialog): def __init__(self): pre = wx.PreDialog() self.PostCreate(pre) pub.subscribe(self.patient_data_updated, "patient.loaded") pub.sendMessage("patient.request", {}) def patient_data_updated(self, msg): self.data = msg.data def select_drop_by_value(self, drop, value): for i, item in enumerate(drop.GetItems()): if item == value: drop.SetSelection(i) def Init(self, voi): self.voi = voi wx.EVT_BUTTON(self, XRCID('btn_ok'), self.save_and_close) wx.EVT_BUTTON(self, XRCID('btn_close'), self.close) self.label_name = XRCCTRL(self, "label_name") self.label_name.SetLabel(voi.get_name()) self.txt_dose = XRCCTRL(self, "txt_dose") self.txt_dose.SetValue("%.2f" % (voi.get_dose())) self.check_target = XRCCTRL(self, "check_target") self.check_target.SetValue(voi.is_target()) self.check_target.Bind(wx.EVT_CHECKBOX, self.on_check_target_changed) self.check_oar = XRCCTRL(self, "check_oar") self.check_oar.SetValue(voi.is_oar()) self.check_oar.Bind(wx.EVT_CHECKBOX, self.on_check_oar_changed) self.txt_max_dose_fraction = XRCCTRL(self, "txt_max_dose_fraction") self.txt_max_dose_fraction.SetValue("%.2f" % (voi.get_max_dose_fraction())) self.txt_max_dose_fraction.Enable(False) self.txt_dose.Enable(False) if voi.is_target(): self.check_oar.Enable(False) self.txt_dose.Enable(True) if voi.is_oar(): self.txt_max_dose_fraction.Enable(True) self.check_target.Enable(False) self.txt_hu_value = XRCCTRL(self, "txt_hu_value") self.txt_hu_offset = XRCCTRL(self, "txt_hu_offset") if not voi.get_hu_value() is None: self.txt_hu_value.SetValue("%d" % voi.get_hu_value()) if not voi.get_hu_offset() is None: self.txt_hu_offset.SetValue("%d" % voi.get_hu_offset()) self.drop_projectile = XRCCTRL(self, "drop_projectile") self.drop_projectile.Append("H") self.drop_projectile.Append("C") self.txt_dose_percent = XRCCTRL(self, "txt_dose_percent") wx.EVT_BUTTON(self, XRCID('btn_set_dosepercent'), self.set_dose_percent) wx.EVT_CHOICE(self, XRCID('drop_projectile'), self.on_projectile_changed) def on_projectile_changed(self, evt): projectile = self.drop_projectile.GetStringSelection() dose_percent = self.voi.get_dose_percent(projectile) if dose_percent is None: self.txt_dose_percent.SetValue("") else: self.txt_dose_percent.SetValue("%d" % dose_percent) def set_dose_percent(self, evt): if not self.drop_projectile.GetStringSelection() == "": self.voi.set_dose_percent(self.drop_projectile.GetStringSelection(), self.txt_dose_percent.GetValue()) def on_check_target_changed(self, evt): if evt.Checked(): self.check_oar.Enable(False) self.txt_dose.Enable(True) else: self.check_oar.Enable(True) self.txt_dose.Enable(False) def on_check_oar_changed(self, evt): if evt.Checked(): self.txt_max_dose_fraction.Enable(True) self.check_target.Enable(False) else: self.check_target.Enable(True) self.txt_max_dose_fraction.Enable(False) def save_and_close(self, evt): voi = self.voi voi.set_dose(self.txt_dose.GetValue()) if voi.is_target() is not self.check_target.IsChecked(): voi.toogle_target() if voi.is_oar() is not self.check_oar.IsChecked(): voi.toogle_oar() voi.set_max_dose_fraction(self.txt_max_dose_fraction.GetValue()) voi.set_hu_offset(self.txt_hu_offset.GetValue()) voi.set_hu_value(self.txt_hu_value.GetValue()) self.Close() def close(self, evt): self.Close()
class TripExportCubeDialog(wx.Dialog): def __init__(self): pre = wx.PreDialog() self.PostCreate(pre) def Init(self, plan): self.plan = plan self.path = "~/" self.output_path = "" self.checkbox_vois = XRCCTRL(self, "checkbox_vois") wx.EVT_LISTBOX(self, XRCID("checkbox_vois"), self.selected_changed) wx.EVT_BUTTON(self, XRCID("btn_ok"), self.save_and_close) wx.EVT_BUTTON(self, XRCID("btn_cancel"), self.close) wx.EVT_BUTTON(self, XRCID("btn_reset"), self.reset) self.lbl_path = XRCCTRL(self, "lbl_path") self.txt_value = XRCCTRL(self, "txt_value") wx.EVT_TEXT(self, XRCID("txt_value"), self.text_value_changed) pub.subscribe(self.path_changed, "general.export.cube_export_path") pub.sendMessage("settings.value.request", "general.export.cube") pub.subscribe(self.patient_data_updated, "patient.loaded") pub.sendMessage("patient.request", {}) for voi in plan.get_vois(): self.checkbox_vois.Append(voi.get_name()) def patient_data_updated(self, msg): self.data = msg.data def selected_changed(self, evt): selected = self.checkbox_vois.GetStringSelection() self.txt_value.SetValue("") for voi in self.plan.get_vois(): if selected == voi.get_name(): if voi.get_cube_value() is -1: self.txt_value.SetValue("") else: self.txt_value.SetValue("%d" % voi.get_cube_value()) def text_value_changed(self, evt): selected = self.checkbox_vois.GetStringSelection() if len(selected) is 0: return for voi in self.plan.get_vois(): if selected == voi.get_name(): try: voi.set_cube_value(int(self.txt_value.GetValue())) except Exception as e: pass def path_changed(self, msg): if not msg.data is None: self.path = msg.data self.lbl_path.SetLabel(self.path) def reset(self, evt): for voi in self.plan.get_vois(): voi.set_cube_value(-1) self.txt_value.SetValue("") for k, item in enumerate(self.checkbox_vois.GetItems()): self.checkbox_vois.Check(k, False) def browse_for_file(self): dlg = wx.FileDialog(self, message="Save Picture", defaultDir=self.path, style=wx.FD_SAVE) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() a = os.path.splitext(path) if not a[-1] is "dos": path = path + ".dos" self.output_path = path pub.sendMessage("settings.value.updated", {"general.export.cube": os.path.dirname(path)}) return True return False def save_and_close(self, evt): selected = self.checkbox_vois.GetCheckedStrings() vois = [] dos = None for voi in self.plan.get_vois(): if voi.get_name() in selected: if dos is None: dos = voi.get_voi().get_voi_data().get_voi_cube( ) / 1000 * voi.get_cube_value() else: dos.cube[dos.cube == 0] = -1 a = voi.get_voi().get_voi_data().get_voi_cube( ) / 1000 * voi.get_cube_value() dos.cube[dos.cube == -1] = a.cube[dos.cube == -1] if not dos is None: if self.browse_for_file(): dos.write(self.output_path) else: return else: pass self.Close() def close(self, evt): self.Close()
class pluginDVH(wx.Panel): """Plugin to display DVH data with adjustable constraints.""" def __init__(self): pre = wx.PrePanel() # the Create step is done by XRC. self.PostCreate(pre) def Init(self, res): """Method called after the panel has been initialized.""" self.guiDVH = guidvh.guiDVH(self) res.AttachUnknownControl('panelDVH', self.guiDVH.panelDVH, self) # Initialize the Constraint selector controls self.lblType = XRCCTRL(self, 'lblType') self.choiceConstraint = XRCCTRL(self, 'choiceConstraint') self.txtConstraint = XRCCTRL(self, 'txtConstraint') self.sliderConstraint = XRCCTRL(self, 'sliderConstraint') self.lblResultType = XRCCTRL(self, 'lblResultType') self.lblConstraintUnits = XRCCTRL(self, 'lblConstraintUnits') self.lblConstraintTypeUnits = XRCCTRL(self, 'lblConstraintTypeUnits') # Initialize the result labels self.lblConstraintType = XRCCTRL(self, 'lblConstraintType') self.lblResultDivider = XRCCTRL(self, 'lblResultDivider') self.lblConstraintPercent = XRCCTRL(self, 'lblConstraintPercent') # Modify the control and font size on Mac controls = [ self.lblType, self.choiceConstraint, self.sliderConstraint, self.lblResultType, self.lblConstraintUnits, self.lblConstraintPercent, self.lblConstraintType, self.lblConstraintTypeUnits, self.lblResultDivider ] # Add children of composite controls to modification list compositecontrols = [self.txtConstraint] for control in compositecontrols: for child in control.GetChildren(): controls.append(child) # Add the constraint static box to the modification list controls.append(self.lblType.GetContainingSizer().GetStaticBox()) if guiutil.IsMac(): font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetPointSize(10) for control in controls: control.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) control.SetFont(font) # Adjust the control size for the result value labels te = self.lblType.GetTextExtent('0') self.lblConstraintUnits.SetMinSize((te[0] * 10, te[1])) self.lblConstraintPercent.SetMinSize((te[0] * 6, te[1])) self.Layout() # Bind ui events to the proper methods wx.EVT_CHOICE(self, XRCID('choiceConstraint'), self.OnToggleConstraints) wx.EVT_SPINCTRL(self, XRCID('txtConstraint'), self.OnChangeConstraint) wx.EVT_COMMAND_SCROLL_THUMBTRACK(self, XRCID('sliderConstraint'), self.OnChangeConstraint) wx.EVT_COMMAND_SCROLL_CHANGED(self, XRCID('sliderConstraint'), self.OnChangeConstraint) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) # Initialize variables self.structures = {} # structures from initial DICOM data self.checkedstructures = {} # structures that need to be shown self.dvhs = {} # raw dvhs from initial DICOM data self.dvhdata = {} # dict of dvh constraint functions self.dvharray = {} # dict of dvh data processed from dvhdata self.dvhscaling = {} # dict of dvh scaling data self.plan = {} # used for rx dose self.structureid = 1 # used to indicate current constraint structure # Set up pubsub pub.subscribe(self.OnUpdatePatient, 'patient.updated.parsed_data') pub.subscribe(self.OnStructureCheck, 'structures.checked') pub.subscribe(self.OnStructureSelect, 'structure.selected') def OnUpdatePatient(self, msg): """Update and load the patient data.""" self.structures = msg.data['structures'] self.dvhs = msg.data['dvhs'] self.plan = msg.data['plan'] # show an empty plot when (re)loading a patient self.guiDVH.Replot() self.EnableConstraints(False) def OnDestroy(self, evt): """Unbind to all events before the plugin is destroyed.""" pub.unsubscribe(self.OnUpdatePatient) pub.unsubscribe(self.OnStructureCheck) pub.unsubscribe(self.OnStructureSelect) def OnStructureCheck(self, msg): """When a structure changes, update the interface and plot.""" # Make sure that the volume has been calculated for each structure # before setting it self.checkedstructures = msg.data for id, structure in self.checkedstructures.iteritems(): if not self.structures[id].has_key('volume'): self.structures[id]['volume'] = structure['volume'] # make sure that the dvh has been calculated for each structure # before setting it if self.dvhs.has_key(id): self.EnableConstraints(True) # Create an instance of the dvhdata class to can access its functions self.dvhdata[id] = dvhdata.DVH(self.dvhs[id]) # Create an instance of the dvh arrays so that guidvh can plot it self.dvharray[id] = dvhdata.DVH(self.dvhs[id]).dvh # Create an instance of the dvh scaling data for guidvh self.dvhscaling[id] = self.dvhs[id]['scaling'] # 'Toggle' the choice box to refresh the dose data self.OnToggleConstraints(None) if not len(self.checkedstructures): self.EnableConstraints(False) # Make an empty plot on the DVH self.guiDVH.Replot() def OnStructureSelect(self, msg): """Load the constraints for the currently selected structure.""" if (msg.data['id'] == None): self.EnableConstraints(False) else: self.structureid = msg.data['id'] if self.dvhs.has_key(self.structureid): # Create an instance of the dvhdata class to can access its functions self.dvhdata[self.structureid] = dvhdata.DVH( self.dvhs[self.structureid]) # Create an instance of the dvh scaling data for guidvh self.dvhscaling[self.structureid] = self.dvhs[ self.structureid]['scaling'] # 'Toggle' the choice box to refresh the dose data self.OnToggleConstraints(None) else: self.EnableConstraints(False) self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures) def EnableConstraints(self, value): """Enable or disable the constraint selector.""" self.choiceConstraint.Enable(value) self.txtConstraint.Enable(value) self.sliderConstraint.Enable(value) if not value: self.lblConstraintUnits.SetLabel('- ') self.lblConstraintPercent.SetLabel('- ') self.txtConstraint.SetValue(0) self.sliderConstraint.SetValue(0) def OnToggleConstraints(self, evt): """Switch between different constraint modes.""" # Replot the remaining structures and disable the constraints # if a structure that has no DVH calculated is selected if not self.dvhs.has_key(self.structureid): self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures) self.EnableConstraints(False) return else: self.EnableConstraints(True) dvh = self.dvhs[self.structureid] # Check if the function was called via an event or not if not (evt == None): constrainttype = evt.GetInt() else: constrainttype = self.choiceConstraint.GetSelection() constraintrange = 0 # Volume constraint if (constrainttype == 0): self.lblConstraintType.SetLabel(' Dose:') self.lblConstraintTypeUnits.SetLabel('% ') self.lblResultType.SetLabel('Volume:') rxDose = float(self.plan['rxdose']) dvhdata = (len(dvh['data']) - 1) * dvh['scaling'] constraintrange = int(dvhdata * 100 / rxDose) # never go over the max dose as data does not exist if (constraintrange > int(dvh['max'])): constraintrange = int(dvh['max']) # Volume constraint in Gy elif (constrainttype == 1): self.lblConstraintType.SetLabel(' Dose:') self.lblConstraintTypeUnits.SetLabel('Gy ') self.lblResultType.SetLabel('Volume:') constraintrange = self.plan['rxdose'] / 100 maxdose = int(dvh['max'] * self.plan['rxdose'] / 10000) # never go over the max dose as data does not exist if (constraintrange * 100 > maxdose): constraintrange = maxdose # Dose constraint elif (constrainttype == 2): self.lblConstraintType.SetLabel('Volume:') self.lblConstraintTypeUnits.SetLabel(u'% ') self.lblResultType.SetLabel(' Dose:') constraintrange = 100 # Dose constraint in cc elif (constrainttype == 3): self.lblConstraintType.SetLabel('Volume:') self.lblConstraintTypeUnits.SetLabel(u'cm³') self.lblResultType.SetLabel(' Dose:') constraintrange = int(self.structures[self.structureid]['volume']) self.sliderConstraint.SetRange(0, constraintrange) self.sliderConstraint.SetValue(constraintrange) self.txtConstraint.SetRange(0, constraintrange) self.txtConstraint.SetValue(constraintrange) self.OnChangeConstraint(None) def OnChangeConstraint(self, evt): """Update the results when the constraint value changes.""" # Check if the function was called via an event or not if not (evt == None): slidervalue = evt.GetInt() else: slidervalue = self.sliderConstraint.GetValue() self.txtConstraint.SetValue(slidervalue) self.sliderConstraint.SetValue(slidervalue) rxDose = self.plan['rxdose'] id = self.structureid constrainttype = self.choiceConstraint.GetSelection() # Volume constraint if (constrainttype == 0): absDose = rxDose * slidervalue / 100 volume = self.structures[id]['volume'] cc = self.dvhdata[id].GetVolumeConstraintCC(absDose, volume) constraint = self.dvhdata[id].GetVolumeConstraint(absDose) self.lblConstraintUnits.SetLabel("%.1f" % cc + u' cm³') self.lblConstraintPercent.SetLabel("%.1f" % constraint + " %") self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures, ([absDose], [constraint]), id) # Volume constraint in Gy elif (constrainttype == 1): absDose = slidervalue * 100 volume = self.structures[id]['volume'] cc = self.dvhdata[id].GetVolumeConstraintCC(absDose, volume) constraint = self.dvhdata[id].GetVolumeConstraint(absDose) self.lblConstraintUnits.SetLabel("%.1f" % cc + u' cm³') self.lblConstraintPercent.SetLabel("%.1f" % constraint + " %") self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures, ([absDose], [constraint]), id) # Dose constraint elif (constrainttype == 2): dose = self.dvhdata[id].GetDoseConstraint(slidervalue) self.lblConstraintUnits.SetLabel("%.1f" % dose + u' cGy') self.lblConstraintPercent.SetLabel("%.1f" % (dose * 100 / rxDose) + " %") self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures, ([dose], [slidervalue]), id) # Dose constraint in cc elif (constrainttype == 3): volumepercent = slidervalue * 100 / self.structures[id]['volume'] dose = self.dvhdata[id].GetDoseConstraint(volumepercent) self.lblConstraintUnits.SetLabel("%.1f" % dose + u' cGy') self.lblConstraintPercent.SetLabel("%.1f" % (dose * 100 / rxDose) + " %") self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures, ([dose], [volumepercent]), id)
class Panel(ObjectPanel.Panel): """ A wx.Panel for displaying and editing Components """ def __init__(self, parent, id=wx.ID_ANY, style=wx.EXPAND): #load from XRC, need to use two-stage create pre = wx.PrePanel() res = gui.XrcUtilities.XmlResource('./gui/xrc/ComponentPanel.xrc') res.LoadOnPanel(pre, parent, "ComponentPanel") self.PostCreate(pre) ObjectPanel.Panel.Setup(self) self.OnCreate() def OnCreate(self): self.name_field = XRCCTRL(self, "name_field") self.desc_field = XRCCTRL(self, "desc_field") self.tpcl_req_stc = XRCCTRL(self, "tpcl_req_stc") self.cat_choice = XRCCTRL(self, "cat_choice") self.prop_list = XRCCTRL(self, "prop_list") self.tpcl_cost_stc = XRCCTRL(self, "tpcl_cost_stc") self.tpcl_cost_stc.Enable(False) add_button = XRCCTRL(self, "add_button") self.Bind(wx.EVT_BUTTON, self.OnAddProperty, add_button) remove_button = XRCCTRL(self, "remove_button") self.Bind(wx.EVT_BUTTON, self.OnRemoveProperty, remove_button) #bind event handlers self.desc_field.Bind(wx.EVT_TEXT, self.CreateAttributeMonitor('description')) self.tpcl_req_stc.Bind(wx.stc.EVT_STC_CHANGE, self.CreateAttributeMonitor('tpcl_requirements')) self.tpcl_cost_stc.Bind(wx.stc.EVT_STC_CHANGE, self.OnCostEdit) self.filling_tpcl_cost = False self.Bind(wx.EVT_LISTBOX, self.OnPropListSelect, self.prop_list) self.cat_choice.Bind(wx.EVT_CHECKLISTBOX, self.CreateAttributeMonitor('categories')) #self.BindEditWatchers([self.desc_field, self.tpcl_req_stc]) self.loaded = False def LoadObject(self, node): print "ComponentPanel loading %s" % node.name #VERY IMPORTANT TO MARK OURSELVES AS LOADING HERE # THIS WAY WE AVOID PROGRAMMATIC CHANGES BEING MARKED AS USER CHANGES self.loading = True self.node = node self.object = node.GetObject() print "\tErrors:", self.object.errors self.node.visible = True self.name_field.SetLabel(str(self.object.name)) self.desc_field.SetValue(str(self.object.description)) if self.object.errors.has_key('description'): print "Error in description!" self.SetErrorLabel('description', self.object.errors['description']) self.tpcl_req_stc.SetText(self.object.tpcl_requirements) if self.object.errors.has_key('tpcl_requirements'): print "Error in tpcl_requirements!" self.SetErrorLabel('tpcl_requirements', self.object.errors['tpcl_requirements']) self.filling_tpcl_cost = True self.tpcl_cost_stc.SetText("") self.filling_tpcl_cost = False self.tpcl_cost_stc.Enable(False) if self.object.errors.has_key('properties'): print "Error in properties!" self.SetErrorLabel('properties', self.object.errors['properties']) #fill the category choice box self.cat_choice.Clear() for catnode in self.node.object_database.getObjectsOfType('Category'): idx = self.cat_choice.Append(catnode.name) if catnode.name in self.object.categories: self.cat_choice.Check(idx) if self.object.errors.has_key('categories'): print "Error in categories!" self.SetErrorLabel('categories', self.object.errors['categories']) #create the property list self.prop_sel = -1 prop_names = [pname for pname in self.object.properties.keys()] self.prop_list.Set(prop_names) self.node.object_database.Emphasize(prop_names, "BLUE") self.loaded = True self.loading = False self.Show() return self def OnDClickProperty(self, event): """\ Should open a window to edit the TPCL cost function here. """ pass def OnPropListSelect(self, event): sel_idx = self.prop_list.GetSelection() if sel_idx != wx.NOT_FOUND: self.tpcl_cost_stc.Enable(True) self.prop_sel = sel_idx self.filling_tpcl_cost = True self.tpcl_cost_stc.SetText(self.object.properties[ self.prop_list.GetString(sel_idx)]) self.filling_tpcl_cost = False def OnCostEdit(self, event): """\ Saves changes made to the TPCL cost functions """ print "Handling a cost edit event!" idx = self.prop_list.GetSelection() if idx == wx.NOT_FOUND or self.filling_tpcl_cost: pass else: self.object.properties[self.prop_list.GetString(idx)] = \ self.tpcl_cost_stc.GetText() self.node.SetModified(True) event.Skip() def OnAddProperty(self, event): print "On Add Property" loose_props = filter(lambda x: not x in self.object.properties.keys(), [n.name for n in self.node.object_database.getObjectsOfType('Property')]) choice_diag = wx.MultiChoiceDialog(self, "Choose the Properties to add...", "Add Properties...", loose_props) choice_diag.ShowModal() if len(choice_diag.GetSelections()) > 0: print "Selection OK" prop_names = [] for i in choice_diag.GetSelections(): print "\t" + loose_props[i] prop_names.append(loose_props[i]) self.object.properties[loose_props[i]] = "(lambda (design) #)" self.prop_list.Append(loose_props[i]) self.node.SetModified(True) self.node.object_database.Emphasize(prop_names, "BLUE") else: #cancelled print "CANCELED!" print "Selections: ", choice_diag.GetSelections() pass choice_diag.Destroy() def OnRemoveProperty(self, event): #remove the selected properties if self.prop_list.GetSelections() != []: ridx = [] prop_names = [] for idx in self.prop_list.GetSelections(): prop_name = self.prop_list.GetString(idx) prop_names.append(prop_name) del self.object.properties[prop_name] ridx.insert(0, idx) for i in ridx: self.prop_list.Delete(i) self.node.object_database.UnEmphasize(prop_names) self.tpcl_cost_stc.SetText("") self.node.SetModified(True) def Destroy(self): self.Hide() def ReallyDestroy(self): wx.Panel.Destroy(self) def cleanup(self): print "Cleaning up Component Panel" self.CleanupErrorLabels() self.node.object_database.UnEmphasize( [self.prop_list.GetString(i) for i in range(0, self.prop_list.GetCount())]) self.node.visible = False self.Hide() self.node.ClearObject() self.object = None self.loaded = False
class ConformalityPanel(wx.Dialog): """Plugin to calculate congruence between selected structure and an isodose line.""" def __init__(self): pre = wx.PreDialog() # the Create step is done by XRC. self.PostCreate(pre) def Init(self, structures, dose, plan, dvhs): """Method called after the panel has been initialized.""" # Initialize the Conformality selector controls self.lblType = XRCCTRL(self, 'lblType') self.choiceConformalityStructure = XRCCTRL( self, 'choiceConformalityStructure') self.choiceConformalityDose = XRCCTRL(self, 'choiceConformalityDose') self.lblConformalityIndex = XRCCTRL(self, 'lblConformalityIndex') self.lblUnderdoseRatio = XRCCTRL(self, 'lblUnderdoseRatio') self.lblOverdoseRatio = XRCCTRL(self, 'lblOverdoseRatio') self.lblTargetVolume = XRCCTRL(self, 'lblTargetVolume') self.lblCoverageVolume = XRCCTRL(self, 'lblCoverageVolume') self.lblIsodoseVolume = XRCCTRL(self, 'lblIsodoseVolume') # Bind ui events to the proper methods wx.EVT_CHOICE(self, XRCID('choiceConformalityStructure'), self.OnStructureSelect) wx.EVT_CHOICE(self, XRCID('choiceConformalityDose'), self.OnIsodoseSelect) # Initialize variables self.structures = structures self.dose = dose self.rxdose = plan['rxdose'] self.dvhs = dvhs self.conformalitydata = {} self.structureid = {} self.dvhdata = {} self.PopulateStructureChoices() # Setup toolbar controls # Set up preferences def PopulateStructureChoices(self): """Load the plan structure list.""" self.choiceConformalityStructure.Clear() for id, structure in self.structures.iteritems(): i = self.choiceConformalityStructure.Append(structure['name']) self.choiceConformalityStructure.SetClientData(i, id) def OnStructureSelect(self, evt=None): if (evt == None): self.choiceConformalityStructure.SetSelection(0) choiceItem = 0 else: choiceItem = evt.GetInt() # Load the structure id chosen from the choice control self.structureid = self.choiceConformalityStructure.GetClientData( choiceItem) if self.choiceConformalityDose.GetSelection() > 0: self.GetConformality(self.isodose) def OnIsodoseSelect(self, evt=None): """When the isodose list changes, update the panel.""" if (evt == None): self.choiceConformalityDose.SetSelection(0) choiceItem = 0 else: choiceItem = evt.GetInt() isodoseID = self.choiceConformalityDose.GetSelection() # Here "isodoseID == 0" is the '-' in the list, from the # conformality.xrc file if isodoseID == 1: self.isodose = 100 if isodoseID == 2: self.isodose = 90 if isodoseID == 3: self.isodose = 80 if isodoseID == 4: self.isodose = 70 if isodoseID == 5: self.isodose = 60 if isodoseID == 6: self.isodose = 50 self.GetConformality(self.isodose) def GetConformality(self, isodose): #set thresholds for conformality lowerlimit = isodose * self.rxdose / 100 id = self.structureid PITV, CV = CalculateCI(self, self.structures[id], lowerlimit) self.dvhdata[id] = dvhdata.DVH(self.dvhs[id]) TV = dvhdata.CalculateVolume(self.structures[id]) self.lblConformalityIndex.SetLabel( str(CV * CV / (TV * PITV) * 100)[0:5]) self.lblUnderdoseRatio.SetLabel(str(CV / (TV) * 100)[0:5]) self.lblOverdoseRatio.SetLabel(str(CV / (PITV) * 100)[0:5]) self.lblTargetVolume.SetLabel(str(TV)[0:6]) self.lblIsodoseVolume.SetLabel(str(PITV)[0:6]) self.lblCoverageVolume.SetLabel(str(CV)[0:6])
class FieldDialog(wx.Dialog): def __init__(self): pre = wx.PreDialog() self.PostCreate(pre) def Init(self, field): self.field = field self.btn_ok = XRCCTRL(self, 'btn_ok') wx.EVT_BUTTON(self, XRCID('btn_ok'), self.save_and_close) self.btn_cancel = XRCCTRL(self, 'btn_close') wx.EVT_BUTTON(self, XRCID('btn_close'), self.close) self.label_fieldname = XRCCTRL(self, 'label_fieldname') self.label_fieldname.SetLabel(field.get_name()) self.check_isocenter = XRCCTRL(self, 'check_isocenter') target = field.get_target() if len(target) > 0: self.check_isocenter.SetValue(True) self.check_isocenter.Bind(wx.EVT_CHECKBOX, self.on_check_isocenter_changed) self.txt_targetx = XRCCTRL(self, 'txt_targetx') self.txt_targety = XRCCTRL(self, 'txt_targety') self.txt_targetz = XRCCTRL(self, 'txt_targetz') if len(target) > 0: self.txt_targetx.SetValue("%.2f" % (target[0])) self.txt_targety.SetValue("%.2f" % (target[1])) self.txt_targetz.SetValue("%.2f" % (target[2])) else: self.txt_targetx.Enable(False) self.txt_targety.Enable(False) self.txt_targetz.Enable(False) self.txt_gantry = XRCCTRL(self, 'txt_gantry') self.txt_gantry.SetValue("%.2f" % field.get_gantry()) self.txt_couch = XRCCTRL(self, 'txt_couch') self.txt_couch.SetValue("%.2f" % field.get_couch()) self.txt_fwhm = XRCCTRL(self, 'txt_fwhm') self.txt_fwhm.SetValue("%.2f" % field.get_fwhm()) self.txt_zsteps = XRCCTRL(self, 'txt_zsteps') self.txt_zsteps.SetValue("%.2f" % field.get_zsteps()) self.txt_doseextension = XRCCTRL(self, 'txt_doseext') self.txt_doseextension.SetValue("%.2f" % field.get_doseextension()) self.txt_contourextension = XRCCTRL(self, 'txt_contourext') self.txt_contourextension.SetValue("%.2f" % field.get_contourextension()) self.txt_raster1 = XRCCTRL(self, 'txt_raster1') self.txt_raster2 = XRCCTRL(self, 'txt_raster2') raster = field.get_rasterstep() self.txt_raster1.SetValue("%.2f" % raster[0]) self.txt_raster2.SetValue("%.2f" % raster[1]) self.drop_projectile = XRCCTRL(self, 'drop_projectile') self.drop_projectile.SetSelection( self.drop_projectile.GetItems().index(field.projectile)) def on_check_isocenter_changed(self, evt): if self.check_isocenter.IsChecked(): self.txt_targetx.Enable(True) self.txt_targety.Enable(True) self.txt_targetz.Enable(True) else: self.txt_targetx.Enable(False) self.txt_targety.Enable(False) self.txt_targetz.Enable(False) def save_and_close(self, evt): self.field.set_couch(self.txt_couch.GetValue()) self.field.set_gantry(self.txt_gantry.GetValue()) self.field.set_fwhm(self.txt_fwhm.GetValue()) if self.check_isocenter.IsChecked(): self.field.set_target(self.txt_targetx.GetValue() + "," + self.txt_targety.GetValue() + "," + self.txt_targetz.GetValue()) else: self.field.set_target("") self.field.set_zsteps(self.txt_zsteps.GetValue()) self.field.set_doseextension(self.txt_doseextension.GetValue()) self.field.set_contourextension(self.txt_contourextension.GetValue()) self.field.set_rasterstep(self.txt_raster1.GetValue(), self.txt_raster2.GetValue()) self.field.set_projectile(self.drop_projectile.GetStringSelection()) self.Close() def close(self, evt): self.Close()