CaImAn’s Demo pipeline (NWB)

This notebook will help to demonstrate the process of CaImAn and how it uses different functions to denoise, deconvolve and demix neurons from a two-photon Calcium Imaging dataset. The demo shows how to construct the `params`, `MotionCorrect` and `cnmf` objects and call the relevant functions. You can also run a large part of the pipeline with a single method (`cnmf.fit_file`). See inside for details. Dataset couresy of Sue Ann Koay and David Tank (Princeton University) This demo pertains to two photon data. For a complete analysis pipeline for one photon microendoscopic data see demo_pipeline_cnmfE.ipynb

More information can be found in the companion paper.


In [4]:
import bokeh.plotting as bpl
import cv2
import glob
import logging
import matplotlib.pyplot as plt
import numpy as np
import os

try:
    cv2.setNumThreads(0)
except():
    pass

try:
    if __IPYTHON__:
        # this is used for debugging purposes only. allows to reload classes
        # when changed
        get_ipython().magic('load_ext autoreload')
        get_ipython().magic('autoreload 2')
except NameError:
    pass

import caiman as cm
from caiman.motion_correction import MotionCorrect
from caiman.source_extraction.cnmf import cnmf as cnmf
from caiman.source_extraction.cnmf import params as params
from caiman.utils.utils import download_demo
from caiman.utils.visualization import plot_contours, nb_view_patches, nb_plot_contour
bpl.output_notebook()
from caiman.paths import caiman_datadir


The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
Loading BokehJS ...

Set up logger (optional)

You can log to a file using the filename parameter, or make the output more or less verbose by setting level to logging.DEBUG, logging.INFO, logging.WARNING, or logging.ERROR. A filename argument can also be passed to store the log file


In [5]:
logging.basicConfig(format=
                          "%(relativeCreated)12d [%(filename)s:%(funcName)20s():%(lineno)s] [%(process)d] %(message)s",
                    # filename="/tmp/caiman.log",
                    level=logging.WARNING)

Select file(s) to be processed

The download_demo function will download the specific file for you and return the complete path to the file which will be stored in your caiman_data directory. If you adapt this demo for your data make sure to pass the complete path to your file(s). Remember to pass the fname variable as a list.


In [6]:
fnames = [os.path.join(caiman_datadir(), 'example_movies/Sue_2x_3000_40_-46.nwb')] # filename to be created or processed

#fnames = ['Sue_2x_3000_40_-46.tif']  # filename to be processed
#if fnames[0] in ['Sue_2x_3000_40_-46.tif', 'demoMovie.tif']:
#    fnames = [download_demo(fnames[0])]

Load the file and save it in NWB format

Converts the original movie to an NWB format if it isn't there already


In [7]:
fr = 15  # imaging rate in frames per second
decay_time = 0.4  # length of a typical transient in seconds
#%% load the file and save it in the NWB format (if it doesn't exist already)
if not os.path.exists(fnames[0]):
    fnames_orig = 'Sue_2x_3000_40_-46.tif'  # filename to be processed
    if fnames_orig in ['Sue_2x_3000_40_-46.tif', 'demoMovie.tif']:
        fnames_orig = [download_demo(fnames_orig)]
    print(fnames_orig)
    orig_movie = cm.load(fnames_orig, fr=fr)

    # save file in NWB format with various additional info
    orig_movie.save(fnames[0], sess_desc='test', identifier='demo 1',
             exp_desc='demo movie', imaging_plane_description='single plane',
             emission_lambda=520.0, indicator='GCAMP6f',
             location='parietal cortex', starting_time=0.,
             experimenter='Sue Ann Koay', lab_name='Tank Lab',
             institution='Princeton U',
             experiment_description='Experiment Description',
             session_id='Session 1')

Setup some parameters

We set some parameters that are relevant to the file, and then parameters for motion correction, processing with CNMF and component quality evaluation. Note that the dataset Sue_2x_3000_40_-46.tif has been spatially downsampled by a factor of 2 and has a lower than usual spatial resolution (2um/pixel). As a result several parameters (gSig, strides, max_shifts, rf, stride_cnmf) have lower values (halved compared to a dataset with spatial resolution 1um/pixel).


In [8]:
# dataset dependent parameters
# these next to parameters have been defined in the previous cell
#fr = 15  # imaging rate in frames per second
#decay_time = 0.4  # length of a typical transient in seconds

# dataset dependent parameters
#fr = 30                             # imaging rate in frames per second
#decay_time = 0.4                    # length of a typical transient in seconds

# motion correction parameters
dxy = (2., 2.)  # spatial resolution in x and y in (um per pixel)
# note the lower than usual spatial resolution here
max_shift_um = (12., 12.)  # maximum shift in um
patch_motion_um = (100., 100.)  # patch size for non-rigid correction in um
pw_rigid = True             # flag for performing non-rigid motion correction

max_shifts = [int(a/b) for a, b in zip(max_shift_um, dxy)]
# start a new patch for pw-rigid motion correction every x pixels
strides = tuple([int(a/b) for a, b in zip(patch_motion_um, dxy)])
# overlap between pathes (size of patch in pixels: strides+overlaps)
overlaps = (24, 24)
# maximum deviation allowed for patch with respect to rigid shifts
max_deviation_rigid = 3

# parameters for source extraction and deconvolution
p = 1                       # order of the autoregressive system
gnb = 2                     # number of global background components
merge_thr = 0.85            # merging threshold, max correlation allowed
rf = 15                     # half-size of the patches in pixels. e.g., if rf=25, patches are 50x50
stride_cnmf = 6             # amount of overlap between the patches in pixels
K = 4                       # number of components per patch
gSig = [4, 4]               # expected half size of neurons in pixels
method_init = 'greedy_roi'  # initialization method (if analyzing dendritic data using 'sparse_nmf')
ssub = 2                    # spatial subsampling during initialization
tsub = 2                    # temporal subsampling during intialization

# parameters for component evaluation
min_SNR = 2.0               # signal to noise ratio for accepting a component
rval_thr = 0.85              # space correlation threshold for accepting a component
cnn_thr = 0.99              # threshold for CNN based classifier
cnn_lowest = 0.1 # neurons with cnn probability lower than this value are rejected

Create a parameters object

You can creating a parameters object by passing all the parameters as a single dictionary. Parameters not defined in the dictionary will assume their default values. The resulting params object is a collection of subdictionaries pertaining to the dataset to be analyzed (params.data), motion correction (params.motion), data pre-processing (params.preprocess), initialization (params.init), patch processing (params.patch), spatial and temporal component (params.spatial), (params.temporal), quality evaluation (params.quality) and online processing (params.online)


In [9]:
opts_dict = {'fnames': fnames,
            'fr': fr,
            'decay_time': decay_time,
            'dxy': dxy,
            'strides': strides,
            'overlaps': overlaps,
            'max_shifts': max_shifts,
            'max_deviation_rigid': max_deviation_rigid,
            'pw_rigid': pw_rigid,
            'border_nan': 'copy',
            'var_name_hdf5': 'acquisition/mov',    
            'p': p,
            'nb': gnb,
            'rf': rf,
            'K': K, 
            'stride': stride_cnmf,
            'method_init': method_init,
            'rolling_sum': True,
            'only_init': True,
            'ssub': ssub,
            'tsub': tsub,
            'gSig': gSig,
            'merge_thr': merge_thr, 
            'min_SNR': min_SNR,
            'rval_thr': rval_thr,
            'use_cnn': True,
            'min_cnn_thr': cnn_thr,
            'cnn_lowest': cnn_lowest}

opts = params.CNMFParams(params_dict=opts_dict)


      448274 [params.py:                 set():786] [8340] Changing key fnames in group data from None to ['C:\\Users\\Alex\\caiman_data\\example_movies/Sue_2x_3000_40_-46.nwb']
      448289 [params.py:                 set():786] [8340] Changing key fr in group data from 30 to 15
      448292 [params.py:                 set():786] [8340] Changing key dxy in group data from (1, 1) to (2.0, 2.0)
      448292 [params.py:                 set():786] [8340] Changing key var_name_hdf5 in group data from mov to acquisition/mov
      448296 [params.py:                 set():786] [8340] Changing key rf in group patch from None to 15
      448300 [params.py:                 set():786] [8340] Changing key stride in group patch from None to 6
      448300 [params.py:                 set():786] [8340] Changing key p in group preprocess from 2 to 1
      448304 [params.py:                 set():786] [8340] Changing key nb in group init from 1 to 2
      448304 [params.py:                 set():786] [8340] Changing key K in group init from 30 to 4
      448308 [params.py:                 set():786] [8340] Changing key gSig in group init from [5, 5] to [4, 4]
      448312 [params.py:                 set():786] [8340] Changing key nb in group spatial from 1 to 2
      448312 [params.py:                 set():786] [8340] Changing key p in group temporal from 2 to 1
      448316 [params.py:                 set():786] [8340] Changing key nb in group temporal from 1 to 2
      448324 [params.py:                 set():786] [8340] Changing key merge_thr in group merging from 0.8 to 0.85
      448328 [params.py:                 set():786] [8340] Changing key min_SNR in group quality from 2.5 to 2.0
      448332 [params.py:                 set():786] [8340] Changing key rval_thr in group quality from 0.8 to 0.85
      448336 [params.py:                 set():786] [8340] Changing key min_cnn_thr in group quality from 0.9 to 0.99
      448340 [params.py:                 set():786] [8340] Changing key min_SNR in group online from 2.5 to 2.0
      448340 [params.py:                 set():786] [8340] Changing key rval_thr in group online from 0.8 to 0.85
      448344 [params.py:                 set():786] [8340] Changing key strides in group motion from (96, 96) to (50, 50)
      448344 [params.py:                 set():786] [8340] Changing key overlaps in group motion from (32, 32) to (24, 24)
      448348 [params.py:                 set():786] [8340] Changing key max_shifts in group motion from (6, 6) to [6, 6]
      448348 [params.py:                 set():786] [8340] Changing key pw_rigid in group motion from False to True

Play the movie (optional)

Play the movie (optional). This will require loading the movie in memory which in general is not needed by the pipeline. Displaying the movie uses the OpenCV library. Press q to close the video panel.


In [10]:
display_movie = False
if display_movie:
    m_orig = cm.load_movie_chain(fnames, var_name_hdf5=opts.data['var_name_hdf5'])
    ds_ratio = 0.2
    m_orig.resize(1, 1, ds_ratio).play(
        q_max=99.5, fr=30, magnification=2)


100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:01<00:00,  1.82s/it]

Setup a cluster

To enable parallel processing a (local) cluster needs to be set up. This is done with a cell below. The variable backend determines the type of cluster used. The default value 'local' uses the multiprocessing package. The ipyparallel option is also available. More information on these choices can be found here. The resulting variable dview expresses the cluster option. If you use dview=dview in the downstream analysis then parallel processing will be used. If you use dview=None then no parallel processing will be employed.


In [11]:
#%% start a cluster for parallel processing (if a cluster already exists it will be closed and a new session will be opened)
if 'dview' in locals():
    cm.stop_server(dview=dview)
c, dview, n_processes = cm.cluster.setup_cluster(
    backend='local', n_processes=None, single_thread=False)

Motion Correction

First we create a motion correction object with the parameters specified. Note that the file is not loaded in memory


In [12]:
# first we create a motion correction object with the parameters specified
mc = MotionCorrect(fnames, dview=dview, var_name_hdf5=opts.data['var_name_hdf5'], **opts.get_group('motion'))
# note that the file is not loaded in memory

Now perform motion correction. From the movie above we see that the dateset exhibits non-uniform motion. We will perform piecewise rigid motion correction using the NoRMCorre algorithm. This has already been selected by setting pw_rigid=True when defining the parameters object.


In [13]:
%%capture
#%% Run piecewise-rigid motion correction using NoRMCorre
mc.motion_correct(save_movie=True)
m_els = cm.load(mc.fname_tot_els)
border_to_0 = 0 if mc.border_nan is 'copy' else mc.border_to_0 
    # maximum shift to be used for trimming against NaNs


      488842 [movies.py:      extract_shifts():253] [8340] Movie average is negative. Removing 1st percentile.
      488918 [movies.py:      extract_shifts():271] [8340] Movie average is negative. Removing 1st percentile.
      490480 [movies.py:      extract_shifts():253] [8340] Movie average is negative. Removing 1st percentile.
      490542 [movies.py:      extract_shifts():271] [8340] Movie average is negative. Removing 1st percentile.
      492252 [movies.py:      extract_shifts():253] [8340] Movie average is negative. Removing 1st percentile.
      492314 [movies.py:      extract_shifts():271] [8340] Movie average is negative. Removing 1st percentile.

Inspect the results by comparing the original movie. A more detailed presentation of the motion correction method can be found in the demo motion correction notebook.


In [14]:
#%% compare with original movie
display_movie = False
if display_movie:
    m_orig = cm.load_movie_chain(fnames, var_name_hdf5=opts.data['var_name_hdf5'])
    ds_ratio = 0.2
    cm.concatenate([m_orig.resize(1, 1, ds_ratio) - mc.min_mov*mc.nonneg_movie,
                    m_els.resize(1, 1, ds_ratio)], 
                   axis=2).play(fr=60, gain=15, magnification=2, offset=0)  # press q to exit

Memory mapping

The cell below memory maps the file in order 'C' and then loads the new memory mapped file. The saved files from motion correction are memory mapped files stored in 'F' order. Their paths are stored in mc.mmap_file.


In [15]:
#%% MEMORY MAPPING
# memory map the file in order 'C'
fname_new = cm.save_memmap(mc.mmap_file, base_name='memmap_', order='C',
                           border_to_0=border_to_0) # exclude borders

# now load the file
Yr, dims, T = cm.load_memmap(fname_new)
images = np.reshape(Yr.T, [T] + list(dims), order='F') 
    #load frames in python format (T x X x Y)

Now restart the cluster to clean up the memory


In [16]:
#%% restart cluster to clean up memory
cm.stop_server(dview=dview)
c, dview, n_processes = cm.cluster.setup_cluster(
    backend='local', n_processes=None, single_thread=False)

Run CNMF on patches in parallel

  • The FOV is split is different overlapping patches that are subsequently processed in parallel by the CNMF algorithm.
  • The results from all the patches are merged with special attention to idendtified components on the border.
  • The results are then refined by additional CNMF iterations.

In [17]:
%%capture
#%% RUN CNMF ON PATCHES

# First extract spatial and temporal components on patches and combine them
# for this step deconvolution is turned off (p=0)
opts.change_params({'p': 0,'n_processes': n_processes})
cnm = cnmf.CNMF(n_processes, params=opts, dview=dview)
cnm = cnm.fit(images)


      600168 [params.py:                 set():786] [8340] Changing key n_processes in group patch from 1 to 4
      600168 [params.py:                 set():786] [8340] Changing key p in group preprocess from 1 to 0
      600168 [params.py:                 set():786] [8340] Changing key p in group temporal from 1 to 0
      600168 [params.py:                 set():786] [8340] Changing key init_batch in group online from 200 to 3000
      600184 [params.py:                 set():786] [8340] Changing key medw in group spatial from None to (3, 3)
      600184 [params.py:                 set():786] [8340] Changing key se in group spatial from None to [[1 1 1]
 [1 1 1]
 [1 1 1]]
      600184 [params.py:                 set():786] [8340] Changing key ss in group spatial from None to [[1 1 1]
 [1 1 1]
 [1 1 1]]
      600184 [params.py:                 set():786] [8340] Changing key n_pixels_per_process in group preprocess from None to 7225
      600184 [params.py:                 set():786] [8340] Changing key n_pixels_per_process in group spatial from None to 7225
      600200 [params.py:                 set():786] [8340] Changing key n_pixels_per_process in group preprocess from 7225 to 225
      600200 [params.py:                 set():786] [8340] Changing key n_pixels_per_process in group spatial from 7225 to 225

Run the entire pipeline up to this point with one command

It is possible to run the combined steps of motion correction, memory mapping, and cnmf fitting in one step as shown below. The command is commented out since the analysis has already been performed. It is recommended that you familiriaze yourself with the various steps and the results of the various steps before using it.


In [18]:
# cnm1 = cnmf.CNMF(n_processes, params=opts, dview=dview)
# cnm1.fit_file(motion_correct=True)

Inspecting the results

Briefly inspect the results by plotting contours of identified components against correlation image. The results of the algorithm are stored in the object cnm.estimates. More information can be found in the definition of the estimates object and in the wiki.


In [19]:
#%% plot contours of found components
Cn = cm.local_correlations(images, swap_dim=False)
#Cn = cm.local_correlations(images.transpose(1,2,0))
Cn[np.isnan(Cn)] = 0
cnm.estimates.plot_contours_nb(img=Cn)

cnm.estimates.Cn = Cn
cnm.save(fname_new[:-4]+'hdf5')


C:\Users\Alex\Anaconda3\envs\caiman\lib\site-packages\h5py\_hl\dataset.py:313: H5pyDeprecationWarning: dataset.value has been deprecated. Use dataset[()] instead.
  "Use dataset[()] instead.", H5pyDeprecationWarning)

Re-run (seeded) CNMF on the full Field of View

You can re-run the CNMF algorithm seeded on just the selected components from the previous step. Be careful, because components rejected on the previous step will not be recovered here.


In [20]:
%%capture
#%% RE-RUN seeded CNMF on accepted patches to refine and perform deconvolution 
cnm.params.change_params({'p': p})
cnm2 = cnm.refit(images, dview=dview)


      643472 [params.py:                 set():786] [8340] Changing key p in group preprocess from 0 to 1
      643476 [params.py:                 set():786] [8340] Changing key p in group temporal from 0 to 1
      663094 [params.py:                 set():786] [8340] Changing key p in group temporal from 1 to 0
      679349 [params.py:                 set():786] [8340] Changing key p in group temporal from 0 to 1

Component Evaluation

The processing in patches creates several spurious components. These are filtered out by evaluating each component using three different criteria:

  • the shape of each component must be correlated with the data at the corresponding location within the FOV
  • a minimum peak SNR is required over the length of a transient
  • each shape passes a CNN based classifier


In [21]:
#%% COMPONENT EVALUATION
# the components are evaluated in three ways:
#   a) the shape of each component must be correlated with the data
#   b) a minimum peak SNR is required over the length of a transient
#   c) each shape passes a CNN based classifier

cnm2.estimates.evaluate_components(images, cnm2.params, dview=dview)

cnm2.estimates.Cn = Cn
cnm2.save(fname_new[:-4] + 'hdf5')


WARNING:tensorflow:From c:\code\nwb\caiman\caiman\utils\utils.py:518: calling import_graph_def (from tensorflow.python.framework.importer) with op_dict is deprecated and will be removed in a future version.
Instructions for updating:
Please file an issue at https://github.com/tensorflow/tensorflow/issues if you depend on this feature.
      683110 [tf_logging.py:             warning():125] [8340] From c:\code\nwb\caiman\caiman\utils\utils.py:518: calling import_graph_def (from tensorflow.python.framework.importer) with op_dict is deprecated and will be removed in a future version.
Instructions for updating:
Please file an issue at https://github.com/tensorflow/tensorflow/issues if you depend on this feature.

Plot contours of selected and rejected components


In [20]:
#%% PLOT COMPONENTS
#cnm2.estimates.plot_contours_nb(img=Cn, idx=cnm2.estimates.idx_components)


Out[20]:
<caiman.source_extraction.cnmf.estimates.Estimates at 0x235cbc657b8>

View traces of accepted and rejected components. Note that if you get data rate error you can start Jupyter notebooks using: 'jupyter notebook --NotebookApp.iopub_data_rate_limit=1.0e10'


In [21]:
# accepted components
#cnm2.estimates.view_components(images, img=Cn,idx=cnm2.estimates.idx_components)

#cnm2.estimates.nb_view_components(img=Cn, idx=cnm2.estimates.idx_components)


Component:0
Out[21]:
<caiman.source_extraction.cnmf.estimates.Estimates at 0x235cbc657b8>

In [22]:
# rejected components
#if len(cnm2.estimates.idx_components_bad) > 0:
#    cnm2.estimates.view_components(images, img=Cn,idx=cnm2.estimates.idx_components_bad)
#    cnm2.estimates.nb_view_components(img=Cn, idx=cnm2.estimates.idx_components_bad)
#else:
#    print("No components were rejected.")


Component:0

Extract DF/F values


In [22]:
#%% Extract DF/F values
cnm2.estimates.detrend_df_f(quantileMin=8, frames_window=250)


Out[22]:
<caiman.source_extraction.cnmf.estimates.Estimates at 0x21857042080>

Select only high quality components


In [23]:
cnm2.estimates.select_components(use_object=True)


Out[23]:
<caiman.source_extraction.cnmf.estimates.Estimates at 0x21857042080>

Display final results


In [25]:
#cnm2.estimates.view_components(img=Cn)

#cnm2.estimates.nb_view_components(img=Cn, denoised_color='red')
#print('you may need to change the data rate to generate this one: use jupyter notebook --NotebookApp.iopub_data_rate_limit=1.0e10 before opening jupyter notebook')


you may need to change the data rate to generate this one: use jupyter notebook --NotebookApp.iopub_data_rate_limit=1.0e10 before opening jupyter notebook

Closing, saving, and creating denoised version

You can save an hdf5 file with all the fields of the cnmf object


In [25]:
save_results = True
if save_results:
    #cnm2.save('analysis_results.hdf5')
    cnm2.estimates.save_NWB(fnames[0], imaging_rate=fr)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-25-e91ce216abfb> in <module>
      2 if save_results:
      3     #cnm2.save('analysis_results.hdf5')
----> 4     cnm2.estimates.save_NWB(fnames[0], imaging_rate=fr)

c:\code\nwb\caiman\caiman\source_extraction\cnmf\estimates.py in save_NWB(self, filename, imaging_plane_name, imaging_series_name, sess_desc, exp_desc, imaging_rate, location, orig_file_format)
   1220                 nwbfile = io.read()
   1221                 # Add processing results
-> 1222                 mod = nwbfile.create_processing_module('Estimates', 'contains caiman estimates for the main imagin plane')
   1223                 img_seg = ImageSegmentation()
   1224                 mod.add_data_interface(img_seg)

~\Anaconda3\envs\caiman\lib\site-packages\hdmf\utils.py in func_call(*args, **kwargs)
    386                         raise_from(ExceptionType(msg), None)
    387 
--> 388                 return func(self, **parsed['args'])
    389         else:
    390             def func_call(*args, **kwargs):

~\Anaconda3\envs\caiman\lib\site-packages\pynwb\core.py in _func(self, **kwargs)
    756             cargs, ckwargs = fmt_docval_args(container_type.__init__, kwargs)
    757             ret = container_type(*cargs, **ckwargs)
--> 758             getattr(self, add_name)(ret)
    759             return ret
    760         return _func

~\Anaconda3\envs\caiman\lib\site-packages\hdmf\utils.py in func_call(*args, **kwargs)
    386                         raise_from(ExceptionType(msg), None)
    387 
--> 388                 return func(self, **parsed['args'])
    389         else:
    390             def func_call(*args, **kwargs):

~\Anaconda3\envs\caiman\lib\site-packages\pynwb\core.py in _func(self, **kwargs)
    741                 if tmp.name in d:
    742                     msg = "'%s' already exists in '%s'" % (tmp.name, self.name)
--> 743                     raise ValueError(msg)
    744                 d[tmp.name] = tmp
    745             return container

ValueError: 'Estimates' already exists in 'root'

Stop cluster and clean up LOG files


In [27]:
#%% STOP CLUSTER and clean up log files
cm.stop_server(dview=dview)
log_files = glob.glob('*_LOG_*')
for log_file in log_files:
    os.remove(log_file)

View movie with the results

We can inspect the denoised results by reconstructing the movie and playing alongside the original data and the resulting (amplified) residual movie


In [28]:
cnm2.estimates.play_movie(images, q_max=99.9, gain_res=2,
                                  magnification=2,
                                  bpx=border_to_0,
                                  include_bck=False)


---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-28-7033397341a5> in <module>
      2                                   magnification=2,
      3                                   bpx=border_to_0,
----> 4                                   include_bck=False)

c:\code\nwb\caiman\caiman\source_extraction\cnmf\estimates.py in play_movie(self, imgs, q_max, q_min, gain_res, magnification, include_bck, frame_range, bpx, thr, save_movie, movie_name)
    564         else:
    565             mov.play(q_min=q_min, q_max=q_max, magnification=magnification,
--> 566                      save_movie=save_movie, movie_name=movie_name)
    567 
    568         return self

c:\code\nwb\caiman\caiman\base\movies.py in play(self, gain, fr, magnification, offset, interpolation, backend, do_loop, bord_px, q_max, q_min, plot_text, save_movie, movie_name)
   1090                     if save_movie:
   1091                         out.write(frame.astype('uint8'))
-> 1092                     if cv2.waitKey(int(1. / fr * 1000)) & 0xFF == ord('q'):
   1093                         looping = False
   1094                         terminated = True

KeyboardInterrupt: 

The denoised movie can also be explicitly constructed using:


In [ ]:
#%% reconstruct denoised movie
denoised = cm.movie(cnm2.estimates.A.dot(cnm2.estimates.C) + \
                    cnm2.estimates.b.dot(cnm2.estimates.f)).reshape(dims + (-1,), order='F').transpose([2, 0, 1])