IceTray Modules

So far, we used simple python functions and added them as modules to our tray:

In []:
def PrintEventHeaderStartTime(frame): 
    if frame.Has("I3EventHeader"):
        print frame["I3EventHeader"].event_id
tray.Add(PrintEventHeaderStartTime)

At some point, we might however be required to write much more complicated modules. In this case, we will create a class for our module, which will serve as a container for all the functionalities the module is supposed to provide. In the case of an icetray module, these classes can be conveniently derived from a C++ class called I3Module. This base class already contains commonly used functionalities.

In []:
class MyModule(icetray.I3Module):

The I3Module lives in icetray (http://code.icecube.wisc.edu/projects/icecube/browser/IceTray/projects/icetray/releases/V15-03-00/private/icetray/I3Module.cxx). Also, there exists the I3ConditionalModule, which runs only if a certain condition is satisfied, which is given to the module via an inherent If parameter.
Let's create a new python script called my_first_icetray_module and put in our class.

In []:
class MyModule(icetray.I3ConditionalModule):
    def __init__(self, context):
        super(MyModule, self).__init__(context)
        
    def Configure(self):
        #ToDO Add parameters
    def Physics(self, frame):
        #ToDo Add operations on P frames
    def DAQ(self, frame):
        #ToDo Add operations on Q frames
    def Geometry(self, frame):
        #ToDo Add operations on G frames

Each class needs to be initialized via the init constructor, which always takes an instance of the class, called self as argument. In the case of an I3ConditionalModule, an additional argument, the context, has to be added. Adding the context allows our module to access entries in the context dictionary (see IceTray Introduction).
When you add this module to the tray, init will automatically be called.
The first, and maybe only thing that init is supposed to do is to initialize the module.
We can add as many function as we like. Usually, you want to be able to set parameters for your module: Configure(self) and add some ability to it to work on I3Frames: Physics(self, frame), DAQ(self, frame), Geometry(self, frame).
Depending on which frame is fed into the module, the appropriate function will be run automagically.
If e.g. the DAQ function doesn't exist in the module, all Q-frames will be ignored.
Note that functions cannot be left empty, so the above example wont run as is!

In []:
def Physics(self, frame):
    self.PushFrame(frame)
    #ToDo Add operations on P frames

Each function in the module can pass on the frame it is operating on to the next module.
Otherwise, it will be removed from the stream (this can be used to cut events).
The full module script will look like this:

In []:
#!/usr/bin/env python

import sys

from icecube import icetray, dataio

from I3Tray import *

outfile = sys.argv[1]
infiles = sys.argv[2:]

tray = I3Tray()

tray.Add("I3Reader",FilenameList=infiles)

def my_dump(frame, printy=True):
    if printy:
        print frame
tray.Add(my_dump,"print_me", printy=True, Streams=[icetray.I3Frame.Physics,icetray.I3Frame.DAQ])

class MyModule(icetray.I3ConditionalModule):
    def __init__(self, context):
        super(MyModule, self).__init__(context)
        self.AddParameter("EatPhysicsFrames","I will eat physics frames if you want me to", False)
    def Configure(self):
        self.eat_frame = self.GetParameter("EatPhysicsFrames")
    def Physics(self, frame):
        print "Got a physics frame"
        if not self.eat_frame:
            self.PushFrame(frame)
    def DAQ(self, frame):
        self.PushFrame(frame)
    def Geometry(self, frame):
        pass
        
tray.Add(MyModule,EatPhysicsFrames=False)  

tray.Add(my_dump,"print_me_again", printy=True, Streams=[icetray.I3Frame.Physics,icetray.I3Frame.DAQ])

#outstream = [icetray.I3Frame.TrayInfo,icetray.I3Frame.Geometry,icetray.I3Frame.DAQ,icetray.I3Frame.Physics]
#tray.Add("I3Writer", streams=outstream, DropOrphanStreams=[icetray.I3Frame.DAQ], filename=outfile)
tray.Add("I3Writer", DropOrphanStreams=[icetray.I3Frame.DAQ], filename=outfile)

tray.Execute(100)
tray.Finish()

We need to import all required software again, create the tray and read-in a file.
We also want to dump the frame. We can either use the Dump module that is implemented in icetray, or write our own.
As an example, we want our MyModule class to be able to remove P-frames if we ask it to do so.
First, we need to add a parameter to the initialization that allows us to tell our module if it should push (keep) P-frames or not: EatPhysicsFrames.
Out module shall take this parameter to evaluate if the P-frame is to be pushed.
You can test if this works by looking at the resulting I3File with dataio-pyshovel, or simply by looking at how many frames were written (this number is output on the very last line in the shell).
Also notice what happens with our Q-frames if the P-frames are deleted.

Exercise: Modify the module so only every 2nd P-frame gets written.