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 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)