matplotlib

Source code for beamon

# Copyright (c) 2014, Varian Medical Systems, Inc. (VMS)
# All rights reserved.
# 
# Veritas is an open source tool for TrueBeam Developer Mode provided by Varian Medical Systems, Palo Alto. 
# It lets users generate XML beams without assuming any prior knowledge of the underlying XML-schema rules. 
# This version is based on the schema for TrueBeam 1.5 and 1.6. 
#
# Veritas is licensed under the Varian Open Source License.
# You may obtain a copy of the License at:
#
#      http://radiotherapyresearchtools.com/license/
# 
# For questions, please send us an email at: TrueBeamDeveloper@varian.com                   
# 
# Developer Mode is intended for non-clinical use only and is NOT cleared for use on humans.
# Created on: 11:11:51 AM, Jul 7, 2014
# Author: Pankaj Mishra

# *************************************
# This file represents the dialog after pressing the beamon button from the main window

# In general, any given class has the following structure:
#    1) Each class, in the root folder corresponds logically to
#       a single window in the main application
#    2) The visual layout and UI elements are specified by an import
#       of form ui_xxxxx where xxxx is the name of the class
#    3) The main dataflow is mediated by the cp_data variable, which
#       is used as a container to pass information between windows
#    4) The class itself handles the semantics of what UI elements
#       map to what actions and what data passes through to cp_data
#    5) Any instance attributes inside a class is used as bookkeeping
#       to keep track of data before it gets sent to cp_data

# The above will be reproduced in the preface document, and is included
# to be an inline reference to the structure.
# Up to date as of May, 12 2015.

# %%%

# The main data flow for this file is as follows:
# After the header modal is initialized, it has either recieved
#  data from the main window via the cp_data structure.
# The data structure determins what the user sees on initialization
# After the user is finished, the data is collected into cp_data
# and sent to the main beamonCP class, where a similar thing happens.
# The fully populated cp_data is then sent back to main window for further
# processing.

# Any non-responsive or buggy UI elements will be here
# Any xml import or export errors will be in CPData
# Any other problems will be in other files
# *************************************
try:
    from PySide.QtGui import QDialog, QFileDialog
except ImportError:
    #print('Pyside not getting imported')
    pass

import os
from UI import ui_beamON_CP, ui_beamonHeader, ui_velTable
from models.CPData import CPData
     
[docs]class beamonHeader(object):#QDialog, ui_beamonHeader.Ui_beamON): ''' This class launches a window prompting user to enter header info, that are part of first control point in the XMl beam. The header info can entered in two ways: a) From scratch for a new XML beam or b) Parsed from a pre-existing XMl beam created either previously or generated from a DICOM-RT file. Class constructor creates an instance control point window as well as variables for saving velocity and tolerance tables. In general, other than instantiation and execution, no other window should should call any methods ''' def __init__(self, sbeam = None, parent=None, cpdata=CPData()): super(beamonHeader, self).__init__(parent) self.setupUi(self) # Stop users from proceeding from accessing previous other windows self.setModal(True) # Variables containing tolerance table values self.tolTableProperties = dict() # Mechanical axis tolerance self.velTableProperties = dict() # Velocity table tolerance # Connect tolerance table push buttons to the respective table buttons self.TolTable.clicked.connect(self.openTolTable) self.VelTable.clicked.connect(self.openVelTable) # Initialize instances of tolerance table and velocity limit table window self.tolTable = tvTable() self.velTable = tvTable() # Instantiate a control point window self.cpWindow = beamonCP() self.cpdata = cpdata self._auto_fill_header() # Fill out the self.cp data, if cpdata has it, # Otherwise initialize to None try: self.cp = self.cpdata.get_cp(0) except IndexError: self.cp = None # Activates on pressing OK button
[docs] def accept(self): ''' On pressing OK, checks self.cp for any xml data already read in and populates the next window with the proper entries filled in. Hides current window and returns accept code when finished Data is passed to beamonCP in the _create_beamoncp() function ''' self._create_beamoncp() self._set_cp_fields() QDialog.done(self, 1)
# Activates on pressing Cancel or on window exit
[docs] def reject(self): ''' ON pressing cancel, closes the window without peforming any processing. ''' QDialog.done(self, 0)
# Activates on pressing Properties for TolTable
[docs] def openTolTable(self): ''' Show tolerance table window ''' self.tolTable.show() if(self.tolTable.exec_()): self.tolTableProperties = self.tolTable.tableVal
# Activates on pressing Properties for VelTable
[docs] def openVelTable(self): ''' Show velocity limit table window ''' self.velTable.show() if(self.velTable.exec_()): self.velTableProperties = self.velTable.tableVal
# --- accept private methods def _auto_fill_header(self): ''' Parse a pre-existing XML file so that different fields can be displayed properly in windows ''' if self.cpdata.get_MLCmodel(): mlc_model = self.cpdata.get_MLCmodel() mlc_mappings = {"NDS80": 0, "NDS120": 1, "NDS120HD": 2} self.MLCModel.setCurrentIndex(mlc_mappings[mlc_model]) if self.cpdata.get_drate(): self.DRate.setText(self.cpdata.get_drate()) if self.cpdata.get_energy_type(): type_mappings = {"k": 0, "x": 1, "e": 2} energy_type = self.cpdata.get_energy_type() self.EnergyValue.setText(str(self.cpdata.get_energy_val())) self.EnergyType.setCurrentIndex(type_mappings[energy_type]) def _set_cp_fields(self): tags = ("GantryRtn", "CollRtn", "CouchVrt", "CouchLat", "CouchLng", "CouchRtn", "X1", "X2", "Y1", "Y2", "Mu") if self.cp is not None: self._get_header() # Collect all the values self.cpWindow.displayCP(self.cp) def _create_beamoncp(self): self.cpWindow = beamonCP(cpdata=self.cpdata) self.cpWindow.setWindowTitle("Control Point ") if self.cp is None: self._get_header() # Collect all the values self.cpWindow.dispCount.setText(str(self.cpWindow.navCounter)) else: self.cpWindow.show() # Now show the window self.cpWindow.dispCount.setText(str('0')) def _get_header(self): ''' Save the header info from the header window to cpdata ''' self.cpdata.set_MLCmodel(self.MLCModel.currentText()) mappings = {'kV': 'k', 'MV': 'x'} # Added 'electron' option for energy type - JDeMarco, 7/28/2014 mappings['Elec'] = 'e' cur_enval = self.EnergyValue.text() cur_entype = self.EnergyType.currentText() if mappings.get(cur_entype): self.cpdata.set_energy(cur_enval, mappings[cur_entype]) self.cpdata.set_drate(self.DRate.text()) self.cpdata.set_toltable(**self.tolTableProperties) self.cpdata.set_veltable(**self.velTableProperties)
[docs]class beamonCP(object):#QDialog, ui_beamON_CP.Ui_beamON_CP): ''' Create a control point window. control point window lets the user the enter a set of value corresponding to the different mechanical axes. In case of pre-existing XML beam this class lets the user modify it instead When its job is done, it passes a populated cp_data structure to the caller via self.get_data() In general, only self.get_data() should be called by another object. Other code is used as UI logic ''' def __init__(self, xmlCP = None, parent = None, cpdata=CPData()): super(beamonCP, self).__init__(parent) self.setupUi(self) # Control point variable self.cp = dict() # The current control point self.cpdata = cpdata # Initialize points according to cp_data's # beamonCP keeps its own list for navigation # to avoid coupling to cpdata and only access it via getters/setters self.controlPoints = self.cpdata.get_all_cps() # The None cap at the end of controlPoints serves as a way to differentiate between # the modifying the last control point and adding an additional control point after # Also, it prevents controlPoints from being an empty list and having IndexErrors # on navigation self.controlPoints.append(None) # These constants below provide a convenient way of collecting all the text # data from the UI's fields self.fields = ("GantryRtn", "CollRtn", "CouchVrt", "CouchLat", "CouchLng", "CouchRtn", "X1", "X2", "Y1", "Y2", "Mu") self.lineedits = (self.GantryRtn, self.CollRtn, self.CouchVrt, self.CouchLat, self.CouchLng, self.CouchRtn, self.X1, self.X2, self.Y1, self.Y2, self.Mu) # Load Mlc from a text file self.Mlc.clicked.connect(self.openMlcFile) self.xmlFlag = False # Flag for reading XML file and propagating the previous values self._init_nav() self._init_cp() self._init_done_cp() if(xmlCP): self._init_xml(xmlCP) # Used by mainwindow to pass cp_data to mainwindow
[docs] def get_data(self): ''' Return all accumulated data ''' return self.cpdata
# Activated by Add/Edit Button
[docs] def editAddCP(self): ''' Commit changes made to the control point and add it to the list of control points. ''' self.copyCP() # Create the first control point for XML from scratch if any(self.cp.values()): is_at_end = (self.navCounter+1 == len(self.controlPoints)) if is_at_end: cur_cp = self.cp.copy() self.controlPoints[-1] = cur_cp self.controlPoints.append(None) self.cpdata.add_cp(**cur_cp) self._go_to_CP(len(self.controlPoints)-1) self.clearCP() # For modifying the values of exiting control points. # This corresponds to the edit part of the Add/Edit button elif self.navCounter < len(self.controlPoints)-1: # Edit: Change the random point cur_cp = self.cp.copy() self.controlPoints[self.navCounter] = cur_cp self.cpdata.set_cp(self.navCounter, **cur_cp) else: print self.navCounter print self.controlPoints raise Exception else: pass
# Activate on pressing Done button
[docs] def doneCP(self): ''' All control points have been generated. Tear down self.controlPoints and close window ''' # Note: After add/edit there is always an extra # empty point added. Hence always remove last point if self.controlPoints[len(self.controlPoints)-1] is None: self.controlPoints.pop() # Close the dialog box emitting int signal '1' for exec_ QDialog.done(self, 1)
# Activate by pressing Load MLC button
[docs] def openMlcFile(self): ''' Add the MLC file from a text file. This function assumes that MLCs are stored in a text file with two lines corresponding to each MLC leaves. ''' direc = "." fileObj = QFileDialog.getOpenFileName(self, "Choose an MLC txt file", dir=direc, filter="Text files (*.txt)") fileName = fileObj[0] if os.path.isfile(fileName): fid = open(fileName, "r") self.cp["AB"] = (fid.readline().strip(), fid.readline().strip()) fid.close() else: print "No file selected"
# This function executes when user types in a textbox and # then confirms, with <Enter>
[docs] def randomCpoint(self): ''' Jump to any control point and populate the control point window accordingly. ''' index = int(self.randomCP.text()) self._go_to_CP(index)
# --- The four functions below are associated with navigation. # Their association with buttons is left as an exercise.
[docs] def firstCP(self): ''' Jump to the last control point of the XML beam. ''' self._go_to_CP(0)
[docs] def lastCP(self): ''' Jump to the first control point of the XML beam. ''' # it's -2 because -1 is the None occupied control point self._go_to_CP(len(self.controlPoints)-2)
[docs] def nextCP(self): ''' Go to the next control point in the control point window. ''' self._go_to_CP(self.navCounter+1)
[docs] def prevCP(self): ''' Show previous control point in control point window. ''' self._go_to_CP(self.navCounter-1)
# --- __init__ private methods def _init_nav(self): self.navCounter = 0 # For previous and next buttons self.nextButton.clicked.connect(self.nextCP) self.previousButton.clicked.connect(self.prevCP) self.firstButton.clicked.connect(self.firstCP) # Jump to first control point self.lastButton.clicked.connect(self.lastCP) # jump to last control point self.randomCP.returnPressed.connect(self.randomCpoint) def _init_cp(self): self.editAddButton.clicked.connect(self.editAddCP) def _init_done_cp(self): self.doneButton.clicked.connect(self.doneCP) # Print and close the cp dialog box def _init_xml(self, xmlCP): self.controlPoints = xmlCP.append(None) self.cp = xmlCP[0] self.displayCP(self.cp) self.xmlFlag = True # --- __init__ private end # --- navigation private methods, interface with UI def _go_to_CP(self, index): # guard against invalid indices if index < 0 or index > len(self.controlPoints)-1: return self.dispCount.setText(str(index)) if self.controlPoints[index] is None: self.clearCP() else: self.displayCP(self.controlPoints[index]) self.navCounter = index def copyCP(self): ''' Copy field values from the current screen ''' for (field, lineEdit) in zip(self.fields, self.lineedits): self.cp[field] = lineEdit.text() def clearCP(self): ''' Clear all the field values ''' for (field, lineEdit) in zip(self.fields, self.lineedits): self.cp[field] = lineEdit.clear() self.cp["AB"] = None def displayCP(self, currentCP): ''' Display all the current points to the control point window Note: MLC leaves are not displayed :param currentCP: ''' if currentCP is not None and "AB" in currentCP.keys(): del currentCP["AB"] # Propagate values in case of reading a new XML file if self.xmlFlag is True: self.clearCP() # Clear all the field values # Clear all the field values if currentCP is not None: for field in currentCP.keys(): lineEdit = self.lineedits[self.fields.index(field)] if currentCP[field]: lineEdit.setText(currentCP[field])
# -- end navigation private methods class tvTable(object):#QDialog, ui_velTable.Ui_velTable): ''' Class for generating tolerance and velocity limit tables ''' def __init__(self, parent=None): super(tvTable, self).__init__(parent) self.setupUi(self) self.tableVal = dict() self.fields = ("GantryRtn", "CollRtn", "CouchVrt", "CouchLat", "CouchLng", "CouchRtn", "X1", "X2", "Y1", "Y2") self.setModal(True) # Make it modal def accept(self): for field in self.fields: self.tableVal[str(field)] = getattr(self, field).text() QDialog.accept(self)