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()
Exemple #2
0
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)})
Exemple #3
0
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()
Exemple #4
0
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 EditorFrame(wx.Frame):
    """
    A wx.Panel for displaying and editing Categories
    """
    def __init__(self, parent, block_store, id=wx.ID_ANY, style=wx.EXPAND):
        #load from XRC, need to use two-stage create
        res = gui.XrcUtilities.XmlResource('./gui/xrc/TpclEditorFrame.xrc')
        pre = wx.PreFrame()
        res.LoadOnFrame(pre, parent, "editor")
        self.PostCreate(pre)

        self.block_store = block_store
        self.OnCreate()

    def OnCreate(self):
        self.SetSize((600, 400))

        #widgets
        self.code_stc = XRCCTRL(self, "code_stc")
        self.code_stc.Bind(wx.EVT_LEFT_UP, self.ContextMenuHandler)
        self.block_tree = XRCCTRL(self, "block_tree")
        self.block_tree.SetBlockstore(self.block_store)
        self.preview_ctrl = XRCCTRL(self, "preview_ctrl")
        self.block_tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelectionChanged)

        #buttons
        self.clear_button = XRCCTRL(self, "clear_button")
        self.Bind(wx.EVT_BUTTON, self.OnClear, self.clear_button)
        self.remove_button = XRCCTRL(self, "remove_button")
        self.Bind(wx.EVT_BUTTON, self.OnRemove, self.remove_button)
        self.save_button = XRCCTRL(self, "save_button")
        self.Bind(wx.EVT_BUTTON, self.OnSave, self.save_button)
        self.info_button = XRCCTRL(self, "info_button")
        self.Bind(wx.EVT_BUTTON, self.OnInfo, self.info_button)

        #fill the block_tree
        #tpcl.IO.LoadBlockIntoTree(self.block_tree)
        self.root_expression = None

        self.CreateQuickInsertMenu()

    def CreateQuickInsertMenu(self):
        self.quick_insert_menu = wx.Menu()
        submenus = [
            "Procedure Definitions", "Literal Expression",
            "Numerical Functions", "Flow Control"
        ]
        for sm in submenus:
            cat_id = self.block_store.FindCategory(sm)
            smenu = wx.Menu()
            for child_id in self.block_store.GetChildBlocks(cat_id):
                node = self.block_store.GetNode(child_id)
                mitem = smenu.Append(wx.ID_ANY, node.name)
                self.Bind(wx.EVT_MENU, MakeInserter(self, node.block), mitem)
            self.quick_insert_menu.AppendMenu(wx.ID_ANY, sm, smenu)

    def OnInfo(self, event):
        """\
        Opens a dialog without a parent for the moment
        """
        sel_id = self.block_tree.GetSelection()
        if sel_id.IsOk():
            block = self.block_tree.GetPyData(sel_id)
            if block:
                info_dialog = BlockInfoDialog(self, block.name, block.display,
                                              block.description)
                info_dialog.ShowModal()
        event.Skip()

    def OnClear(self, event):
        """\
        Clears the code window, deleting all unsaved
        changes.
        """
        self.code_stc.ClearAll()
        #for now we'll put the root expression back in place
        self.root_expression = None
        event.Skip()

    def OnRemove(self, event):
        """\
        Removes the current tpcl code block that is
        selected.
        """
        pos = self.code_stc.GetCurrentPos()
        try:
            self.root_expression.RemoveExpression(pos)
            self.code_stc.SetText(str(self.root_expression))
        except ValueError:
            #text in top level expression, get rid of it
            self.root_expression = None
            self.code_stc.ClearAll()
        event.Skip()

    def OnSave(self, event):
        """\
        Saves the current work.
        """
        event.Skip()

    def OnSelectionChanged(self, event):
        """\
        Selection change in the block tree
        We need to fill the preview control here
        """
        print "Handling selection changed event."
        sel_id = self.block_tree.GetSelection()
        if sel_id.IsOk():
            block = self.block_tree.GetPyData(sel_id)
            if block:
                self.preview_ctrl.SetValue(block.display)
            else:
                self.preview_ctrl.SetValue("")
        else:
            self.preview_ctrl.SetValue("")
        event.Skip()

    def OnInsert(self, event, block=None):
        """\
        Inserts the currently selected code block
        into the currently selected code socket.
        """
        pos = self.code_stc.GetCurrentPos()

        if not block:
            sel_id = self.block_tree.GetSelection()
            if sel_id.IsOk():
                block = self.block_tree.GetPyData(sel_id)

        if block:
            print "Trying to insert block..."
            try:
                #provide the OnInsert function of the block
                # access to us as a parent frame for when they
                # need to show a dialog
                parent_frame = self

                expression = TpclExpression(block)
                insert_ok = True
                if block.on_insert:
                    print "Trying to use OnInsert function of block"
                    exec(block.on_insert)
                    insert_ok = OnInsert(expression)

                if insert_ok:
                    if not self.root_expression:
                        self.root_expression = expression
                    else:
                        self.root_expression.InsertExpression(pos, expression)
                    self.code_stc.SetText(str(self.root_expression))

            except ValueError:
                print "Tried to insert in a place where there's no expansion point"
        event.Skip()

    def ContextMenuHandler(self, event):
        """\
        Processes a left click on the STC
        """
        event.Skip()
        print "Trying to show context menu at pos:", self.code_stc.GetCurrentPos(
        )
        try:
            if wx.GetMouseState().ControlDown():
                print "Trying to show popup menu..."
                menu = wx.Menu()

                offset = self.code_stc.GetCurrentPos()

                if not self.root_expression:
                    is_insertion_point = True
                    is_added_insertion_point = False
                    is_expansion_point = False
                else:
                    is_insertion_point = self.root_expression.IsInsertionPoint(
                        offset)[0]
                    is_added_insertion_point = self.root_expression.IsAddedInsertionPoint(
                        offset)[0]
                    is_expansion_point = self.root_expression.IsExpansionPoint(
                        offset)[0]

                print "is_insertion_point", is_insertion_point
                print "is_expansion_point", is_expansion_point

                sel_id = self.block_tree.GetSelection()
                if sel_id.IsOk():
                    block = self.block_tree.GetPyData(sel_id)
                else:
                    block = None

                #insert item
                insert_item = menu.Append(wx.ID_ANY, "Insert")
                self.Bind(wx.EVT_MENU, self.OnInsert, insert_item)
                #only enable if we're on an expansion point
                insert_item.Enable(block != None and is_insertion_point)

                #quick insert
                quick_insert = menu.AppendMenu(wx.ID_ANY, "Quick Insert...",
                                               self.quick_insert_menu)
                quick_insert.Enable(is_insertion_point)

                #remove item
                remove_item = menu.Append(wx.ID_ANY, "Remove")
                self.Bind(wx.EVT_MENU, self.OnRemove, remove_item)
                remove_item.Enable(
                    (not is_insertion_point and not is_expansion_point)
                    or is_added_insertion_point)

                #check for expansion menu
                if is_expansion_point:
                    exp_menu = wx.Menu()
                    i = 0
                    for option in self.root_expression.GetExpansionOptions(
                            offset):
                        opt_item = exp_menu.Append(wx.ID_ANY, option)
                        self.Bind(wx.EVT_MENU, MakeExpander(self, offset, i),
                                  opt_item)
                        i += 1
                    menu.AppendMenu(wx.ID_ANY, "Expansion Point...", exp_menu)

                self.code_stc.PopupMenu(menu, event.GetPosition())

        except ValueError:
            print "Index out of range..."

    def ShowModal(self):
        print "TPCLEE Showing itself Modally"
        self.MakeModal(True)
        self.old_top_window = wx.GetApp().GetTopWindow()
        wx.GetApp().SetTopWindow(self)
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Show(True)

    def OnClose(self, event):
        self.MakeModal(False)
        self.Unbind(wx.EVT_CLOSE)
        wx.GetApp().SetTopWindow(self.old_top_window)
        event.Skip()
Exemple #6
0
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)
Exemple #7
0
class AnalysisPanel(wx.Dialog):
    """Panel that shows DVH and constraint value comparisons."""
    def __init__(self):
        pre = wx.PreDialog()
        # the Create step is done by XRC.
        self.PostCreate(pre)

    def Init(self, structures, dvhs):
        """Method called after panel has ben initialized."""

        # Initialize variables
        self.dvhs = {}  # raw dvhs from initial DICOM data
        self.dvhdata = {}  # dict of dvh constraint functions
        self.structures = structures
        self.dvhs = dvhs

        # Setup toolbar controls
        #drawingstyles = ['Solid', 'Transparent', 'Dot', 'Dash', 'Dot Dash']
        #self.tools = []

        self.widgetDict = {
            'choiceOptic': ['Optic', 'OpticMax'],
            'choiceCochlea': ['CochleaMax'],
            'choiceBrainstem': ['Brainstem', 'BrainstemMax'],
            'choiceSpinal': ['Spinal1', 'Spinal2', 'SpinalMax'],
            'choiceCauda': ['Cauda', 'CaudaMax'],
            'choiceSacral': ['Sacral', 'SacralMax'],
            'choiceEsophagus': ['Esophagus', 'EsophagusMax'],
            'choiceBrachial': ['Brachial', 'BrachialMax'],
            'choiceHeart': ['Heart', 'HeartMax'],
            'choiceGreatVessels': ['GreatVessels', 'GreatVesselsMax'],
            'choiceTrachea': ['Trachea', 'TracheaMax'],
            'choiceSmallBronchus': ['SmallBronchus', 'SmallBronchusMax'],
            'choiceRib': ['Rib', 'RibMax'],
            'choiceSkin': ['Skin', 'SkinMax'],
            'choiceStomach': ['Stomach', 'StomachMax'],
            'choiceBowel': ['Bowel', 'BowelMax'],
            'choiceRenalHilum': ['RenalHilum'],
            'choiceLungs': ['Lungs1', 'Lungs2'],
            'choiceLiver': ['Liver'],
            'choiceRenalCortex': ['RenalCortex']
        }

        self.volDict = {
            1: 'Optic',
            2: 'Brainstem',
            3: 'Spinal1',
            4: 'Spinal2',
            5: 'Cauda',
            6: 'Sacral',
            7: 'Esophagus',
            8: 'Brachial',
            9: 'Heart',
            10: 'GreatVessels',
            11: 'Trachea',
            12: 'SmallBronchus',
            13: 'Rib',
            14: 'Skin',
            15: 'Stomach',
            16: 'Bowel',
            17: 'RenalHilum',
            18: 'Lungs1',
            19: 'Lungs2',
            20: 'Liver',
            21: 'RenalCortex'
        }

        self.initialGuessDict = {
            'choiceOptic': ['optic pathway', 'optic structures'],
            'choiceCochlea': ['cochlea'],
            'choiceBrainstem': ['brainstem'],
            'choiceSpinal': ['spinal cord', 'cord'],
            'choiceCauda': ['cauda', 'cauda equina'],
            'choiceEsophagus': ['esophagus'],
            'choiceBrachial': ['brachial plexus'],
            'choiceHeart': ['heart'],
            'choiceGreatVessels': ['great vessels', 'greater vessels', 'gv'],
            'choiceTrachea':
            ['trachea', 'trachea/bronchus', 'bronchus', 'large bronchus'],
            'choiceSmallBronchus': ['small bronchus', 'smaller bronchus'],
            'choiceRib': ['rib', 'ribs'],
            'choiceSkin': [
                'skin', 'skin 5mm', 'skin (5mm)', 'skin(5mm)', 'skin(0.5cm)',
                'skin (0.5cm)'
            ],
            'choiceStomach': ['stomach'],
            'choiceBowel': ['bowel'],
            'choiceRenalHilum': ['renal hilum'],
            'choiceLungs': ['lungs', 'total lung', 'total lungs', 'lung'],
            'choiceLiver': ['liver'],
            'choiceRenalCortex': ['renal cortex']
        }

        "Tolerances from TG-101 table."
        "Format (serial) [volume, limit(1fx), limit(2fx), limit(3fx), limit(4fx), limit(5fx)]"
        "Format (parall) [thresh, limit(1fx), limit(2fx), limit(3fx), limit(4fx), limit(5fx)]"
        self.toleranceOptic = [0.2, 8.0, 11.7, 15.3, 19.2, 23.0]
        self.toleranceOpticMax = [0, 10, 13.7, 17.4, 21.2, 25]
        self.toleranceCochleaMax = [0, 9, 13.1, 17.1, 21.1, 25]
        self.toleranceBrainstem = [0.5, 10, 14, 18, 20.5, 23]
        self.toleranceBrainstemMax = [0, 15, 19.1, 23.1, 27.1, 31]
        self.toleranceSpinal1 = [0.35, 10, 14, 18, 20.5, 23]
        self.toleranceSpinal2 = [1.2, 7, 9.7, 12.3, 13.4, 14.5]
        self.toleranceSpinalMax = [0, 14, 18, 21.9, 26, 30]
        self.toleranceCauda = [5, 14, 18, 21.9, 26, 30]
        self.toleranceCaudaMax = [0, 16, 20, 24, 28, 32]
        self.toleranceSacral = [5, 14.4, 18.5, 22.5, 26.3, 30]
        self.toleranceSacralMax = [0, 16, 20, 24, 28, 32]
        self.toleranceEsophagus = [5, 11.9, 14.8, 17.7, 18.6, 19.5]
        self.toleranceEsophagusMax = [0, 15.4, 20.3, 25.2, 30.1, 35]
        self.toleranceBrachial = [3, 14, 17.2, 20.4, 23.7, 27]
        self.toleranceBrachialMax = [0, 17.5, 20.8, 24, 27.3, 30.5]
        self.toleranceHeart = [15, 16, 20, 24, 28, 32]
        self.toleranceHeartMax = [0, 22, 26, 30, 34, 38]
        self.toleranceGreatVessels = [10, 31, 35, 39, 43, 47]
        self.toleranceGreatVesselsMax = [0, 37, 41, 45, 49, 53]
        self.toleranceTrachea = [4, 10.5, 12.8, 15, 15.8, 16.5]
        self.toleranceTracheaMax = [0, 20.2, 25.1, 30, 35, 40]
        self.toleranceSmallBronchus = [0.5, 12.4, 15.7, 18.9, 20, 21]
        self.toleranceSmallBronchusMax = [0, 13.3, 18.2, 23.1, 28.1, 33]
        self.toleranceRib = [1, 22, 25.4, 28.8, 31.9, 35]
        self.toleranceRibMax = [0, 30, 33.5, 36.9, 40, 43]
        self.toleranceSkin = [10, 23, 26.5, 30, 33.3, 36.5]
        self.toleranceSkinMax = [0, 26, 29.5, 33, 36.3, 39.5]
        self.toleranceStomach = [10, 11.2, 13.9, 16.5, 17.3, 18]
        self.toleranceStomachMax = [0, 12.4, 17.3, 22.2, 27.1, 32]
        self.toleranceBowel = [5, 11.9, 14.8, 17.7, 18.6, 19.5]
        self.toleranceBowelMax = [0, 15.4, 20.3, 25.2, 30.1, 35]
        self.toleranceRenalHilum = ['66.6%', 10.6, 14.6, 18.6, 20.8, 23]
        self.toleranceLungs1 = ['> 1500', 7, 9.3, 11.6, 12.1, 12.5]
        self.toleranceLungs2 = ['> 1000', 7.4, 9.9, 12.4, 13, 13.5]
        self.toleranceLiver = ['> 700', 9.1, 14.2, 19.2, 20.1, 21]
        self.toleranceRenalCortex = ['> 200', 8.4, 12.2, 16, 16.8, 17.5]

        # Initialize the Analysis fractionation control
        self.choiceFractions = XRCCTRL(self, 'choiceFractions')

        # Volumes defined per fraction/Organ
        # Doses defined per fraction/Organ (Critical Volume/Threshold organs)
        # NOTE: Lungs, Liver, and RenalCortex are actually THRESHOLDS (in Gy),
        #       NOT volumes.  This was only done for a simpler for_loop.
        #       See columns on GUI.

        # Look up GUI Column 2 values
        for i, organName in self.volDict.iteritems():
            volName = 'vol' + organName
            setattr(self, volName, XRCCTRL(self, volName))

        # Define structure lists (choices) and dose limits (in cGy) for organ
        for choiceName, organList in self.widgetDict.iteritems():
            #i.e. self.choiceOptic (combobox)
            setattr(self, choiceName, XRCCTRL(self, choiceName))
            for organName in organList:
                limitName = 'limit' + organName  # TG limit values
                planName = 'plan' + organName  # Dose values obtained from plan
                imgName = 'img' + organName  #Image box indicator
                # i.e. self.limitOptic
                setattr(self, limitName, XRCCTRL(self, limitName))
                # i.e. self.planOptic
                setattr(self, planName, XRCCTRL(self, planName))
                # i.e. self.imgOptic
                setattr(self, imgName, XRCCTRL(self, imgName))

        img = os.path.join(os.path.dirname(__file__), 'transparent.png')
        img = wx.Image(img, wx.BITMAP_TYPE_ANY)
        self.imgTransparent = wx.BitmapFromImage(img)

        img = os.path.join(os.path.dirname(__file__), 'check.png')
        img = wx.Image(img, wx.BITMAP_TYPE_ANY)
        self.imgCheck = wx.BitmapFromImage(img)

        img = os.path.join(os.path.dirname(__file__), 'flag.png')
        img = wx.Image(img, wx.BITMAP_TYPE_ANY)
        self.imgFlag = wx.BitmapFromImage(img)

        # Bind ui events to the proper methods
        wx.EVT_COMBOBOX(self, XRCID('choiceFractions'), self.ReadTolerances)
        wx.EVT_COMBOBOX(self, XRCID('choiceOptic'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceCochlea'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceBrainstem'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceSpinal'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceCauda'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceSacral'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceEsophagus'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceBrachial'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceHeart'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceGreatVessels'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceTrachea'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceSmallBronchus'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceRib'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceSkin'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceStomach'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceBowel'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceRenalHilum'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceLungs'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceLiver'), self.OnComboOrgan)
        wx.EVT_COMBOBOX(self, XRCID('choiceRenalCortex'), self.OnComboOrgan)

        self.SetStructureChoices()
        self.InitialGuessCombobox()
        self.DisableChoices()

    def PrintEventInfo(self, event):
        print event.GetEventObject().GetName(), 'name'
        print event.GetEventObject().GetLabel(), 'label'

    def ReadTolerances(self, event=None):
        """Read in a text file that contains TG-101 constraints 
        e.g. [volume,Fx1dose,...etc]."""
        FractionID = self.choiceFractions.GetSelection()

        # Initialize the cells after the fractionation is changed
        self.ResetVolumeValues()
        self.ResetLimitValues()
        self.ResetPlanValues()
        if FractionID == 0:
            self.DisableChoices()
            self.ResetImgs()
        else:
            self.EnableChoices()
            self.ResetImgs()
            # Look up GUI Column 2 values
            for i, organName in self.volDict.iteritems():
                # exclude max points in column 2
                if hasattr(self, 'vol' + organName) == True:
                    VolObject = getattr(self, 'vol' + organName)
                    tolerance = getattr(self, 'tolerance' + organName)
                    # All except hilum and parallel organs
                    if type(tolerance[0]) != str:
                        VolObject.SetValue(str(tolerance[0]))
                    if type(tolerance[0]) == str:  # hilum and lungs
                        if '%' in tolerance[0]:  # hilum
                            s = tolerance[0]  # i.e. '66.6%' -> 0.666
                            setattr(self, 'ratio' + organName,
                                    float(s.rstrip('%')) / 100)
                            setattr(self, 'percentage' + organName, s)
                            VolObject.SetValue(s)
                        if '>' in tolerance[0]:  # lungs
                            VolObject.SetValue(str(tolerance[FractionID]))
            # Look up GUI Column 3 values
            for choiceName, organList in self.widgetDict.iteritems():
                for organName in organList:
                    tolerance = getattr(self, 'tolerance' + organName)
                    LimitObject = getattr(self, 'limit' + organName)
                    if type(tolerance[0]) != str:
                        LimitObject.SetValue(str(tolerance[FractionID]))
                    if type(tolerance[0]) == str:
                        if '%' in tolerance[0]:
                            LimitObject.SetValue(str(tolerance[FractionID]))
                        if '>' in tolerance[0]:
                            LimitObject.SetValue(tolerance[0])

            # Autopopulate the preset structures, if found by name
            for choiceName, organList in self.widgetDict.iteritems():
                ChoiceObject = getattr(self, choiceName)
                CurrentChoiceObject = ChoiceObject.GetCurrentSelection()
                if CurrentChoiceObject > 0:  # ignore choice(0) = ''
                    for organName in organList:
                        self.FindOrganPlan(
                            ChoiceObject.GetClientData(CurrentChoiceObject),
                            organName)

    def SetStructureChoices(self):
        """Reinitialize the lists; will have double the entries if not."""

        choiceList = self.widgetDict.keys()
        for choiceName in choiceList:
            choiceObject = getattr(self, choiceName)
            choiceObject.Clear()
            choiceObject.Append('')  # Pad the first item
            for id, structure in self.structures.iteritems():
                i = choiceObject.Append(structure['name'])
                choiceObject.SetClientData(i, id)

    def InitialGuessCombobox(self):
        # i.e. ['skin', 'skin (0.5cm)']
        for choiceName, guessList in self.initialGuessDict.iteritems():
            ChoiceObject = getattr(self, choiceName)
            # combobox list ['', 'Optic', 'Skin', ...]
            for i in range(ChoiceObject.GetCount()):
                if self.KeywordsInCombobox(ChoiceObject.GetString(i),
                                           guessList) == True:
                    ChoiceObject.SetSelection(i)

    def KeywordsInCombobox(self, comboboxString, guessList):
        for guess in guessList:
            if comboboxString.lower() == guess:
                return True

    def ResetVolumeValues(self):
        # NOTE: Lungs, Liver, and RenalCortex are actually THRESHOLDS (in Gy),
        #       NOT volumes.  This was only done for a simpler for_loop.
        #       See columns on GUI.
        for choiceName, organList in self.widgetDict.iteritems():
            for organName in organList:
                if hasattr(self, 'vol' + organName) == True:
                    VolObject = getattr(self, 'vol' + organName)
                    VolObject.Clear()

    def ResetLimitValues(self):
        for choiceName, organList in self.widgetDict.iteritems():
            for organName in organList:
                LimitObject = getattr(self, 'limit' + organName)
                LimitObject.Clear()

    def ResetPlanValues(self):
        for choiceName, organList in self.widgetDict.iteritems():
            for organName in organList:
                PlanObject = getattr(self, 'plan' + organName)
                PlanObject.Clear()

    def ResetImgs(self):
        for choiceName, organList in self.widgetDict.iteritems():
            for organName in organList:
                ImgObject = getattr(self, 'img' + organName)
                ImgObject.SetBitmap(self.imgTransparent)

    def EnableChoices(self):
        choiceList = self.widgetDict.keys()
        for choiceName in choiceList:
            choiceObject = getattr(self, choiceName)
            choiceObject.Enable()

    def DisableChoices(self):
        choiceList = self.widgetDict.keys()
        for choiceName in choiceList:
            choiceObject = getattr(self, choiceName)
            choiceObject.Enable(False)

    def OnComboOrgan(self, event=None):  # event based
        choiceItem = event.GetInt()  # i.e. userinput-> index
        choiceName = event.GetEventObject().GetName()  # i.e. choiceObject
        choiceObject = getattr(self, choiceName)  # i.e. self.choiceOptic
        id = choiceObject.GetClientData(choiceItem)
        for organName in self.widgetDict[choiceName]:
            if id > 0:  # exclude choice(0) = ''
                self.FindOrganPlan(id, organName)
            else:
                PlanObject = getattr(self, 'plan' + organName)
                ImgObject = getattr(self, 'img' + organName)
                PlanObject.Clear()
                ImgObject.SetBitmap(self.imgTransparent)

    def FindOrganPlan(self, id, organName):
        LimitObject = getattr(self, 'limit' + organName)
        PlanObject = getattr(self, 'plan' + organName)
        ImgObject = getattr(self, 'img' + organName)
        self.dvhdata[id] = dvhdata.DVH(self.dvhs[id])
        total_vol = dvhdata.CalculateVolume(self.structures[id])
        if hasattr(self, 'vol' + organName) == True:  # exclude statictext
            VolObject = getattr(self, 'vol' + organName)
            if '%' in VolObject.GetValue():  #hilum
                s = VolObject.GetValue()
                s = s.rstrip('%')
                formatted_vol = s
        if '> ' in LimitObject.GetValue():  #hilum and lung
            s = LimitObject.GetValue()
            s = s.lstrip('> ')
            formatted_limit = s
        if 'Max' not in organName:
            if 'formatted_vol' in locals():
                Vol = float(formatted_vol) * 100 / total_vol
            else:
                Vol = float(VolObject.GetValue()) * 100 / total_vol
            dose = self.dvhdata[id].GetDoseConstraint(Vol)
            dose = str(dose / 100)  # Dose from DVH is in cGy
            if 'formatted_limit' in locals():
                crit_vol = total_vol - float(formatted_limit)
                crit_vol = str(round(crit_vol, 1))
                PlanObject.SetValue(crit_vol)
                if float(PlanObject.GetValue()) > float(formatted_limit):
                    ImgObject.SetBitmap(self.imgCheck)
                else:
                    ImgObject.SetBitmap(self.imgFlag)
            else:
                PlanObject.SetValue(dose)
                if float(PlanObject.GetValue()) < float(
                        LimitObject.GetValue()):
                    ImgObject.SetBitmap(self.imgCheck)
                else:
                    ImgObject.SetBitmap(self.imgFlag)

        else:  # dose max
            voxelPointVol = 0.035 * 100 / total_vol  # 0.035 cc is "voxel"
            dose = self.dvhdata[id].GetDoseConstraint(
                voxelPointVol)  #dose at DVH tail
            dose = str(dose / 100)
            PlanObject.SetValue(dose)
            if float(PlanObject.GetValue()) < float(LimitObject.GetValue()):
                ImgObject.SetBitmap(self.imgCheck)
            else:
                ImgObject.SetBitmap(self.imgFlag)
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])
Exemple #10
0
class PlanDialog(wx.Dialog):
    def __init__(self):
        pre = wx.PreDialog()
        self.PostCreate(pre)
        pub.subscribe(self.patient_data_updated, "patient.loaded")
        pub.sendMessage("patient.request", {})

    def Init(self, plan):
        self.plan = plan

        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.init_general()
        self.init_trip_panel()
        self.init_opt_panel()
        self.init_calculation_panel()
        self.init_files_panel()
        self.init_advanved_dose()

    def patient_data_updated(self, msg):
        self.data = msg.data

    def init_files_panel(self):
        self.txt_ddd = XRCCTRL(self, "txt_ddd")
        self.txt_ddd.SetValue(self.plan.get_ddd_folder())

        self.txt_spc = XRCCTRL(self, "txt_spc")
        self.txt_spc.SetValue(self.plan.get_spc_folder())

        self.txt_sis = XRCCTRL(self, "txt_sis")
        self.txt_sis.SetValue(self.plan.get_sis_file())

        wx.EVT_BUTTON(self, XRCID("btn_ddd"), self.on_btn_ddd_clicked)
        wx.EVT_BUTTON(self, XRCID("btn_spc"), self.on_btn_spc_clicked)
        wx.EVT_BUTTON(self, XRCID("btn_sis"), self.on_btn_sis_clicked)

    def init_general(self):
        self.drop_res_tissue_type = XRCCTRL(self, "drop_res_tissue_type")
        rbe_list = self.data.get_rbe()
        for rbe in rbe_list.get_rbe_list():
            self.drop_res_tissue_type.Append(rbe.get_name())
        self.select_drop_by_value(self.drop_res_tissue_type,
                                  self.plan.get_res_tissue_type())
        self.drop_target_tissue_type = XRCCTRL(self, "drop_target_tissue_type")
        for rbe in rbe_list.get_rbe_list():
            self.drop_target_tissue_type.Append(rbe.get_name())
        self.select_drop_by_value(self.drop_target_tissue_type,
                                  self.plan.get_target_tissue_type())

    def select_drop_by_value(self, drop, value):
        for i, item in enumerate(drop.GetItems()):
            if item == value:
                drop.SetSelection(i)

    def init_calculation_panel(self):
        self.check_phys_dose = XRCCTRL(self, "check_phys_dose")
        self.check_phys_dose.SetValue(self.plan.get_out_phys_dose())

        self.check_bio_dose = XRCCTRL(self, "check_bio_dose")
        self.check_bio_dose.SetValue(self.plan.get_out_bio_dose())

        self.check_dose_mean_let = XRCCTRL(self, "check_mean_let")
        self.check_dose_mean_let.SetValue(self.plan.get_out_dose_mean_let())

        self.check_field = XRCCTRL(self, "check_field")
        self.check_field.SetValue(self.plan.get_out_field())

    def init_opt_panel(self):
        self.txt_iterations = XRCCTRL(self, "txt_iterations")
        self.txt_iterations.SetValue("%d" % self.plan.get_iterations())

        self.txt_eps = XRCCTRL(self, "txt_eps")
        self.txt_eps.SetValue("%f" % self.plan.get_eps())

        self.txt_geps = XRCCTRL(self, "txt_geps")
        self.txt_geps.SetValue("%f" % self.plan.get_geps())

        self.drop_opt_method = XRCCTRL(self, "drop_opt_method")
        self.select_drop_by_value(self.drop_opt_method,
                                  self.plan.get_opt_method())

        self.drop_opt_principle = XRCCTRL(self, "drop_opt_principle")
        self.select_drop_by_value(self.drop_opt_principle,
                                  self.plan.get_opt_princip())

        self.drop_dose_alg = XRCCTRL(self, "drop_dose_alg")
        self.select_drop_by_value(self.drop_dose_alg,
                                  self.plan.get_dose_algorithm())

        self.drop_bio_alg = XRCCTRL(self, "drop_bio_alg")
        self.select_drop_by_value(self.drop_bio_alg,
                                  self.plan.get_dose_algorithm())

        self.drop_opt_alg = XRCCTRL(self, "drop_opt_alg")
        self.select_drop_by_value(self.drop_opt_alg,
                                  self.plan.get_opt_algorithm())

    def init_trip_panel(self):
        self.drop_location = XRCCTRL(self, "drop_location")
        if self.plan.is_remote():
            self.drop_location.SetSelection(1)

        self.txt_working_dir = XRCCTRL(self, "txt_working_dir")
        self.txt_working_dir.SetValue(self.plan.get_working_dir())

        wx.EVT_BUTTON(self, XRCID('btn_working_dir'),
                      self.on_browse_working_dir)

        self.txt_username = XRCCTRL(self, "txt_username")
        self.txt_username.SetValue(self.plan.get_username())

        self.txt_password = XRCCTRL(self, "txt_password")
        self.txt_password.SetValue(self.plan.get_password())

        self.txt_server = XRCCTRL(self, "txt_server")
        self.txt_server.SetValue(self.plan.get_server())

    def init_advanved_dose(self):
        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.plan.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.plan.set_dose_percent(
                self.drop_projectile.GetStringSelection(),
                self.txt_dose_percent.GetValue())

    def on_browse_working_dir(self, evt):
        dlg = wx.DirDialog(
            self,
            defaultPath=self.txt_working_dir.GetValue(),
            message=
            "Choose the folder pytripgui should use as working directory")
        if dlg.ShowModal() == wx.ID_OK:
            self.txt_working_dir.SetValue(dlg.GetPath())

    def on_btn_ddd_clicked(self, evt):
        dlg = wx.DirDialog(self,
                           defaultPath=self.txt_ddd.GetValue(),
                           message="Choose folder where ddd files are located")
        if dlg.ShowModal() == wx.ID_OK:
            self.txt_ddd.SetValue(dlg.GetPath())

    def on_btn_sis_clicked(self, evt):
        dlg = wx.FileDialog(self,
                            defaultFile=self.txt_sis.GetValue(),
                            message="Choose sis file")
        if dlg.ShowModal() == wx.ID_OK:
            self.txt_sis.SetValue(dlg.GetPath())

    def on_btn_spc_clicked(self, evt):
        dlg = wx.DirDialog(self,
                           defaultPath=self.txt_spc.GetValue(),
                           message="Choose folder where spc files are located")
        if dlg.ShowModal() == wx.ID_OK:
            self.txt_spc.SetValue(dlg.GetPath())

    def save_and_close(self, evt):
        self.plan.set_res_tissue_type(
            self.drop_res_tissue_type.GetStringSelection())
        self.plan.set_target_tissue_type(
            self.drop_target_tissue_type.GetStringSelection())
        if self.drop_location.GetSelection() is 0:
            self.plan.set_remote_state(False)
        else:
            self.plan.set_remote_state(True)
        self.plan.set_working_dir(self.txt_working_dir.GetValue())
        self.plan.set_server(self.txt_server.GetValue())
        self.plan.set_username(self.txt_username.GetValue())
        self.plan.set_password(self.txt_password.GetValue())

        self.plan.set_iterations(self.txt_iterations.GetValue())
        self.plan.set_eps(self.txt_eps.GetValue())
        self.plan.set_geps(self.txt_geps.GetValue())
        self.plan.set_opt_method(self.drop_opt_method.GetStringSelection())
        self.plan.set_opt_princip(self.drop_opt_principle.GetStringSelection())
        self.plan.set_dose_algorithm(self.drop_dose_alg.GetStringSelection())
        self.plan.set_bio_algorithm(self.drop_bio_alg.GetStringSelection())
        self.plan.set_opt_algorithm(self.drop_opt_alg.GetStringSelection())

        self.plan.set_out_phys_dose(self.check_phys_dose.GetValue())
        self.plan.set_out_bio_dose(self.check_bio_dose.GetValue())
        self.plan.set_out_dose_mean_let(self.check_dose_mean_let.GetValue())
        self.plan.set_out_field(self.check_field.GetValue())

        self.plan.set_ddd_folder(self.txt_ddd.GetValue())
        self.plan.set_spc_folder(self.txt_ddd.GetValue())
        self.plan.set_sis_file(self.txt_sis.GetValue())

        self.Close()

    def close(self, evt):
        self.Close()