From 374656f31c00402664cafe6c97ed77bc7a08d941 Mon Sep 17 00:00:00 2001 From: epnev Date: Fri, 12 Jul 2019 12:01:08 -0400 Subject: [PATCH 1/5] storing caiman version and HEAD hash inside params.data --- caiman/source_extraction/cnmf/params.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/caiman/source_extraction/cnmf/params.py b/caiman/source_extraction/cnmf/params.py index 5d5acd06c..be6c8471b 100644 --- a/caiman/source_extraction/cnmf/params.py +++ b/caiman/source_extraction/cnmf/params.py @@ -1,6 +1,6 @@ import logging import os - +import subprocess import numpy as np import scipy from scipy.ndimage.morphology import generate_binary_structure, iterate_structure @@ -532,7 +532,9 @@ def __init__(self, fnames=None, dims=None, dxy=(1, 1), 'fr': fr, 'decay_time': decay_time, 'dxy': dxy, - 'var_name_hdf5': var_name_hdf5 + 'var_name_hdf5': var_name_hdf5, + 'caiman_version': 1.5, + 'last_commit': None, } self.patch = { @@ -729,6 +731,11 @@ def __init__(self, fnames=None, dims=None, dxy=(1, 1), } self.change_params(params_dict) + try: + lc = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").split("\n")[0] + self.data['last_commit'] = lc + except(): + pass if self.data['dims'] is None and self.data['fnames'] is not None: self.data['dims'] = get_file_size(self.data['fnames'], var_name_hdf5=self.data['var_name_hdf5'])[0] if self.data['fnames'] is not None: From f00f63221a0160210d80ef0198c8acb249847166 Mon Sep 17 00:00:00 2001 From: Libby Li Date: Fri, 12 Jul 2019 15:09:51 -0400 Subject: [PATCH 2/5] gui updated --- caiman/gui/gui_pyqtgraph_layout.py | 297 ++++++++++++++++++++++++++--- 1 file changed, 268 insertions(+), 29 deletions(-) diff --git a/caiman/gui/gui_pyqtgraph_layout.py b/caiman/gui/gui_pyqtgraph_layout.py index 0ec1d5518..6ce0595f0 100644 --- a/caiman/gui/gui_pyqtgraph_layout.py +++ b/caiman/gui/gui_pyqtgraph_layout.py @@ -28,10 +28,14 @@ except NameError: print('Not launched under iPython') -def make_color_img(img, gain=255, out_type=np.uint8): - min = img.min() - max = img.max() - img = (img-min)/(max-min)*gain +def make_color_img(img, gain=255, min_max=None,out_type=np.uint8): + if min_max is None: + min_ = img.min() + max_ = img.max() + else: + min_, max_ = min_max + + img = (img-min_)/(max_-min_)*gain img = img.astype(out_type) img = np.dstack([img]*3) return img @@ -46,6 +50,8 @@ def make_color_img(img, gain=255, out_type=np.uint8): # movie # mov = cm.load('/Users/agiovann/caiman_data/example_movies/memmap__d1_60_d2_80_d3_1_order_C_frames_2000_.mmap') mov = cm.load(cnm_obj.mmap_file) +min_mov = np.min(mov) +max_mov = np.max(mov) # load summary image # Cn = cm.load('/Users/agiovann/caiman_data/example_movies/memmap__d1_60_d2_80_d3_1_order_C_frames_2000__Cn.tif') @@ -53,6 +59,19 @@ def make_color_img(img, gain=255, out_type=np.uint8): estimates = cnm_obj.estimates +min_mov_denoise = np.min(estimates.A.dot(estimates.C)) +max_mov_denoise = np.max(estimates.A.dot(estimates.C)) +min_background = [] +max_background = [] +background_num = -1 +neuron_selected = False +nr_index = 0 + +for i in range(0,estimates.f.shape[0]): + min_background.append(np.min(estimates.b[:,i].reshape(4800,1).dot(estimates.f[i,:].reshape(1,2000)))) + max_background.append(np.max(estimates.b[:,i].reshape(4800,1).dot(estimates.f[i,:].reshape(1,2000)))) + + if not hasattr(estimates, 'accepted_list'): # if estimates.discarded_components.A.shape[-1] > 0: # estimates.restore_discarded_components() @@ -69,9 +88,27 @@ def make_color_img(img, gain=255, out_type=np.uint8): estimates.img_components = estimates.img_components.astype(np.uint8) +def draw_contours_overall(md): + if md is "reset": + draw_contours() + elif md is "neurons": + if neuron_selected is True: + #if a specific neuron has been selected, only one contour should be changed while thrshcomp_line is changing + if nr_index is 0: + #if user does not start to move through the frames + draw_contours_update(estimates.background_image, img) + draw_contours_update(comp2_scaled, img2) + else: + draw_contours_update(raw_mov_scaled, img) + draw_contours_update(frame_denoise_scaled, img2) + else: + #if no specific neuron has been selected, all the contours are changing + draw_contours() + else: + #md is "background": + return + - -#%% def draw_contours(): global thrshcomp_line, estimates, cnm_obj, img bkgr_contours = estimates.background_image.copy() @@ -79,7 +116,6 @@ def draw_contours(): if len(estimates.idx_components) > 0: contours = [cv2.findContours(cv2.threshold(img, np.int(thrshcomp_line.value()), 255, 0)[1], cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0] for img in estimates.img_components[estimates.idx_components]] - SNRs = np.array(estimates.r_values) iidd = np.array(estimates.idx_components) @@ -105,6 +141,46 @@ def draw_contours(): img.setImage(bkgr_contours, autoLevels=False) # pg.setConfigOptions(imageAxisOrder='row-major') + + +def draw_contours_update(cf, im): + global thrshcomp_line, estimates, cnm_obj + curFrame = cf.copy() + + if len(estimates.idx_components) > 0: + contours = [cv2.findContours(cv2.threshold(img, np.int(thrshcomp_line.value()), 255, 0)[1], cv2.RETR_TREE, + cv2.CHAIN_APPROX_SIMPLE)[0] for img in estimates.img_components[estimates.idx_components]] + SNRs = np.array(estimates.r_values) + iidd = np.array(estimates.idx_components) + + idx1 = np.where(SNRs[iidd]<0.1)[0] + idx2 = np.where((SNRs[iidd]>=0.1) & + (SNRs[iidd]<0.25))[0] + idx3 = np.where((SNRs[iidd]>=0.25) & + (SNRs[iidd]<0.5))[0] + idx4 = np.where((SNRs[iidd]>=0.5) & + (SNRs[iidd]<0.75))[0] + idx5 = np.where((SNRs[iidd]>=0.75) & + (SNRs[iidd]<0.9))[0] + idx6 = np.where(SNRs[iidd]>=0.9)[0] + + if min_dist_comp in idx1: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (255, 0, 0), 1) + if min_dist_comp in idx2: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (0, 255, 0), 1) + if min_dist_comp in idx3: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (0, 0, 255), 1) + if min_dist_comp in idx4: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (255, 255, 0), 1) + if min_dist_comp in idx5: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (255, 0, 255), 1) + if min_dist_comp in idx6: + cv2.drawContours(curFrame, contours[min_dist_comp], -1, (0, 255, 255), 1) + + im.setImage(curFrame, autoLevels=False) + + + #%% @@ -161,6 +237,10 @@ def draw_contours(): layout.addWidget(p2, 1, 1) # plot goes on right side, spanning 2 rows +#enable only horizontal zoom for the traces component +p2.setMouseEnabled(x=True, y=False) + + draw_contours() hist.setLevels(estimates.background_image.min(), estimates.background_image.max()) @@ -181,11 +261,15 @@ def draw_contours(): # zoom to fit imageo p1.autoRange() -thrshcomp_line.sigDragged.connect(draw_contours) + +mode = "reset" +p2.setTitle("mode: %s" % (mode)) + +thrshcomp_line.sigDragged.connect(lambda: draw_contours_overall(mode)) + def imageHoverEvent(event): - """Show the position, pixel, and value under the mouse cursor. - """ + #Show the position, pixel, and value under the mouse cursor. global x,y,i,j,val pos = event.pos() i, j = pos.y(), pos.x() @@ -195,32 +279,61 @@ def imageHoverEvent(event): ppos = img.mapToParent(pos) x, y = ppos.x(), ppos.y() - # Monkey-patch the image to use our custom hover function. # This is generally discouraged (you should subclass ImageItem instead), # but it works for a very simple use like this. img.hoverEvent = imageHoverEvent + + def mouseClickEvent(event): - global x, y, i, j, val, img2 - distances = np.sum(((x,y)-estimates.cms[estimates.idx_components])**2, axis=1)**0.5 - min_dist_comp = np.argmin(distances) - estimates.components_to_plot = estimates.idx_components[min_dist_comp] - p2.plot(estimates.C[estimates.components_to_plot] + estimates.YrA[estimates.components_to_plot], clear=True) - img2.setImage(estimates.A[:,estimates.components_to_plot].toarray().reshape(estimates.dims, order='F'), autoLevels=True) - p3.setTitle("pos: (%0.1f, %0.1f) component: %d value: %g dist:%f" % (x, y, estimates.components_to_plot, - val, distances[min_dist_comp])) - p1.setTitle("pos: (%0.1f, %0.1f) component: %d value: %g dist:%f" % (x, y, estimates.components_to_plot, - val, distances[min_dist_comp])) + global mode + global x,y,i,j,val + + pos = img.mapFromScene(event.pos()) + x = int(pos.x()) + y = int(pos.y()) + + if x < 0 or x > mov.shape[1] or y < 0 or y > mov.shape[2]: + # if the user click outside of the movie, do nothing and jump out of the function + return + + i, j = pos.y(), pos.x() + i = int(np.clip(i, 0, estimates.background_image.shape[0] - 1)) + j = int(np.clip(j, 0, estimates.background_image.shape[1] - 1)) + val = estimates.background_image[i, j, 0] + + if mode is "neurons": + show_neurons_clicked() + p1.mousePressEvent = mouseClickEvent + +#A general rule in Qt is that if you override one mouse event handler, you must override all of them. +def release(event): + pass + +p1.mouseReleaseEvent = release + +def move(event): + pass + +p1.mouseMoveEvent = move + + + + ## PARAMS params = [{'name': 'min_cnn_thr', 'type': 'float', 'value': 0.99, 'limits': (0, 1),'step':0.01}, {'name': 'cnn_lowest', 'type': 'float', 'value': 0.1, 'limits': (0, 1),'step':0.01}, {'name': 'rval_thr', 'type': 'float', 'value': 0.85, 'limits': (-1, 1),'step':0.01}, {'name': 'rval_lowest', 'type': 'float', 'value': -1, 'limits': (-1, 1),'step':0.01}, {'name': 'min_SNR', 'type': 'float', 'value': 2, 'limits': (0, 20),'step':0.1}, - {'name': 'SNR_lowest', 'type': 'float', 'value': 0, 'limits': (0, 20),'step':0.1}] + {'name': 'SNR_lowest', 'type': 'float', 'value': 0, 'limits': (0, 20),'step':0.1}, + {'name': 'RESET', 'type': 'action'}, + {'name': 'SHOW BACKGROUND', 'type': 'action'}, + {'name': 'SHOW NEURONS', 'type': 'action'} + ] ## Create tree of Parameter objects pars = Parameter.create(name='params', type='group', children=params) @@ -235,19 +348,146 @@ def mouseClickEvent(event): {'name': 'REMOVE SINGLE', 'type': 'action'}, {'name': 'SAVE OBJECT', 'type': 'action'} ] - + pars_action = Parameter.create(name='params_action', type='group', children=params_action) t_action.setParameters(pars_action, showTop=False) t_action.setWindowTitle('Parameter Action') + +def reset_button(): + global mode + mode = "reset" + p2.setTitle("mode: %s" % (mode)) + #clear the upper right image + zeros = np.asarray([ [0] * 80 for _ in range(60)]) + img2.setImage(make_color_img(zeros), autoLevels=False) + draw_contours() + + +pars.param('RESET').sigActivated.connect(reset_button) + + +def show_background_button(): + global bg_vline, min_background, max_background, background_num + global mode, background_first_frame_scaled + #clear thhe upper right image + zeros = np.asarray([ [0] * 80 for _ in range(60)]) + img2.setImage(make_color_img(zeros), autoLevels=False) + + background_num = (background_num + 1) % estimates.f.shape[0] + mode = "background" + p2.setTitle("mode: %s %d" % (mode,background_num)) + + # display the first frame of the background + background_first_frame = estimates.b[:,background_num].reshape(estimates.dims, order='F') + min_background_first_frame = np.min(background_first_frame) + max_background_first_frame = np.max(background_first_frame) + background_first_frame_scaled = make_color_img(background_first_frame, min_max=(min_background_first_frame, max_background_first_frame)) + img.setImage(background_first_frame_scaled,autoLevels=False) + + # draw the trace and the infinite line + trace_background = estimates.f[background_num] + p2.plot(trace_background, clear=True) + bg_vline = pg.InfiniteLine(angle = 90, movable = True) + p2.addItem(bg_vline, ignoreBounds=True) + bg_vline.setValue(0) + bg_vline.sigPositionChanged.connect(show_background_update) + + +def show_background_update(): + global bg_index, min_background, max_background, background_scaled + bg_index = int(bg_vline.value()) + if bg_index > 0 and bg_index < 2001: + # upper left component scrolls through the frames of the background + background = estimates.b[:,background_num].dot(estimates.f[background_num,bg_index]).reshape(estimates.dims, order='F') + background_scaled = make_color_img(background, min_max=(min_background[background_num], max_background[background_num])) + img.setImage(background_scaled,autoLevels=False) + +pars.param('SHOW BACKGROUND').sigActivated.connect(show_background_button) + + + + + +def show_neurons_button(): + global mode, neuron_selected + mode = "neurons" + neuron_selected = False + p2.setTitle("mode: %s" % (mode)) + #clear the upper right image + zeros = np.asarray([ [0] * 80 for _ in range(60)]) + img2.setImage(make_color_img(zeros), autoLevels=False) + + + +def show_neurons_clicked(): + global nr_vline, nr_index + global x,y,i,j,val,min_dist_comp,contour_single, neuron_selected, comp2_scaled + neuron_selected = True + distances = np.sum(((x,y)-estimates.cms[estimates.idx_components])**2, axis=1)**0.5 + min_dist_comp = np.argmin(distances) + contour_all =[cv2.threshold(img, np.int(thrshcomp_line.value()), 255, 0)[1] for img in estimates.img_components[estimates.idx_components]] + contour_single = contour_all[min_dist_comp] + + # draw the traces (lower left component) + estimates.components_to_plot = estimates.idx_components[min_dist_comp] + p2.plot(estimates.C[estimates.components_to_plot] + estimates.YrA[estimates.components_to_plot], clear=True) + + # plot img (upper left component) + img.setImage(estimates.background_image, autoLevels=False) + draw_contours_update(estimates.background_image, img) + # plot img2 (upper right component) + comp2 = np.multiply(Cn, contour_single>0) + comp2_scaled = make_color_img(comp2, min_max=(np.min(comp2), np.max(comp2))) + img2.setImage(comp2_scaled,autoLevels=False) + draw_contours_update(comp2_scaled, img2) + # set title for the upper two components + p3.setTitle("pos: (%0.1f, %0.1f) component: %d value: %g dist:%f" % (x, y, estimates.components_to_plot, + val, distances[min_dist_comp])) + p1.setTitle("pos: (%0.1f, %0.1f) component: %d value: %g dist:%f" % (x, y, estimates.components_to_plot, + val, distances[min_dist_comp])) + # draw the infinite line + nr_vline = pg.InfiniteLine(angle = 90, movable = True) + p2.addItem(nr_vline, ignoreBounds=True) + nr_vline.setValue(0) + nr_vline.sigPositionChanged.connect(show_neurons_update) + nr_index = 0 + + +def show_neurons_update(): + global nr_index, frame_denoise_scaled, estimates, raw_mov_scaled + global min_mov, max_mov, min_mov_denoise, max_mov_denoise + if neuron_selected is False: + return + nr_index = int(nr_vline.value()) + if nr_index > 0 and nr_index < mov[:,0,0].shape[0]: + # upper left compoenent scrolls through the raw movie + raw_mov = mov[nr_index,:,:] + raw_mov_scaled = make_color_img(raw_mov, min_max=(min_mov,max_mov)) + img.setImage(raw_mov_scaled, autoLevels=False) + draw_contours_update(raw_mov_scaled, img) + # upper right component scrolls through the denoised movie + frame_denoise = estimates.A[:,estimates.idx_components].dot(estimates.C[estimates.idx_components,nr_index]).reshape(estimates.dims, order='F') + frame_denoise_scaled = make_color_img(frame_denoise, min_max=(min_mov_denoise,max_mov_denoise)) + img2.setImage(frame_denoise_scaled,autoLevels=False) + draw_contours_update(frame_denoise_scaled, img2) + + +pars.param('SHOW NEURONS').sigActivated.connect(show_neurons_button) + + + + + + + def add_group(): estimates.accepted_list = np.union1d(estimates.accepted_list,estimates.idx_components) estimates.rejected_list = np.setdiff1d(estimates.rejected_list,estimates.idx_components) change(None, None) - pars_action.param('ADD GROUP').sigActivated.connect(add_group) def remove_group(): @@ -301,8 +541,10 @@ def change(param, changes): estimates.filter_components(mov, params_obj, dview=None, select_mode=pars_action.param('View components').value()) - - draw_contours() + if mode is "background": + return + else: + draw_contours() pars.sigTreeStateChanged.connect(change) @@ -312,9 +554,6 @@ def change(param, changes): ## END PARAMS - -draw_contours() - ## Display the widget as a new window w.show() From 45a700d6c2cf8f88997908027c3aed4b89f42367 Mon Sep 17 00:00:00 2001 From: epnev Date: Fri, 12 Jul 2019 17:12:52 -0400 Subject: [PATCH 3/5] specify exception --- caiman/source_extraction/cnmf/params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caiman/source_extraction/cnmf/params.py b/caiman/source_extraction/cnmf/params.py index be6c8471b..d6db32d90 100644 --- a/caiman/source_extraction/cnmf/params.py +++ b/caiman/source_extraction/cnmf/params.py @@ -734,7 +734,7 @@ def __init__(self, fnames=None, dims=None, dxy=(1, 1), try: lc = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").split("\n")[0] self.data['last_commit'] = lc - except(): + except subprocess.CalledProcessError: pass if self.data['dims'] is None and self.data['fnames'] is not None: self.data['dims'] = get_file_size(self.data['fnames'], var_name_hdf5=self.data['var_name_hdf5'])[0] From 16fe240aeb0794124f8fe9a61b4352389d345345 Mon Sep 17 00:00:00 2001 From: Libby Li Date: Sat, 13 Jul 2019 12:20:13 -0400 Subject: [PATCH 4/5] problem fixed --- caiman/gui/gui_pyqtgraph_layout.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/caiman/gui/gui_pyqtgraph_layout.py b/caiman/gui/gui_pyqtgraph_layout.py index 6ce0595f0..25f59c446 100644 --- a/caiman/gui/gui_pyqtgraph_layout.py +++ b/caiman/gui/gui_pyqtgraph_layout.py @@ -61,15 +61,12 @@ def make_color_img(img, gain=255, min_max=None,out_type=np.uint8): estimates = cnm_obj.estimates min_mov_denoise = np.min(estimates.A.dot(estimates.C)) max_mov_denoise = np.max(estimates.A.dot(estimates.C)) -min_background = [] -max_background = [] background_num = -1 neuron_selected = False nr_index = 0 -for i in range(0,estimates.f.shape[0]): - min_background.append(np.min(estimates.b[:,i].reshape(4800,1).dot(estimates.f[i,:].reshape(1,2000)))) - max_background.append(np.max(estimates.b[:,i].reshape(4800,1).dot(estimates.f[i,:].reshape(1,2000)))) +min_background = np.min(estimates.b, axis=0)*np.min(estimates.f, axis=1) +max_background = np.max(estimates.b, axis=0)*np.max(estimates.f, axis=1) if not hasattr(estimates, 'accepted_list'): @@ -399,7 +396,7 @@ def show_background_button(): def show_background_update(): global bg_index, min_background, max_background, background_scaled bg_index = int(bg_vline.value()) - if bg_index > 0 and bg_index < 2001: + if bg_index > -1 and bg_index < estimates.f.shape[-1]: # upper left component scrolls through the frames of the background background = estimates.b[:,background_num].dot(estimates.f[background_num,bg_index]).reshape(estimates.dims, order='F') background_scaled = make_color_img(background, min_max=(min_background[background_num], max_background[background_num])) From a4c96720b776c6b1e6db8c0a1a644a358a0627a4 Mon Sep 17 00:00:00 2001 From: epnev Date: Mon, 15 Jul 2019 15:41:20 -0400 Subject: [PATCH 5/5] fixing a bug during refit --- caiman/source_extraction/cnmf/cnmf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/caiman/source_extraction/cnmf/cnmf.py b/caiman/source_extraction/cnmf/cnmf.py index e7b9dd95a..7bc3a2270 100644 --- a/caiman/source_extraction/cnmf/cnmf.py +++ b/caiman/source_extraction/cnmf/cnmf.py @@ -504,7 +504,7 @@ def fit(self, images, indices=[slice(None), slice(None)]): self.params.set('temporal', {'p': 0}) else: self.params.set('temporal', {'p': self.params.get('preprocess', 'p')}) - logging.info('deconvolution ...') + logging.info('deconvolution ...') self.update_temporal(Yr) @@ -676,6 +676,7 @@ def compute_residuals(self, Yr): AA = Ab.T.dot(Ab) * nA2_inv_mat self.estimates.YrA = (YA - (AA.T.dot(Cf)).T)[:, :self.estimates.A.shape[-1]].T + self.estimates.R = self.estimates.YrA return self @@ -855,6 +856,7 @@ def update_temporal(self, Y, use_init=True, **kwargs): self.estimates.g, self.estimates.YrA, self.estimates.lam = update_temporal_components( Y, self.estimates.A, self.estimates.b, self.estimates.C, self.estimates.f, dview=self.dview, **self.params.get_group('temporal')) + self.estimates.R = self.estimates.YrA return self def update_spatial(self, Y, use_init=True, **kwargs):