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