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_())