This notebook contains a step-by-step example of how to use the FISSA toolbox. See basic_usage.py (or basic_usage_windows.py for Windows users) for a short example script outside of a notebook interface.
In [1]:
# Import the FISSA toolbox
import fissa
In this notebook, we will be plotting our results using HoloViews, but you can use any plotting library you prefer (matplotlib, bokeh, etc).
In [2]:
# Import our plotting toolbox, and enable options for embedded notebook figures
import holoviews as hv
%load_ext holoviews.ipython
%output widgets='embed'
To run a separation step with fissa, you need create a fissa.Experiment object, which will hold your extraction parameters and results.
The inputs to the fissa.Experiment instance are the:
Images can be defined as a folder with tiff stacks:
images = 'folder'
Where each tiff stack in the folder is a trial with several frames.
Or the data can also be given as a list of arrays if not stored as tiffs:
images = [array1, array2, array3, ...]
For ROIs either a set of ROIs across all images should be defined, or a set of ROIs per image.
If the ROIs were defined using ImageJ use ImageJ's export function to save them in a zip. Then, indicate the locations as a list:
rois = 'rois.zip' # for a single set of rois across images
rois = ['rois1.zip', 'rois2.zip', ...] # for a roiset for each image
Defining a different roiset per image can be useful if you need to adjust for motion drift for example.
Then, we can define out experiment:
In [3]:
# Define image and ROI locations
images_location = 'exampleData/20150529'
rois_location = 'exampleData/20150429.zip'
# Define the folder where FISSA's outputs will be stored, so they can be
# quickly reloaded in the future without having to recompute them.
# Make sure to use a different folder for each experiment.
# This argument is optional; if it is not provided, FISSA will not save its
# results for later use.
output_folder = 'fissa_example'
experiment = fissa.Experiment(images_location, rois_location, output_folder)
Previously analyzed experiments in output_folder will be loaded, if they exist, and the next step could be skipped.
In [4]:
experiment.separate()
If you want to redo preparation and/or separation you can set:
experiment.separate(redo_prep=True, redo_sep=True)
(If you redo prepartion this will also redo the separation, to make sure these always match up).
After running experiment.separate() the results are stored as follows.
The ROI outlines, as well as the extra neuropil regions, can be found as in experiment.roi_polys as follows. For cell number c and tiff number t, the set of ROIs for that cell and tiff is at
experiment.roi_polys[c][t][0][0] # basic ROI
experiment.roi_polys[c][t][n][0] # n = 1, 2, 3, .... the neuropil regions
Sometimes ROIs cannot be expressed as a single polygon (e.g. a ring-ROI), in those cases several polygons are used to describe it as:
experiment.roi_polys[c][t][n][i] # i iterates over the different polygons
As an example, plotting the first region of interest plus its surrounding neuropil subregions.
In [5]:
# Visualise the ROI polygon for a sample cell
c = 0
t = 0
neuropil1 = hv.Curve(experiment.roi_polys[c][t][1][0])
neuropil2 = hv.Curve(experiment.roi_polys[c][t][2][0])
neuropil3 = hv.Curve(experiment.roi_polys[c][t][3][0])
neuropil4 = hv.Curve(experiment.roi_polys[c][t][4][0])
cell = hv.Curve(experiment.roi_polys[c][t][0][0])
neuropil1 * neuropil2 * neuropil3 * neuropil4 * cell
Out[5]:
In this example, the ROI (grey) is on the boundary of the image, so the surrounding neuropil subregions are arranged to share the available space.
The final extracted traces can be found in experiment.result as follows. For cell number c and tiff number t, the final extracted trace is given by:
experiment.result[c][t][0, :]
In experiment.result one can find the signals present in the cell ROI, ordered by how strongly they are present (relative to the surrounding regions). experiment.result[c][t][0, :] gives the most strongly present signal, and is seen as the cell's "true" signal. [i, :] for i=1, 2 , 3, ... gives the other signals which are present in the cell ROI.
The raw extracted signals can be found in experiment.raw in the same way. Now in experiment.raw[c][t][i,:], i indicates the region number, with i=0 being the cell, and i=1, 2, 3, ... indicating the surrounding regions.
As an example, plotting the raw and extracted signals for the second trial for the third cell:
In [6]:
c = 2
t = 1
(hv.Curve(experiment.raw[c][t][0, :], label='Raw')
* hv.Curve(experiment.result[c, t][0, :], label='Decontaminated')
)
Out[6]:
It is often useful to calculate the intensity of a signal relative to the baseline value, df/f0, for the traces. This can be done as follows.
Note that by default, f0 is determined as the minimum across all trials (all tiffs) to ensure that results are directly comparable between trials, but you can normalise each trial individually instead if you prefer by setting across_trials=False.
Since FISSA is very good at removing contamination from the ROI signals, the minimum value on the decontaminated trace will typically be 0.. Consequently, we use the minimum value of the (smoothed) raw signal to provide the f0 from the raw trace for both the raw and decontaminated df/f0.
In [7]:
experiment.calc_deltaf(freq=10, across_trials=True)
In [8]:
c = 2
t = 1
(hv.Curve(experiment.deltaf_raw[c][t], label='Raw', vdims=['df/f0']) *
hv.Curve(experiment.deltaf_result[c, t][0, :], label='Decontaminated')
)
Out[8]:
In [9]:
experiment.save_to_matlab()
Loading output_folder/matlab.mat in MATLAB will give you three structs, ROIs, raw, and result.
These interface similarly as experiment.ROIs, experiment.raw, and experiment.result described above. However, Matlab counts from 1 (as opposed to Python counting from 0), such that the ROI, raw trace, and decontaminated trace are all found for cell 0 trial 0 as:
ROIs.cell0.trial0{1} % polygon for the ROI
ROIs.cell0.trial0{2} % polygon for first neuropil subregion
result.cell0.trial0(1,:) % final extracted cell signal
result.cell0.trial0(2,:) % contaminating signal
raw.cell0.trial0(1,:) % raw measured celll signal
raw.cell0.trial0(2,:) % raw signal from first neuropil subregion
In [10]:
trial_of_interest = 1
print(experiment.images[trial_of_interest])
You can get the temporal-mean image for a trial with experiment.means.
In [11]:
hv.Image(experiment.means[trial_of_interest])
Out[11]:
You can also superimpose the cell ROIs on the temporal-mean image as follows.
In [12]:
# Using holoviews
t = trial_of_interest
fig = hv.Raster(experiment.means[t])
for c in range(experiment.nCell):
roi_poly = experiment.roi_polys[c][t][0][0]
x = roi_poly[:, 1]
y = roi_poly[:, 0]
fig *= hv.Curve(zip(x, y))
fig
Out[12]:
In [13]:
# Using matplotlib
import matplotlib.pyplot as plt
t = trial_of_interest
plt.imshow(experiment.means[t], cmap='gray')
for roi_poly in experiment.roi_polys:
# Plot border around cells
# plt.plot(roi_poly[t][0][0][:, 1], roi_poly[t][0][0][:, 0], ':c')
# Fill cells with partially-transparent shaded area
plt.fill(roi_poly[t][0][0][:, 1], roi_poly[t][0][0][:, 0], ':r', alpha=0.2)
plt.show()
In [14]:
# FISSA uses multiprocessing to speed up its processing.
# By default, it will spawn one worker per CPU core on your machine.
# However, if you have a lot of cores and not much memory, you many not
# be able to suport so many workers simultaneously.
# In particular, this can be problematic during the data preparation step
# in which tiffs are loaded into memory.
# The default number of cores for the data preparation and separation steps
# can be changed as follows.
ncores_preparation = 4 # If None, uses all available cores
ncores_separation = None # if None, uses all available cores
# By default, FISSA uses 4 subregions for the neuropil region.
# If you have very dense data with a lot of different signals per unit area,
# you may wish to increase the number of regions.
nRegions = 8
# By default, each surrounding region has the same area as the central ROI.
# i.e. expansion = 1
# However, you may wish to increase or decrease this value.
expansion = 0.75
# The degree of signal sparsity can be controlled with the alpha parameter.
alpha = 0.1
# Set up a FISSA experiment with these parameters
experiment = fissa.Experiment(
images_location,
rois_location,
output_folder,
nRegions=nRegions,
expansion=expansion,alpha=alpha,
ncores_preparation=ncores_preparation,
ncores_separation=ncores_separation,
)
# Extract the data with these new parameters.
# Note that we are using the same output folder as before. Since FISSA has cached
# a result alrady to this directory, its default behaviour is to restore the
# previously generated results.
# To make sure FISSA runs a fresh decontamination process with the new parameters,
# we need to make sure to specify to redo the preparation and separation.
# FISSA will then ignore the cached output and overwrite it with new results.
experiment.separate(redo_prep=True)
We can plot the new results for our example trace from before. Although we doubled the number of neuropil regions around the cell, very little has changed for this example because there were not many sources of contamination.
However, there will be more of a difference if your data has more neuropil sources per unit area within the image.
In [15]:
# Plot the new results
c = 2
t = 1
(hv.Curve(experiment.raw[c][t][0, :], label='Raw')
* hv.Curve(experiment.result[c, t][0, :], label='Decontaminated')
)
Out[15]:
Alternatively, these settings can be refined after creating the experiment object, as follows.
In [16]:
experiment.ncores_preparation = 8
experiment.alpha = 0.1
experiment.expansion = 0.75
By default, FISSA loads entire tiff files into memory at once and then manipulates all ROIs within the tiff. This can sometimes be problematic when working with very large tiff files which can not be loaded into memory all at once. If you have out-of-memory problems, you can activate FISSA's low memory mode, which will cause it to manipulate each tiff file frame-by-frame.
In [17]:
experiment = fissa.Experiment(images_location, rois_location, output_folder, lowmemory_mode=True)
By default FISSA can use tiff files or numpy arrays as its input image data, and numpy arrays or ImageJ zip files for the ROI definitions. It is also possible to give FISSA a custom script in order to work with other custom and/or proprietary formats that might be used in your lab.
To do so, one must define a python file which implements the same functions as the fissa.datahandler module of FISSA.
As an example, see the datahandler_custom.py file in the examples folder.
In [18]:
import datahandler_custom
experiment = fissa.Experiment(
images_location,
rois_location,
output_folder,
datahandler_custom=datahandler_custom,
)