class TripLogDialog(wx.Dialog): def __init__(self): pre = wx.PreDialog() self.PostCreate(pre) def Init(self, tripexecuter): self.tripexecuter = tripexecuter self.tripexecuter.add_log_listener(self) self.txt_log = XRCCTRL(self, "txt_log") wx.EVT_BUTTON(self, XRCID("btn_ok"), self.close) self.btn_ok = XRCCTRL(self, "btn_ok") self.btn_ok.Enable(False) self.check_close = XRCCTRL(self, "check_close") def close(self, evt): self.Close() def finish(self): self.btn_ok.Enable(True) if self.check_close.IsChecked(): self.Close() def write(self, txt): self.txt_log.AppendText("%s\n" % txt)
class AnonymizeDialog(wx.Dialog): """Dialog that shows the options to anonymize DICOM / DICOM RT data.""" def __init__(self): pre = wx.PreDialog() # the Create step is done by XRC. self.PostCreate(pre) def Init(self): """Method called after the dialog has been initialized.""" # Set window icon if not guiutil.IsMac(): self.SetIcon(guiutil.get_icon()) # Initialize controls self.txtDICOMFolder = XRCCTRL(self, 'txtDICOMFolder') self.checkPatientName = XRCCTRL(self, 'checkPatientName') self.txtFirstName = XRCCTRL(self, 'txtFirstName') self.txtLastName = XRCCTRL(self, 'txtLastName') self.checkPatientID = XRCCTRL(self, 'checkPatientID') self.txtPatientID = XRCCTRL(self, 'txtPatientID') self.checkPrivateTags = XRCCTRL(self, 'checkPrivateTags') self.bmpError = XRCCTRL(self, 'bmpError') self.lblDescription = XRCCTRL(self, 'lblDescription') # Bind interface events to the proper methods wx.EVT_BUTTON(self, XRCID('btnFolderBrowse'), self.OnFolderBrowse) wx.EVT_CHECKBOX(self, XRCID('checkPatientName'), self.OnCheckPatientName) wx.EVT_CHECKBOX(self, XRCID('checkPatientID'), self.OnCheckPatientID) wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK) # Set and bold the font of the description label if guiutil.IsMac(): font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.FONTWEIGHT_BOLD) self.lblDescription.SetFont(font) # Initialize the import location via pubsub pub.subscribe(self.OnImportPrefsChange, 'general.dicom.import_location') pub.sendMessage('preferences.requested.value', 'general.dicom.import_location') # Pre-select the text on the text controls due to a Mac OS X bug self.txtFirstName.SetSelection(-1, -1) self.txtLastName.SetSelection(-1, -1) self.txtPatientID.SetSelection(-1, -1) # Load the error bitmap self.bmpError.SetBitmap(wx.Bitmap(util.GetResourcePath('error.png'))) # Initialize variables self.name = self.txtLastName.GetValue( ) + '^' + self.txtFirstName.GetValue() self.patientid = self.txtPatientID.GetValue() self.privatetags = True def OnImportPrefsChange(self, msg): """When the import preferences change, update the values.""" self.path = unicode(msg.data) self.txtDICOMFolder.SetValue(self.path) def OnFolderBrowse(self, evt): """Get the directory selected by the user.""" dlg = wx.DirDialog( self, defaultPath=self.path, message="Choose a folder to save the anonymized DICOM data...") if dlg.ShowModal() == wx.ID_OK: self.path = dlg.GetPath() self.txtDICOMFolder.SetValue(self.path) dlg.Destroy() def OnCheckPatientName(self, evt): """Enable or disable whether the patient's name is anonymized.""" self.txtFirstName.Enable(evt.IsChecked()) self.txtLastName.Enable(evt.IsChecked()) if not evt.IsChecked(): self.txtDICOMFolder.SetFocus() else: self.txtFirstName.SetFocus() self.txtFirstName.SetSelection(-1, -1) def OnCheckPatientID(self, evt): """Enable or disable whether the patient's ID is anonymized.""" self.txtPatientID.Enable(evt.IsChecked()) if not evt.IsChecked(): self.txtDICOMFolder.SetFocus() else: self.txtPatientID.SetFocus() self.txtPatientID.SetSelection(-1, -1) def OnOK(self, evt): """Return the options from the anonymize data dialog.""" # Patient name if self.checkPatientName.IsChecked(): self.name = self.txtLastName.GetValue() if len(self.txtFirstName.GetValue()): self.name = self.name + '^' + self.txtFirstName.GetValue() else: self.name = '' # Patient ID if self.checkPatientID.IsChecked(): self.patientid = self.txtPatientID.GetValue() else: self.patientid = '' # Private tags if self.checkPrivateTags.IsChecked(): self.privatetags = True else: self.privatetags = False self.EndModal(wx.ID_OK) def OnDestroy(self, evt): """Unbind to all events before the plugin is destroyed.""" pub.unsubscribe(self.OnImportPrefsChange, 'general.dicom.import_location') pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.raw_data')
class pluginTreeView(wx.Panel): """Plugin to display DICOM data in a tree view.""" def __init__(self): wx.Panel.__init__(self) def Init(self, res): """Method called after the panel has been initialized.""" # Initialize the panel controls self.choiceDICOM = XRCCTRL(self, 'choiceDICOM') self.tlcTreeView = DICOMTree(self) res.AttachUnknownControl('tlcTreeView', self.tlcTreeView, self) # Bind interface events to the proper methods self.Bind(wx.EVT_CHOICE, self.OnLoadTree, id=XRCID('choiceDICOM')) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) # Decrease the font size on Mac font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) if guiutil.IsMac(): font.SetPointSize(10) self.tlcTreeView.SetFont(font) # Set up pubsub pub.subscribe(self.OnUpdatePatient, 'patient.updated.raw_data') def OnUpdatePatient(self, msg): """Update and load the patient data.""" self.choiceDICOM.Enable() self.choiceDICOM.Clear() self.choiceDICOM.Append("Select a DICOM dataset...") self.choiceDICOM.Select(0) self.tlcTreeView.DeleteAllItems() # Iterate through the message and enumerate the DICOM datasets for k, v in msg.items(): if isinstance(v, pydicom.dataset.FileDataset): i = self.choiceDICOM.Append(v.SOPClassUID.name.split(' Storage')[0]) self.choiceDICOM.SetClientData(i, v) # Add the images to the choicebox if (k == 'images'): for imgnum, image in enumerate(v): i = self.choiceDICOM.Append( image.SOPClassUID.name.split(' Storage')[0] + \ ' Slice ' + str(imgnum + 1)) self.choiceDICOM.SetClientData(i, image) pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.raw_data') def OnDestroy(self, evt): """Unbind to all events before the plugin is destroyed.""" pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.raw_data') def OnLoadTree(self, event): """Update and load the DICOM tree.""" choiceItem = event.GetInt() # Load the dataset chosen from the choice control if not (choiceItem == 0): dataset = self.choiceDICOM.GetClientData(choiceItem) else: return self.tlcTreeView.DeleteAllItems() self.root = self.tlcTreeView.AppendItem(self.tlcTreeView.GetRootItem(),dataset.SOPClassUID.name) self.tlcTreeView.Collapse(self.root) # Initialize the progress dialog dlgProgress = guiutil.get_progress_dialog( wx.GetApp().GetTopWindow(), "Loading DICOM data...") # Set up the queue so that the thread knows which item was added self.queue = queue.Queue() # Initialize and start the recursion thread self.t=threading.Thread(target=self.RecurseTreeThread, args=(dataset, self.root, self.AddItemTree, dlgProgress.OnUpdateProgress, len(dataset))) self.t.start() # Show the progress dialog dlgProgress.ShowModal() dlgProgress.Destroy() self.tlcTreeView.SetFocus() self.tlcTreeView.Expand(self.root) def RecurseTreeThread(self, ds, parent, addItemFunc, progressFunc, length): """Recursively process the DICOM tree.""" for i, data_element in enumerate(ds): # Check and update the progress of the recursion if (length > 0): wx.CallAfter(progressFunc, i, length, 'Processing DICOM data...') if (i == length-1): wx.CallAfter(progressFunc, i, len(ds), 'Done') # Add the data_element to the tree if not a sequence element if not (data_element.VR == 'SQ'): cs = ds.get('SpecificCharacterSet', "ISO_IR 6") wx.CallAfter(addItemFunc, data_element, parent, cs=cs) # Otherwise add the sequence element to the tree else: wx.CallAfter(addItemFunc, data_element, parent, needQueue=True) item = self.queue.get() # Enumerate for each child element of the sequence for i, ds in enumerate(data_element.value): sq_item_description = data_element.name.replace(" Sequence", "") sq_element_text = "%s %d" % (sq_item_description, i+1) # Add the child of the sequence to the tree wx.CallAfter(addItemFunc, data_element, item, sq_element_text, needQueue=True) sq = self.queue.get() self.RecurseTreeThread(ds, sq, addItemFunc, progressFunc, 0) def AddItemTree(self, data_element, parent, sq_element_text="", needQueue=False, cs=None): """Add a new item to the DICOM tree.""" # Set the item if it is a child of a sequence element if not (sq_element_text == ""): item = self.tlcTreeView.AppendItem(parent, text=sq_element_text) else: item = self.tlcTreeView.AppendItem(parent, text=data_element.name) # Set the value if not a sequence element if not (data_element.VR == 'SQ'): value = data_element.value # Account for Pixel data if (data_element.name == 'Pixel Data'): value = 'Array of ' + str(len(data_element.value)) + ' bytes' # Account for Unknown VRs elif ((data_element.VR == 'UN') and \ not (type(data_element.value) == str)): value = data_element.repval else: # Apply the DICOM character encoding to the data element if not isinstance(data_element.value, str): try: pydicom.charset.decode( pydicom.charset.decode(data_element, cs)) # Otherwise try decoding via ASCII encoding except: try: value = str(data_element.value) except: logger.info( "Could not decode character set for %s.", data_element.name) value = str( data_element.value, errors='replace') else: value = data_element.value self.tlcTreeView.SetItemText(item, 1, value) # Fill in the rest of the data_element properties self.tlcTreeView.SetItemText(item, 2, str(data_element.tag)) self.tlcTreeView.SetItemText(item, 3, str(data_element.VM)) self.tlcTreeView.SetItemText(item, 4, str(data_element.VR)) if (needQueue): self.queue.put(item)
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 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 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 AnonymizeDialog(wx.Dialog): """Dialog th # Changed foundstructure to fixed 'True' # I didn't understand the reason why RT Structure Set will be # set to 'not found' in this case. (Actually RT Structure has already been found by the above code.) # In this case, 'RT Plan' is not found, RT Structure Set in patient, and foundstructure = False. # Previous code: foundstructure = Falseat shows the options to anonymize DICOM / DICOM RT data.""" def __init__(self): wx.Dialog.__init__(self) def Init(self): """Method called after the dialog has been initialized.""" # Set window icon if not guiutil.IsMac(): self.SetIcon(guiutil.get_icon()) # Initialize controls self.txtDICOMFolder = XRCCTRL(self, 'txtDICOMFolder') self.checkPatientName = XRCCTRL(self, 'checkPatientName') self.txtFirstName = XRCCTRL(self, 'txtFirstName') self.txtLastName = XRCCTRL(self, 'txtLastName') self.checkPatientID = XRCCTRL(self, 'checkPatientID') self.txtPatientID = XRCCTRL(self, 'txtPatientID') self.checkPrivateTags = XRCCTRL(self, 'checkPrivateTags') self.bmpError = XRCCTRL(self, 'bmpError') self.lblDescription = XRCCTRL(self, 'lblDescription') # Bind interface events to the proper methods wx.EVT_BUTTON(self, XRCID('btnFolderBrowse'), self.OnFolderBrowse) wx.EVT_CHECKBOX(self, XRCID('checkPatientName'), self.OnCheckPatientName) wx.EVT_CHECKBOX(self, XRCID('checkPatientID'), self.OnCheckPatientID) wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK) # Set and bold the font of the description label if guiutil.IsMac(): font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.FONTWEIGHT_BOLD) self.lblDescription.SetFont(font) # Initialize the import location via pubsub pub.subscribe(self.OnImportPrefsChange, 'general.dicom') pub.sendMessage('preferences.requested.values', msg='general.dicom') # Pre-select the text on the text controls due to a Mac OS X bug self.txtFirstName.SetSelection(-1, -1) self.txtLastName.SetSelection(-1, -1) self.txtPatientID.SetSelection(-1, -1) # Load the error bitmap self.bmpError.SetBitmap(wx.Bitmap(util.GetResourcePath('error.png'))) # Initialize variables self.name = self.txtLastName.GetValue( ) + '^' + self.txtFirstName.GetValue() self.patientid = self.txtPatientID.GetValue() self.privatetags = True def OnImportPrefsChange(self, topic, msg): """When the import preferences change, update the values.""" topic = topic.split('.') if (topic[1] == 'import_location'): self.path = str(msg) self.txtDICOMFolder.SetValue(self.path) def OnFolderBrowse(self, evt): """Get the directory selected by the user.""" dlg = wx.DirDialog( self, defaultPath=self.path, message="Choose a folder to save the anonymized DICOM data...") if dlg.ShowModal() == wx.ID_OK: self.path = dlg.GetPath() self.txtDICOMFolder.SetValue(self.path) dlg.Destroy() def OnCheckPatientName(self, evt): """Enable or disable whether the patient's name is anonymized.""" self.txtFirstName.Enable(evt.IsChecked()) self.txtLastName.Enable(evt.IsChecked()) if not evt.IsChecked(): self.txtDICOMFolder.SetFocus() else: self.txtFirstName.SetFocus() self.txtFirstName.SetSelection(-1, -1) def OnCheckPatientID(self, evt): """Enable or disable whether the patient's ID is anonymized.""" self.txtPatientID.Enable(evt.IsChecked()) if not evt.IsChecked(): self.txtDICOMFolder.SetFocus() else: self.txtPatientID.SetFocus() self.txtPatientID.SetSelection(-1, -1) def OnOK(self, evt): """Return the options from the anonymize data dialog.""" # Patient name if self.checkPatientName.IsChecked(): self.name = self.txtLastName.GetValue() if len(self.txtFirstName.GetValue()): self.name = self.name + '^' + self.txtFirstName.GetValue() else: self.name = '' # Patient ID if self.checkPatientID.IsChecked(): self.patientid = self.txtPatientID.GetValue() else: self.patientid = '' # Private tags if self.checkPrivateTags.IsChecked(): self.privatetags = True else: self.privatetags = False self.EndModal(wx.ID_OK)
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()