Examples

This section presents some example code snippets which are ment to be run from within a Jupyter notebook. All examples can be found in the git repository in doc/examples. If you want to run SamuROI from within scripts, see SamuROI from within normal python scripts.

Lets get started with how to show the GUI window. Only a few lines are required to load a tif file and show up the gui:

# samuroi imports
from samuroi import SamuROIWindow
from samuroi.plugins.tif import load_tif

# load the data into a 3D numpy array
data = load_tif('/path/to/your/file.tif')

# show the gui for the loaded data
mainwindow = SamuROIWindow(data=data)

# maybe necessary depending on IPython
mainwindow.show()

The underlying SamuROIData object

SamuROI tries to follow a strict separation of data and GUI which is sometimes referred as Document/View or Model/View pattern (for more infos see the technical description in the paper and the documentation of samuroi.SamuROIData). When a SamuROI window is created, it also creates an underlying SamuROIData object which can be obtained by:

# get handle on the document of the main window
doc = mainwindow.segmentation

In the following examples we will show how one can read and manipulate many of the attributes of the samuroi.SamuROIData class.

Working with ROIs

All ROIs of a SamuROIData object are stored within the attribute masks of type samuroi.maskset.MaskSet. This class keeps track of the types of masks it stores and provides events for added and removed masks. It is iterable and checks that no mask is added twice (one can add multiple copies of a mask, but not the same object). Iteration over all masks is simple as

# iterate over all masks
for m in doc.masks:
   print m

Because the samuroi.maskset.MaskSet also introduces a hierarchy for the types of masks it stores, one can also iterate through all masks of a certain type like this:

# iterate over all branch masks
from samuroi.masks.branchmask import BranchMask
for bm in doc.masks[BranchMask]:
   print bm

Since masks are stored in a set, the cannot be retrieved by a specific index, i.e. the following wont work:

# iterate over all branch masks
first_mask = doc.masks[0] # ERROR, cannot use [0] to get first of an iterable
first_branch = doc.masks[BranchMask][0] # ERROR, same as above

Instead masks can be identified by their name:

Warning

There can be multiple masks with the same name. It is up to the user to ensure that the name is unique.

# iterate over all branch masks
from samuroi.masks.branchmask import BranchMask
for bm in doc.masks[BranchMask]:
   if bm.name == 'my_special_branch':
      break

print bm.name # yeah, I found my mask

Alternatively:

mybranch = next(branch for branch in doc.masks[BranchMask] if branch.name == 'my_special_branch')

Extracting a trace from a mask

In SamuROI, the masks itself are distinct objects which do not know about data and overlay. Hence calculate the trace of for a mask, one has to combine the 3D dataset, the 2D overlay mask and the geometric information from the mask itself. Because the geometric information is different for different kind of masks (e.g. Circle, Polygon, or Segmentation) this combination is done by the mask itself (samuroi.mask.Mask.__call__()):

# get some mask
mybranch = next(doc.masks[BranchMask])

# combine data, overlay and geometry
trace = mybranch(doc.data,doc.overlay)

# thats it, trace now is a 1D numpy array, we can e.g. plot it with matplotlib
plt.figure()
plt.plot(trace)

Internally, the SamuROI widgets do the very same thing to plot their data.

Extracting detected events

Running the event detection will add detection results to all mask. Having a detection result does not mean, that there is an event. The detection result only describes the outcome of the event detection. For the implemented event detection based on template matching (Clements, J.M. Bekkers) this result hold the optimal scaling and offset values, aswell as the “matching criterion” curve. This data can be accessed via:

# get some mask
mybranch = next(doc.masks[BranchMask])

# print the detection results
print mybranch.events # This only works, if one has done an event detection run before!

# maybe plot the matching criterion ?
plt.figure()
plt.plot(mybranch.events.crit)

Adding and removing masks

We can simply add and remove rois from the samuroi.maskset.MaskSet. E.g. we can add all masks from a swc file like this. (If you need this functionality you can also call samuroi.SamuROIData.load_swc())

from samuroi.masks.circle import CircleMask
from samuroi.masks.branch import BranchMask
from samuroi.plugins.swc import load_swc
swc = load_swc("path/to/file.swc")
for b in swc.branches:
    if len(b) > 1:
        mask = BranchMask(data=b)
    else:
        mask = CircleMask(center=b[['x', 'y']][0], radius=b['radius'][0])
    # this will add the mask to the maskset and trigger maskset's added event.
    doc.masks.add(mask)

Removing masks is similar

from samuroi.masks.branch import BranchMask
# use try catch since there might not be any BranchMask in the MaskSet
try:
    # get handle on the "first" branch in the maskset
    first_branch = doc.masks[BranchMask].next()
    # remove the mask, will trigger the preremove and removed event of the masklist.
    doc.masks.discard(first_branch)
except:
    # we cant remove anything if its not there
    pass

Warning

Removing masks from the maskset from within an iteration through its elements may lead to undefined behaviour.

Installing a custom postprocessor

Often one wants to apply some custom postprocessor to traces produces by the ROIs. This can be achieved by installing a custom postprocessor. E.g. if you click on the “detrend” and “smoothen” buttons in the gui, respective postprocessors will transform the trace before it gets displayed in any widget. In this example we will show how we can transform the trace such that it has a zero mean over time.

def zero_mean_postprocessor(trace):
  """
  :param trace: a 1D numpy array holding the trace of the ROI.
  :return: a 1D numpy array with transformed trace.
  """
  return trace - numpy.mean(trace)

# change the postprocessor
doc.postprocessor = zero_mean_postprocessor

Pretty easy, huh? For something more advanced, an event detection postprocessor and best fit overlay, have a look at SamuROI from within normal python scripts!

SamuROI from within normal python scripts

Usually the IPython notebook takes care of some Qt mechanics that are required by SamuROI. Specifically this is: The Qt main event loop, which handles all direct user input on the GUI. Hence when one wants to run SamuROI from within a script, this handling has to be done by one self. The following example shows what is necessary and provides a nice starting point for your own applications :-)

import numpy

from samuroi.gui.samuroiwindow import SamuROIWindow
from samuroi.plugins.tif import load_tif
from samuroi.plugins.swc import load_swc
from samuroi.masks.segmentation import Segmentation as SegmentationMask

# requirements for template matching and post processing
from samuroi.event.biexponential import BiExponentialParameters
from samuroi.event.template_matching import template_matching
from samuroi.util.postprocessors import PostProcessorPipe, DetrendPostProcessor

import sys
from PyQt4 import QtGui

import argparse

parser = argparse.ArgumentParser(description='Open SamuROI and load some data.')

parser.add_argument('filename', type=str, help='The filename of the tif file to use as data.')

parser.add_argument('--swc', dest='swcfiles', type=str, action='append', help='Filename of swc file to load.')

parser.add_argument('--segmentation', dest='segmentations', type=str, action='append',
                    help='Filename of segmentations to load. (.npy files)')

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    args = parser.parse_args()

    data = load_tif(args.filename)
    morphology = numpy.max(data, axis=-1)

    from samuroi.plugins.baseline import linbleeched_deltaF

    # data = linbleeched_deltaF(data)

    # show the gui for the filtered data
    mainwindow = SamuROIWindow(data=data, morphology=morphology)

    for filename in args.swcfiles:
        swc = load_swc(filename)
        mainwindow.segmentation.load_swc(swc)

    if args.segmentations is not None:
        for filename in args.segmentations:
            segdata = numpy.load(filename)
            seg = SegmentationMask(data=segdata, name="filename")
            mainwindow.segmentation.masks.add(seg)

    # here we can set the template parameters
    params = BiExponentialParameters(tau1=150., tau2=1.)

    kernel = params.kernel()
    # crop the long decay phase of the kernel, otherwise boundary effects get to strong
    # and bursts of events cannot be detected correctly, since the do not fully decay
    kernel = kernel[0:120]


    # if required one can zero pad the kernel to the left to enforce a "silent" phase before an event
    # this will again lead to trouble when detecting bursts of events
    # kernel = numpy.concatenate((numpy.zeros(number_of_required_silent_frames), kernel))

    def matching_postprocess(trace):
        # run the template matching algorithm
        result = template_matching(data=trace, kernel=kernel, threshold=4.)
        return result.crit


    # we either can use the matching postprocessor directly, or add a detrend step in front of it
    postprocessor = PostProcessorPipe()
    postprocessor.append(DetrendPostProcessor())
    postprocessor.append(matching_postprocess)

    # add a button to the main window postprocessor toolbar for enabling the template matching
    action = mainwindow.toolbar_postprocess.addAction("template matching")
    action.setToolTip("Run first linear detrend and then apply the template matching to the trace, then show the"
                      "detection criterion instead of the trace data.")

    # a variable for the line plotting the best fit in the trace widget
    fitcurve = None


    def install_pp(pp):
        if fitcurve is not None:
            fitcurve.remove()
        mainwindow.segmentation.postprocessor = postprocessor


    # if we click the button in the main window to install the postprocessor
    action.triggered.connect(install_pp)


    def redraw_fit():
        global fitcurve
        # the index of the frame of interest
        i = mainwindow.segmentation.active_frame

        # first shift to the active frame, then go back half the kernel size, because the values in we want to plot
        # the kernel centered around the selected frame
        x = numpy.arange(0, len(kernel)) + i - len(kernel) / 2

        if fitcurve is not None:
            fitcurve.remove()

        # we want to calculate the fit for the first cuve in the trace widget, hence, get the y-data of the line
        _, trace = mainwindow.tracedockwidget.canvas.axes.lines[0].get_data()
        result = template_matching(data=trace, kernel=kernel, threshold=4.)

        # we need to apply the best found scale and offset to the kernel
        fitcurve, = mainwindow.tracedockwidget.canvas.axes.plot(x, kernel * result.s[i] + result.c[i])


    mainwindow.segmentation.active_frame_changed.append(redraw_fit)

    mainwindow.show()
    sys.exit(app.exec_())