#!/usr/bin/env python
"""
==============================================================================
CERN@school Simple Frame Viewer
==============================================================================
This script can be used to read and display Timepix data stored in the
ROOT format. See the Allpix README.md for further instructions.
This code owes a huge debt of thanks to:
Eli Bendersky (eliben@gmail.com)
http://eli.thegreenplace.net/
with respect to the PyQt GUI implementation.
"""
# Import the code needed to manage files.
import sys, os, inspect, glob
# Import the argument parser functionality.
import argparse
# Import the required ROOT libraries - hurrah for PyROOT!
from ROOT import TFile, gSystem
# Load in the (skeleton) Frame class - a bare-minimum class that
# provides the ROOT file format interface.
gSystem.Load('Frame_C')
# Import the Graphical User Interface (GUI) libraries.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Import the plotting libraries (and their interfaces to the GUI libraries).
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
# ...for the figures.
from matplotlib.figure import Figure
# ...for the colour scaling.
from matplotlib import colorbar, colors
# ...for the plotting.
import matplotlib.pyplot as plt
# ...for nice text.
from matplotlib import rc
# Uncomment to use LaTeX for the plot text.
rc('font',**{'family':'serif','serif':['Computer Modern']})
rc('text', usetex=True)
# Import the data structures package.
from datastructs import *
# Import the validation package.
from mfvalidator import *
class AppForm(QMainWindow):
""" The main GUI window - Qt-powered.
"""
def __init__(self, args, parent=None):
""" Constructor. """
QMainWindow.__init__(self, parent)
# Set the window title.
self.setWindowTitle('CERN@school: ROOT Frame Viewer')
## The current frame number.
self.fn = 0
### Dictionary for the detector data files.
#self.datafile = {}
# Populate the data file dictionary.
self.datafile = Data(args.inputFilePath)
## The maximum frame number.
self.fmax = self.datafile.nframes - 1
## The pixel dictionary {frame number:pixels}
self.data = [ {} ]
## Dictionary of the maximum count values for the frames.
self.cmaxdict = {}
self.metadata = [ {} ]
# Get the first frame figure.
# Create the mpl Figure and FigCanvas objects.
# 5x4 inches, 100 dots-per-inch
#
self.dpi = 72
## The colour map for the pixel counts.
self.cmap = plt.cm.hot # "Hot" used.
## The current maximum count value.
self.cmax = 1
# The figure and array of axes for the frame display.
self.fig, self.axarr = plt.subplots(1, 1, figsize=(8.0,8.0), dpi=self.dpi)
# Remove the axis labels.
self.axarr.xaxis.set_visible(False)
self.axarr.yaxis.set_visible(False)
self.axarr.set_xlim([0,256])
self.axarr.set_ylim([0,256])
self.create_menu()
self.create_main_frame()
self.framenum_textbox.setText('%d' % self.fn)
self.set_frame()
def save_plot(self):
file_choices = "PNG (*.png)|*.png"
path = unicode(QFileDialog.getSaveFileName(self,
'Save file', '',
file_choices))
if path:
self.canvas.print_figure(path, dpi=self.dpi)
self.statusBar().showMessage('Saved to %s' % path, 2000)
def on_about(self):
msg = """ A Simple ROOT-based Frame Display:
* Browse frames using the navigation buttons below.
* Set the maximum count value (i.e. white in the colour scale)
with the Max. C text box.
"""
QMessageBox.about(self, "About the ROOT Frame Display.", msg.strip())
# def on_pick(self, event):
# # The event received here is of the type
# # matplotlib.backend_bases.PickEvent
# #
# # It carries lots of information, of which we're using
# # only a small amount here.
# #
# box_points = event.artist.get_bbox().get_points()
# msg = "You've clicked on a bar with coords:\n %s" % box_points
#
# QMessageBox.information(self, "Click!", msg)
def on_draw(self):
""" Redraws the figure.
"""
# Plot the frames.
self.plot_frames()
# Update the metadata.
self.update_metadata()
# Redraw the canvas.
self.canvas.draw()
def create_main_frame(self):
""" Create the main frame of the GUI display.
"""
## The main frame.
self.main_frame = QWidget()
## The canvas for the frame display figure.
self.canvas = FigureCanvas(self.fig)
# Set the canvas parent as the main frame.
self.canvas.setParent(self.main_frame)
# Other GUI controls
#--------------------
# Frame navigation
#------------------
self.frame_label = QLabel("Frame:")
self.first_button = QPushButton("&First")
self.connect(self.first_button, SIGNAL('clicked()'), self.first_frame)
self.prev_button = QPushButton("&Previous")
self.connect(self.prev_button, SIGNAL('clicked()'), self.prev_frame)
self.framenum_textbox = QLineEdit()
self.framenum_textbox.setMinimumWidth(50)
self.connect(self.framenum_textbox, SIGNAL('editingFinished ()'), self.set_frame_from_textbox)
self.next_button = QPushButton("&Next")
self.connect(self.next_button, SIGNAL('clicked()'), self.next_frame)
self.last_button = QPushButton("&Last")
self.connect(self.last_button, SIGNAL('clicked()'), self.last_frame)
# Display
#---------
self.cmax_label = QLabel("Max. C:")
self.cmax_textbox = QLineEdit()
self.cmax_textbox.setText('%d' % self.cmax)
self.cmax_textbox.setMinimumWidth(50)
self.cmax_textbox.setMaximumWidth(50)
self.connect(self.cmax_textbox, SIGNAL('editingFinished ()'), self.set_cmax)
# Define the layout with box sizers
#-----------------------------------
## Layout box for the frame navigation panel.
hbox = QHBoxLayout()
# Loop over the widgets.
for w in [ \
self.frame_label, \
self.first_button, \
self.prev_button, \
self.framenum_textbox, \
self.next_button, \
self.last_button, \
self.cmax_label, \
self.cmax_textbox
]:
hbox.addWidget(w)
hbox.setAlignment(w, Qt.AlignVCenter)
## Layout box for the frame display and control panel.
vbox = QVBoxLayout()
# Add the canvas and the control panel.
vbox.addWidget(self.canvas)
vbox.addLayout(hbox)
#self.main_frame.setLayout(vbox)
#self.main_frame.setFixedHeight(630)
#self.main_frame.setFixedWidth(630)
#self.setCentralWidget(self.main_frame)
# The box for the frame status.
statBox = QGridLayout()
statBox.setSpacing(5)
self.gridlabels = {}
self.gridvalues = {}
# #widgetlist1
for i, key in enumerate([ \
"#Payload", \
"Hit pixels", \
"Total counts", \
"Data type", \
#
"#Payoad info.", \
"Dataset ID", \
"Frame ID", \
"Frame size", \
"Original format", \
"Occupancy", \
#
"#Geospatial info.", \
"Location", \
"Altitude", \
"Lab rotation", \
"Lab Omega", \
#
"#Temporal info.", \
"Start time", \
"Start time (str)", \
"Acquisition time", \
"End time (str)", \
#
#"spacer1", \
"#Acquisition", \
"Acq. mode", \
"%Counters", \
"%HW timer mode", \
"%Auto erase int.", \
"%Int. counter", \
"%Last trigger time", \
"%Coinc. mode", \
"%Coinc. delay", \
"%Coinc. live time", \
"Pixelman ver", \
]):
if key[0]=="#":
self.gridlabels[key] = QLabel("%s" % (key[1:]))
elif "?" in key:
self.gridlabels[key] = QLabel("%s" % (key))
elif "%" in key:
self.gridlabels[key] = QLabel("%s" % (key[1:]))
elif "spacer" in key:
self.gridlabels[key] = QLabel("")
else:
self.gridlabels[key] = QLabel("%s:" % (key))
self.gridlabels[key].setFixedWidth(100)
statBox.addWidget(self.gridlabels[key], i, 0)
self.gridvalues[key] = QLabel("")
self.gridvalues[key].setFixedWidth(220)
#self.gridvalues[key].setReadOnly(True)
statBox.addWidget(self.gridvalues[key], i, 1)
# #widgetlist2
for i, key in enumerate([ \
"#Detector info.", \
"Chipboard ID", \
"Custom name", \
"Firmware", \
"Interface", \
"Medipix type", \
"Applied filters", \
"Position", \
"Rotation", \
#
#"spacer1", \
"#Settings", \
"Polarity", \
"Bias voltage", \
"IKrum", \
"Disc.", \
"Preamp", \
"BuffAnalogA", \
"BuffAnalogB", \
"Hist", \
"THL", \
"THLCoarse", \
"Vcas", \
"FBK", \
"GND", \
"THS", \
"BiasLVDS", \
"RefLVDS", \
"Medipix clock", \
"Timepix clock", \
"BS preamp?", \
#
"#Source info.", \
"Source ID", \
]):
if key[0]=="#":
self.gridlabels[key] = QLabel("%s" % (key[1:]))
elif "?" in key:
self.gridlabels[key] = QLabel("%s" % (key))
elif "spacer" in key:
self.gridlabels[key] = QLabel("")
else:
self.gridlabels[key] = QLabel("%s:" % (key))
self.gridlabels[key].setFixedWidth(100)
statBox.addWidget(self.gridlabels[key], i, 2)
self.gridvalues[key] = QLabel("")
self.gridvalues[key].setFixedWidth(180)
#self.gridvalues[key].setReadOnly(True)
statBox.addWidget(self.gridvalues[key], i, 3)
hbox2 = QHBoxLayout()
hbox2.addLayout(statBox)
hbox2.addLayout(vbox)
self.main_frame.setLayout(hbox2)
self.main_frame.setFixedHeight(630)
self.main_frame.setFixedWidth(1230)
self.setCentralWidget(self.main_frame)
## Create the menu.
#
# (Taken directly from Eli's example code - thanks!)
def create_menu(self):
self.file_menu = self.menuBar().addMenu("&File")
load_file_action = self.create_action("&Save plot",
shortcut="Ctrl+S", slot=self.save_plot,
tip="Save the plot")
quit_action = self.create_action("&Quit", slot=self.close,
shortcut="Ctrl+Q", tip="Close the application")
self.add_actions(self.file_menu,
(load_file_action, None, quit_action))
self.help_menu = self.menuBar().addMenu("&Help")
about_action = self.create_action("&About",
shortcut='F1', slot=self.on_about,
tip='About the demo')
self.add_actions(self.help_menu, (about_action,))
## Add the menu actions.
#
# @param [in] The target.
# @param [in] The actions.
def add_actions(self, target, actions):
for action in actions:
if action is None:
target.addSeparator()
else:
target.addAction(action)
## Create the menu actions.
#
def create_action( self, text, slot=None, shortcut=None,
icon=None, tip=None, checkable=False,
signal="triggered()"):
action = QAction(text, self)
if icon is not None:
action.setIcon(QIcon(":/%s.png" % icon))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
if slot is not None:
self.connect(action, SIGNAL(signal), slot)
if checkable:
action.setCheckable(True)
return action
## Set the frame number.
#
def set_frame(self):
# Change the frame number in the text box to the current frame.
self.framenum_textbox.setText('%d' % self.fn)
# Check if the dictionary has the data.
if self.fn not in self.data[0]:
self.load_frame()
# Set the maximum C count value to that of the frame's.
self.cmax_textbox.setText('%d' % self.cmaxdict[self.fn])
self.cmax = self.cmaxdict[self.fn]
self.set_cmax
# Update the canvas with the new frame.
self.on_draw()
def first_frame(self):
""" Go to the first frame. """
self.fn = 0
self.set_frame()
def prev_frame(self):
""" Go to the previous frame. """
if self.fn > 0:
self.fn -= 1
self.set_frame()
def set_frame_from_textbox(self):
""" Go to the frame in the textbox. """
self.fn = int(self.framenum_textbox.text())
self.set_frame()
def next_frame(self):
""" Go to the next frame. """
if self.fn < self.fmax:
self.fn += 1
self.set_frame()
def last_frame(self):
""" Go to the last frame. """
self.fn = self.fmax
self.set_frame()
def set_cmax(self):
""" Set the maximum count value to display (for the colour map). """
self.cmax = int(self.cmax_textbox.text())
self.on_draw()
def plot_frames(self):
""" Plot the frame contents on the frame display.
"""
self.plot_frame(self.axarr, self.data[0][self.fn], 0)
## Plot the individual frame data for a given detector.
# @param self The object pointer.
# @param [in] ax The axis upon which to display the frame.
# @param [in] pxs The pixel dictionary {X:C}.
# @param [in] tpxnum The Timepix detector number.
def plot_frame(self, ax, pxs, tpxnum):
# Clear the display.
ax.clear()
# Set the background colour.
ax.set_axis_bgcolor('#82bcff')
#ax.set_title("TPX%d" % (tpxnum))
# Loop over the pixels in the pixel dictionary.
for X, C in pxs.iteritems():
## The pixel x value.
x = X%256;
## The pixel y value.
y = X/256
#print("* DEBUG: Pixel found at (%3d, %3d) with C = %d" % (x,y,C))
if C > 0:
color = float(C) / float(self.cmax)
#print("* DEBUG: C = %d - setting color to %f" % (C, color))
# Plot the pixel.
ax.add_patch(plt.Rectangle((x,y),1,1, edgecolor=self.cmap(color), facecolor=self.cmap(color)))
else:
ax.add_patch(plt.Rectangle((x,y),1,1, edgecolor='#33aacc', facecolor='#33aacc'))
## Load the current frame into memory.
#
def load_frame(self):
# Loop over the detectors (TPX0 - TPX4).
cmax = 1
for i, pxsdict in enumerate(self.data):
# If the frame number is not found in frame-pixels dictionary,
# retrieve the data from that and add it to the detector's
# dictionary.
if self.fn not in pxsdict:
# Retrieve the pixel data for the detector/frame
#pxsdict[self.fn] = self.datafiles[i].getPixels(self.fn)
pxsdict[self.fn], detcmax = self.datafile.getPixels(self.fn)
# Adjust the maximum C, if required.
if detcmax > cmax:
cmax = detcmax
# Assign the maximum value from all five detectors for this frame.
if self.fn not in self.cmaxdict:
self.cmaxdict[self.fn] = cmax
for i, metadatadict in enumerate(self.metadata):
if self.fn not in metadatadict:
metadatadict[self.fn] = self.datafile.getMetadata(self.fn)
def update_metadata(self):
self.gridvalues["Hit pixels" ].setText(self.metadata[0][self.fn].getNumHits())
self.gridvalues["Total counts" ].setText(self.metadata[0][self.fn].getTotalCounts())
self.gridvalues["Data type" ].setText(self.metadata[0][self.fn].getDataType())
#
self.gridvalues["Dataset ID" ].setText(self.metadata[0][self.fn].getDatasetID())
self.gridvalues["Frame ID" ].setText(self.metadata[0][self.fn].getFrameID())
self.gridvalues["Frame size" ].setText(self.metadata[0][self.fn].getFrameSize())
self.gridvalues["Original format"].setText(self.metadata[0][self.fn].getFormat())
self.gridvalues["Occupancy" ].setText(self.metadata[0][self.fn].getOccupancy())
#
# Acquisition information
#-------------------------
self.gridvalues["Acq. mode" ].setText(self.metadata[0][self.fn].getAcqMode())
na = "n/a"
self.gridvalues["%Counters" ].setText(na)
self.gridvalues["%HW timer mode" ].setText(na)
self.gridvalues["%Auto erase int." ].setText(na)
self.gridvalues["%Int. counter" ].setText(na)
self.gridvalues["%Last trigger time"].setText(na)
self.gridvalues["%Coinc. mode" ].setText(na)
self.gridvalues["%Coinc. delay" ].setText(na)
self.gridvalues["%Coinc. live time" ].setText(na)
self.gridvalues["Pixelman ver" ].setText(self.metadata[0][self.fn].getPixelmanVer())
#
# Geospatial information
#------------------------
self.gridvalues["Location" ].setText(self.metadata[0][self.fn].getLocation())
self.gridvalues["Altitude" ].setText(self.metadata[0][self.fn].getAltitude())
self.gridvalues["Lab rotation"].setText(self.metadata[0][self.fn].getLabRotation())
self.gridvalues["Lab Omega" ].setText(self.metadata[0][self.fn].getLabOmega())
#
# Temporal information
#----------------------
self.gridvalues["Start time" ].setText(self.metadata[0][self.fn].getStartTime())
self.gridvalues["Start time (str)"].setText(self.metadata[0][self.fn].getStartTimeStr())
self.gridvalues["Acquisition time"].setText(self.metadata[0][self.fn].getAcqTime())
self.gridvalues["End time (str)" ].setText(self.metadata[0][self.fn].getEndTimeStr())
#
# Detector information
#----------------------
self.gridvalues["Chipboard ID" ].setText(self.metadata[0][self.fn].getChipboardID())
self.gridvalues["Custom name" ].setText(self.metadata[0][self.fn].getCustomName())
self.gridvalues["Firmware" ].setText(self.metadata[0][self.fn].getFirmware())
self.gridvalues["Interface" ].setText(self.metadata[0][self.fn].getInterfaceType())
self.gridvalues["Medipix type" ].setText(self.metadata[0][self.fn].getMpxType())
self.gridvalues["Applied filters"].setText(self.metadata[0][self.fn].getAppFilters())
self.gridvalues["Position" ].setText(self.metadata[0][self.fn].getDetPosition())
self.gridvalues["Rotation" ].setText(self.metadata[0][self.fn].getDetRotation())
#
# Detector settings
#-------------------
self.gridvalues["Polarity" ].setText(self.metadata[0][self.fn].getPolarity())
self.gridvalues["Bias voltage" ].setText(self.metadata[0][self.fn].getBiasVoltage())
self.gridvalues["IKrum" ].setText(self.metadata[0][self.fn].getIKrum())
self.gridvalues["Disc." ].setText(self.metadata[0][self.fn].getDisc())
self.gridvalues["Preamp" ].setText(self.metadata[0][self.fn].getPreamp())
self.gridvalues["BuffAnalogA" ].setText(self.metadata[0][self.fn].getBuffAnalogA())
self.gridvalues["BuffAnalogB" ].setText(self.metadata[0][self.fn].getBuffAnalogB())
self.gridvalues["Hist" ].setText(self.metadata[0][self.fn].getHist())
self.gridvalues["THL" ].setText(self.metadata[0][self.fn].getTHL())
self.gridvalues["THLCoarse" ].setText(self.metadata[0][self.fn].getTHLCoarse())
self.gridvalues["Vcas" ].setText(self.metadata[0][self.fn].getVcas())
self.gridvalues["FBK" ].setText(self.metadata[0][self.fn].getFBK())
self.gridvalues["GND" ].setText(self.metadata[0][self.fn].getGND())
self.gridvalues["THS" ].setText(self.metadata[0][self.fn].getTHS())
self.gridvalues["BiasLVDS" ].setText(self.metadata[0][self.fn].getBiasLVDS())
self.gridvalues["RefLVDS" ].setText(self.metadata[0][self.fn].getRefLVDS())
self.gridvalues["Medipix clock"].setText(self.metadata[0][self.fn].getMpxClock())
self.gridvalues["Timepix clock"].setText(self.metadata[0][self.fn].getTpxClock())
self.gridvalues["BS preamp?" ].setText(self.metadata[0][self.fn].getBSEnabled())
#
# Source information
#--------------------
self.gridvalues["Source ID"].setText(self.metadata[0][self.fn].getSourceID())
def main(args):
""" The main program.
"""
app = QApplication(sys.argv)
form = AppForm(args)
form.show()
app.exec_()
#
# The main event!
#
if __name__ == "__main__":
print("================================================")
print(" CERN@school ROOT Frame Viewer (Python) ")
print("================================================")
# Get the datafile paths from the command line.
parser = argparse.ArgumentParser()
parser.add_argument("inputFilePath", help="The path of the Timepix data file." )
args = parser.parse_args()
# Call the main program.
main(args)