Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev to main for release 1.11.2 #1379

Merged
merged 33 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
398c64f
VERSION to 1.11.2 after release
pgunn May 31, 2024
e401f75
Add is3D setting to summary_images functions
ethanbb Jun 11, 2024
af09876
Make movie.removeBL work with 3D movies
ethanbb Jun 13, 2024
d139588
Revert "Add is3D setting to summary_images functions"
ethanbb Jun 14, 2024
0857d7a
Add tests for to2D and to3D
ethanbb Jun 14, 2024
7e372b3
Make cpp exclusion more specific
ethanbb Jun 16, 2024
860e8d8
added bokeh implementation of view_quilt
salehtahini Jun 17, 2024
827c541
updated documentation
salehtahini Jun 18, 2024
54b2fb7
Fix naming of Slider()
pgunn Jul 1, 2024
90904b3
Merge pull request #1365 from salehtahini/bokeh_view_quilt
pgunn Jul 1, 2024
9c4646e
pin peakutils >=1.3.5 (see #1369)
pgunn Jul 1, 2024
522d456
Fix CoM calculation when swap_dim is true
ethanbb Jun 28, 2024
1f88e49
Fix plot_contours to use CoMs from updated get_contours
ethanbb Jul 2, 2024
8f359bb
de-emoji the readme
pgunn Jul 2, 2024
b38f0fa
Add rewritten com function with C/F order and add test
ethanbb Jul 2, 2024
f6d30aa
caiman_gui: if the user doesn't provide a filename, bail out with a g…
pgunn Jul 2, 2024
467c591
Restore individual dimension arguments for com
ethanbb Jul 5, 2024
d883598
Add slice_dim option for get_contours and 3D test for swap_dim
ethanbb Jul 5, 2024
830967c
Minor changes to make contours more consistent/predictable
ethanbb Jul 5, 2024
04d1ca2
Use None instead of 'auto'
ethanbb Jul 8, 2024
d219783
Merge pull request #1370 from proektlab/contours-com-fix
pgunn Jul 9, 2024
d0732cb
demo_online_cnmfE: Fix a text cell that was mistakenly marked as code
pgunn Jul 11, 2024
2a9d2c8
Finish the fix started in #1326 (thanks to @EmeEmu and @marberi for b…
pgunn Jul 12, 2024
2d0108e
mmapping: fix a path goof where a double-correction caused a problem,…
pgunn Jul 18, 2024
bf7e8de
Merge pull request #1375 from flatironinstitute/dev-fix_path_goof
pgunn Jul 18, 2024
0dac913
Merge pull request #1363 from proektlab/summary-images-3d
pgunn Jul 24, 2024
c82e768
Move (almost) all caiman logging under a named logger called "caiman"
pgunn Jul 24, 2024
9bdbacb
caiman-logger: Further cleanups, fix problems CI revealed
pgunn Jul 24, 2024
b3fafb5
caiman logging change: had to partly back off on import cleanup, fix …
pgunn Jul 24, 2024
d3a89fc
Initial go at updating notebooks to use the logger we're calling caiman
pgunn Jul 24, 2024
268e807
logging change diffset: Fixing a missed line in the mass cut'n'paste …
pgunn Jul 25, 2024
04a04ae
Logging cleanup diffset: Get rid of almost all calls to global logger
pgunn Jul 25, 2024
e8abf3b
Merge pull request #1378 from flatironinstitute/dev-logging_cleanup
pgunn Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ __pycache__/

# C extensions
*.so
*.c
caiman/source_extraction/cnmf/oasis.cpp

#movie tif
*.tif
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Caiman Central
--------------
- [Caiman Central](https://github.com/flatironinstitute/caiman_central) is the hub for sharing information about CaImAn. Information on quarterly community meetings, workshops, other events, and any other communications between the developers and the user community can be found there.

# Quick start :rocket:
# Quick start
Follow these three steps to get started quickly, from installation to working through a demo notebook. If you do not already have conda installed, [you can find it here](https://docs.conda.io/en/latest/miniconda.html). There is a video walkthrough of the following steps [here](https://youtu.be/b63zAmKihIY?si=m7WleTwdU0rJup_2).

### Step 1: Install caiman
Expand Down Expand Up @@ -41,7 +41,7 @@ Jupyter will open. Navigate to demos/notebooks/ and click on `demo_pipeline.ipyn
## For installation help
Caiman should install easily on Linux, Mac, and Windows. If you run into problems, we have a dedicated [installation page](./docs/source/Installation.rst): the details there should help you troubleshoot. If you don't find what you need there, *please* [create an issue](https://github.com/flatironinstitute/CaImAn/issues) at GitHub, and we will help you get it sorted out.

# Demo notebooks :page_with_curl:
# Demo notebooks
Caiman provides demo notebooks to showcase each of our main features, from motion correction to online CNMF. We recommend starting with the CNMF notebook (`demo_pipeline.ipynb`), which contains more explanation and details than the other notebooks: it covers many concepts that will be used without explanation in the other notebooks. The CNMFE notebook (`demo_pipeline_cnmfE.ipynb`), is also more detailed. Once you've gotten things set up and worked through those "anchor" notebooks, the best way to get started is to work through the demo notebook that most closely matches your use case; you should be able to adapt it for your particular needs.

The main use cases and notebooks are listed in the following table:
Expand Down Expand Up @@ -72,7 +72,7 @@ Caiman also provides commandline demos, similar to the notebooks, demonstrating
- If you have found a bug, we recommend searching the [issues at github](https://github.com/flatironinstitute/CaImAn/issues) and opening a new issue if you can't find the solution there.
- If there is a feature you would like to see implemented, feel free to come chat at the above forums or open an issue at Github.

# How to contribute :hammer:
# How to contribute
Caiman is an open-source project and improves because of contributions from users all over the world. If there is something about Caiman that you would like to work on, then please reach out. We are always looking for more contributors, so please come read the [contributors page](./CONTRIBUTING.md) for more details about how.

# Videos
Expand All @@ -89,7 +89,7 @@ The following talks are more in depth:
* https://www.youtube.com/watch?v=z6TlH28MLRo


# Related repositories :pushpin:
# Related repositories
There are many repositories that use Caiman, or help make using Caiman easier.

* [use\_cases repo](https://github.com/flatironinstitute/caiman_use_cases): additional code (unmaintained) demonstrating how to reproduce results in some Caiman-related papers, and how to use/extend Caiman.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.11.1
1.11.2
2 changes: 2 additions & 0 deletions bin/caiman_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def make_color_img(img, gain=255, min_max=None, out_type=np.uint8):
# load object saved by CNMF
fpath = F.getOpenFileName(caption='Load CNMF Object',
filter='HDF5 (*.h5 *.hdf5);;NWB (*.nwb)')[0]
if fpath == '':
raise Exception("You must provide a filename")
cnm_obj = load_CNMF(fpath)

# movie
Expand Down
169 changes: 101 additions & 68 deletions caiman/base/movies.py

Large diffs are not rendered by default.

111 changes: 48 additions & 63 deletions caiman/base/rois.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,46 +28,36 @@
pass


def com(A: np.ndarray, d1: int, d2: int, d3: Optional[int] = None) -> np.array:
def com(A, d1: int, d2: int, d3: Optional[int] = None, order: str = 'F') -> np.ndarray:
"""Calculation of the center of mass for spatial components

Args:
A: np.ndarray
matrix of spatial components (d x K)
A: np.ndarray or scipy.sparse array or matrix
matrix of spatial components (d x K).

d1: int
number of pixels in x-direction

d2: int
number of pixels in y-direction

d3: int
number of pixels in z-direction
d1, d2, d3: ints
d1, d2, and (optionally) d3 are the original dimensions of the data.

order: 'C' or 'F'
how each column of A should be reshaped to match the given dimensions.

Returns:
cm: np.ndarray
center of mass for spatial components (K x 2 or 3)
center of mass for spatial components (K x D)
"""

if 'csc_matrix' not in str(type(A)):
A = scipy.sparse.csc_matrix(A)

if d3 is None:
Coor = np.matrix([np.outer(np.ones(d2), np.arange(d1)).ravel(),
np.outer(np.arange(d2), np.ones(d1)).ravel()],
dtype=A.dtype)
else:
Coor = np.matrix([
np.outer(np.ones(d3),
np.outer(np.ones(d2), np.arange(d1)).ravel()).ravel(),
np.outer(np.ones(d3),
np.outer(np.arange(d2), np.ones(d1)).ravel()).ravel(),
np.outer(np.arange(d3),
np.outer(np.ones(d2), np.ones(d1)).ravel()).ravel()
],
dtype=A.dtype)

cm = (Coor * A / A.sum(axis=0)).T
dims = [d1, d2]
if d3 is not None:
dims.append(d3)

# make coordinate arrays where coor[d] increases from 0 to npixels[d]-1 along the dth axis
coors = np.meshgrid(*[range(d) for d in dims], indexing='ij')
coor = np.stack([c.ravel(order=order) for c in coors])

# take weighted sum of pixel positions along each coordinate
cm = (coor @ A / A.sum(axis=0)).T
return np.array(cm)


Expand Down Expand Up @@ -222,6 +212,7 @@ def nf_match_neurons_in_binary_masks(masks_gt,
indices false pos

"""
logger = logging.getLogger("caiman")

_, d1, d2 = np.shape(masks_gt)
dims = d1, d2
Expand Down Expand Up @@ -262,7 +253,7 @@ def nf_match_neurons_in_binary_masks(masks_gt,
performance['precision'] = TP / (TP + FP)
performance['accuracy'] = (TP + TN) / (TP + FP + FN + TN)
performance['f1_score'] = 2 * TP / (2 * TP + FP + FN)
logging.debug(performance)
logger.debug(performance)

idx_tp = np.where(np.array(costs) < thresh_cost)[0]
idx_tp_ben = matches[0][idx_tp] # ground truth
Expand Down Expand Up @@ -307,8 +298,8 @@ def nf_match_neurons_in_binary_masks(masks_gt,
pl.show()
pl.axis('off')
except Exception as e:
logging.warning("not able to plot precision recall: graphics failure")
logging.warning(e)
logger.warning("not able to plot precision recall: graphics failure")
logger.warning(e)
return idx_tp_gt, idx_tp_comp, idx_fn_gt, idx_fp_comp, performance


Expand Down Expand Up @@ -402,11 +393,7 @@ def register_ROIs(A1,
ROIs from session 2 aligned to session 1

"""

# if 'csc_matrix' not in str(type(A1)):
# A1 = scipy.sparse.csc_matrix(A1)
# if 'csc_matrix' not in str(type(A2)):
# A2 = scipy.sparse.csc_matrix(A2)
logger = logging.getLogger("caiman")

if 'ndarray' not in str(type(A1)):
A1 = A1.toarray()
Expand Down Expand Up @@ -498,7 +485,7 @@ def register_ROIs(A1,
performance['precision'] = TP / (TP + FP)
performance['accuracy'] = (TP + TN) / (TP + FP + FN + TN)
performance['f1_score'] = 2 * TP / (2 * TP + FP + FN)
logging.info(performance)
logger.info(performance)

if plot_results:
if Cn is None:
Expand Down Expand Up @@ -531,11 +518,6 @@ def register_ROIs(A1,
pl.title('Mismatches')
pl.axis('off')


# except Exception as e:
# logging.warning("not able to plot precision recall usually because we are on travis")
# logging.warning(e)

return matched_ROIs1, matched_ROIs2, non_matched1, non_matched2, performance, A2


Expand Down Expand Up @@ -596,6 +578,7 @@ def register_multisession(A,
by component k in A_union

"""
logger = logging.getLogger("caiman")

n_sessions = len(A)
templates = list(templates)
Expand Down Expand Up @@ -625,7 +608,7 @@ def register_multisession(A,
enclosed_thr=enclosed_thr)

mat_sess, mat_un, nm_sess, nm_un, _, A2 = reg_results
logging.info(len(mat_sess))
logger.info(len(mat_sess))
A_union = A2.copy()
A_union[:, mat_un] = A[sess][:, mat_sess]
A_union = np.concatenate((A_union.toarray(), A[sess][:, nm_sess]), axis=1)
Expand Down Expand Up @@ -773,6 +756,7 @@ def distance_masks(M_s:list, cm_s: list[list], max_dist: float, enclosed_thr: Op

def find_matches(D_s, print_assignment: bool = False) -> tuple[list, list]:
# todo todocument
logger = logging.getLogger("caiman")

matches = []
costs = []
Expand All @@ -781,7 +765,7 @@ def find_matches(D_s, print_assignment: bool = False) -> tuple[list, list]:
# we make a copy not to set changes in the original
DD = D.copy()
if np.sum(np.where(np.isnan(DD))) > 0:
logging.error('Exception: Distance Matrix contains invalid value NaN')
logger.error('Exception: Distance Matrix contains invalid value NaN')
raise Exception('Distance Matrix contains invalid value NaN')

# we do the hungarian
Expand All @@ -794,10 +778,10 @@ def find_matches(D_s, print_assignment: bool = False) -> tuple[list, list]:
for row, column in indexes2:
value = DD[row, column]
if print_assignment:
logging.debug(('(%d, %d) -> %f' % (row, column, value)))
logger.debug(f'({row}, {column}) -> {value}')
total.append(value)
logging.debug(('FOV: %d, shape: %d,%d total cost: %f' % (ii, DD.shape[0], DD.shape[1], np.sum(total))))
logging.debug((time.time() - t_start))
logger.debug(f'FOV: {ii}, shape: {DD.shape[0]},{DD.shape[1]} total cost: {np.sum(total)}')
logger.debug(time.time() - t_start)
costs.append(total)
# send back the results in the format we want
return matches, costs
Expand Down Expand Up @@ -828,6 +812,8 @@ def link_neurons(matches: list[list[tuple]],
neurons: list of arrays representing the indices of neurons in each FOV

"""
logger = logging.getLogger("caiman")

if min_FOV_present is None:
min_FOV_present = len(matches)

Expand All @@ -852,7 +838,7 @@ def link_neurons(matches: list[list[tuple]],
neurons.append(neuron)

neurons = np.array(neurons).T
logging.info(f'num_neurons: {num_neurons}')
logger.info(f'num_neurons: {num_neurons}')
return neurons


Expand Down Expand Up @@ -927,6 +913,8 @@ def nf_read_roi(fileobj) -> np.ndarray:

Adapted from https://gist.github.com/luispedro/3437255
'''
logger = logging.getLogger("caiman")

# This is based on:
# http://rsbweb.nih.gov/ij/developer/source/ij/io/RoiDecoder.java.html
# http://rsbweb.nih.gov/ij/developer/source/ij/io/RoiEncoder.java.html
Expand Down Expand Up @@ -967,8 +955,7 @@ def getfloat():

magic = fileobj.read(4)
if magic != 'Iout':
# raise IOError('Magic number not found')
logging.warning('Magic number not found')
logger.warning('Magic number not found')
version = get16()

# It seems that the roi type field occupies 2 Bytes, but only one is used
Expand All @@ -977,13 +964,6 @@ def getfloat():
# Discard second Byte:
get8()

# if not (0 <= roi_type < 11):
# logging.error(('roireader: ROI type %s not supported' % roi_type))
#
# if roi_type != 7:
#
# logging.error(('roireader: ROI type %s not supported (!= 7)' % roi_type))

top = get16()
left = get16()
bottom = get16()
Expand Down Expand Up @@ -1060,6 +1040,8 @@ def nf_merge_roi_zip(fnames: list[str], idx_to_keep: list[list], new_fold: str):
name of the output zip file (without .zip extension)

"""
logger = logging.getLogger("caiman")

folders_rois = []
files_to_keep = []
# unzip the files and keep only the ones that are requested
Expand All @@ -1068,7 +1050,7 @@ def nf_merge_roi_zip(fnames: list[str], idx_to_keep: list[list], new_fold: str):
folders_rois.append(dirpath)
with zipfile.ZipFile(fn) as zf:
name_rois = zf.namelist()
logging.debug(len(name_rois))
logger.debug(len(name_rois))
zip_ref = zipfile.ZipFile(fn, 'r')
zip_ref.extractall(dirpath)
files_to_keep.append([os.path.join(dirpath, ff) for ff in np.array(name_rois)[idx]])
Expand Down Expand Up @@ -1119,6 +1101,8 @@ def extract_binary_masks_blob(A,
neg_examples:

"""
logger = logging.getLogger("caiman")

params = cv2.SimpleBlobDetector_Params()
params.minCircularity = minCircularity
params.minInertiaRatio = minInertiaRatio
Expand Down Expand Up @@ -1146,7 +1130,7 @@ def extract_binary_masks_blob(A,
neg_examples = []

for count, comp in enumerate(A.tocsc()[:].T):
logging.debug(count)
logger.debug(count)
comp_d = np.array(comp.todense())
gray_image = np.reshape(comp_d, dims, order='F')
gray_image = (gray_image - np.min(gray_image)) / \
Expand All @@ -1171,7 +1155,7 @@ def extract_binary_masks_blob(A,
edges = (label_objects == (1 + idx_largest))
edges = scipy.ndimage.binary_fill_holes(edges)
else:
logging.warning('empty component')
logger.warning('empty component')
edges = np.zeros_like(edges)

masks_ws.append(edges)
Expand Down Expand Up @@ -1275,14 +1259,15 @@ def detect_duplicates_and_subsets(binary_masks,
dist_thr: float = 0.1,
min_dist=10,
thresh_subset: float = 0.8):
logger = logging.getLogger("caiman")

cm = [scipy.ndimage.center_of_mass(mm) for mm in binary_masks]
sp_rois = scipy.sparse.csc_matrix(np.reshape(binary_masks, (binary_masks.shape[0], -1)).T)
D = distance_masks([sp_rois, sp_rois], [cm, cm], min_dist)[0]
np.fill_diagonal(D, 1)
overlap = sp_rois.T.dot(sp_rois).toarray()
sz = np.array(sp_rois.sum(0))
logging.info(sz.shape)
logger.info(sz.shape)
overlap = overlap / sz.T
np.fill_diagonal(overlap, 0)
# pairs of duplicate indices
Expand All @@ -1297,7 +1282,7 @@ def detect_duplicates_and_subsets(binary_masks,
metric = r_values.squeeze()
else:
metric = sz.squeeze()
logging.debug('***** USING MAX AREA BY DEFAULT')
logger.debug('***** USING MAX AREA BY DEFAULT')

overlap_tmp = overlap.copy() >= thresh_subset
overlap_tmp = overlap_tmp * metric[:, None]
Expand Down
Loading