Skip to content

Commit b01b9f4

Browse files
authored
Allow loading seed for CNMF and CNMFE (#336)
* Add HDF5 format movie reader * Correctly set var_name_hdf5 when loading hdf5 and this param is set * Add parameter for loading Ain instead of automatic initialization * Fix f-string bug * Include non-main params in get_params_diffs * Get rid of hack for Ain since not needed after last commit * Factor out params_diffs logic * Allow using pre-computed seed for CNMFE * Ensure correct params are set when Ain is provided; add tests * Update tolerance for seeded CNMFE test * Update tolerance for seeded CNMFE test (again)
1 parent 5713558 commit b01b9f4

5 files changed

Lines changed: 299 additions & 87 deletions

File tree

mesmerize_core/algorithms/cnmf.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,24 @@ def run_algo(batch_path, uuid, data_path: str = None, dview=None):
6666
)
6767
np.save(str(proj_paths[proj_type]), p_img)
6868

69+
# load Ain if given
70+
if 'Ain_path' in params and params['Ain_path'] is not None:
71+
Ain_path_abs = output_dir / params['Ain_path'] # resolve relative to output dir
72+
Ain = np.load(Ain_path_abs, allow_pickle=True)
73+
if Ain.size == 1: # sparse array loaded as object
74+
Ain = Ain.item()
75+
76+
# force params needed for seeded CNMF
77+
cnmf_params.change_params({'patch': {'rf': None, 'only_init': False}})
78+
else:
79+
Ain = None
80+
6981
print("performing CNMF")
70-
cnm = cnmf.CNMF(n_processes, params=cnmf_params, dview=dview)
82+
cnm = cnmf.CNMF(n_processes, params=cnmf_params, dview=dview, Ain=Ain)
7183

7284
print("fitting images")
7385
cnm.fit(images)
74-
#
86+
7587
if "refit" in params.keys():
7688
if params["refit"] is True:
7789
print("refitting")

mesmerize_core/algorithms/cnmfe.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,28 @@ def run_algo(batch_path, uuid, data_path: str = None, dview=None):
3737

3838
with ensure_server(dview) as (dview, n_processes):
3939
try:
40+
# force the CNMFE params
41+
cnmfe_params = {
42+
"method_init": "corr_pnr",
43+
"n_processes": n_processes,
44+
"only_init": True, # for 1p
45+
"center_psf": True, # for 1p
46+
"normalize_init": False, # for 1p
47+
}
48+
49+
params_dict = {**cnmfe_params, **params["main"]}
50+
51+
cnmfe_params = CNMFParams(params_dict=params_dict)
52+
53+
print("making memmap")
4054
fname_new = cm.save_memmap(
41-
[input_movie_path], base_name=f"{uuid}_cnmf-memmap_", order="C", dview=dview
55+
[input_movie_path],
56+
base_name=f"{uuid}_cnmf-memmap_",
57+
order="C",
58+
dview=dview,
59+
var_name_hdf5=cnmfe_params.data['var_name_hdf5']
4260
)
4361

44-
print("making memmap")
4562
Yr, dims, T = cm.load_memmap(fname_new)
4663
images = np.reshape(Yr.T, [T] + list(dims), order="F")
4764

@@ -57,19 +74,23 @@ def run_algo(batch_path, uuid, data_path: str = None, dview=None):
5774

5875
d = dict() # for output
5976

60-
# force the CNMFE params
61-
cnmfe_params_dict = {
62-
"method_init": "corr_pnr",
63-
"n_processes": n_processes,
64-
"only_init": True, # for 1p
65-
"center_psf": True, # for 1p
66-
"normalize_init": False, # for 1p
67-
}
68-
69-
params_dict = {**cnmfe_params_dict, **params["main"]}
70-
71-
cnmfe_params_dict = CNMFParams(params_dict=params_dict)
72-
cnm = cnmf.CNMF(n_processes=n_processes, dview=dview, params=cnmfe_params_dict)
77+
# load Ain if given
78+
if "Ain_path" in params and params["Ain_path"] is not None:
79+
Ain_path_abs = (
80+
output_dir / params["Ain_path"]
81+
) # resolve relative to output dir
82+
Ain = np.load(Ain_path_abs, allow_pickle=True)
83+
if Ain.size == 1: # sparse array loaded as object
84+
Ain = Ain.item()
85+
86+
# force params needed for seeded CNMFE
87+
cnmfe_params.change_params({'patch': {'rf': None, 'only_init': False}})
88+
else:
89+
Ain = None
90+
91+
cnm = cnmf.CNMF(
92+
n_processes=n_processes, dview=dview, params=cnmfe_params, Ain=Ain
93+
)
7394
print("Performing CNMFE")
7495
cnm.fit(images)
7596
print("evaluating components")

mesmerize_core/caiman_extensions/common.py

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
get_parent_raw_data_path,
3131
load_batch,
3232
)
33-
from ..utils import IS_WINDOWS, make_runfile, warning_experimental
33+
from ..utils import IS_WINDOWS, make_runfile, warning_experimental, get_params_diffs
3434
from .cnmf import cnmf_cache
3535
from .. import algorithms
3636
from ..movie_readers import default_reader
@@ -351,22 +351,7 @@ def get_params_diffs(self, algo: str, item_name: str) -> pd.DataFrame:
351351
index of the original DataFrame
352352
353353
"""
354-
355-
def flatten_params(params_dict: dict):
356-
"""
357-
Produce a flat dict with one entry for each parameter in the passed dict.
358-
If params_dict['main'] is nested one level (e.g., {'init': {'K': 5}, 'merging': {'merge_thr': 0.85}}...),
359-
each key in the output is <outerKey>.<innerKey>, e.g., [(init.K, 5), (merging.merge_thr, 0.85)]
360-
"""
361-
params = {}
362-
for key1, val1 in params_dict.items():
363-
if isinstance(val1, dict): # nested
364-
for key2, val2 in val1.items():
365-
params[f"{key1}.{key2}"] = val2
366-
else:
367-
params[key1] = val1
368-
return params
369-
354+
370355
sub_df = self._df[self._df["item_name"] == item_name]
371356
sub_df = sub_df[sub_df["algo"] == algo]
372357

@@ -375,52 +360,11 @@ def flatten_params(params_dict: dict):
375360
f"The given `item_name`: {item_name}, does not exist in the DataFrame"
376361
)
377362

378-
# get flattened parameters for each of the filtered items
379-
params_flat = sub_df.params.map(lambda p: flatten_params(p["main"]))
380-
381-
# build list of params that differ between different parameter sets
382-
common_params = deepcopy(
383-
params_flat.iat[0]
384-
) # holds the common value for parameters found in all sets (so far)
385-
varying_params = (
386-
set()
387-
) # set of parameter keys that appear in not all sets or with varying values
388-
389-
for this_params in params_flat.iloc[1:]:
390-
# first, anything that's not in both this dict and the common set is considered varying
391-
common_paramset = set(common_params.keys())
392-
for not_common_key in common_paramset.symmetric_difference(
393-
this_params.keys()
394-
):
395-
varying_params.add(not_common_key)
396-
if not_common_key in common_paramset:
397-
del common_params[not_common_key]
398-
common_paramset.remove(not_common_key)
399-
400-
# second, look at params in the common set and remove any that differ for this set
401-
for (
402-
key
403-
) in (
404-
common_paramset
405-
): # iterate over this set rather than dict itself to avoid issues when deleting entries
406-
if not np.array_equal(
407-
common_params[key], this_params[key]
408-
): # (should also work for scalars/arbitrary objects)
409-
varying_params.add(key)
410-
del common_params[key]
411-
412-
# gives a list where each item is a dict that has the unique params that correspond to a row
413-
# the indices of this series correspond to the index of the row in the parent dataframe
414-
diffs = params_flat.map(
415-
lambda p: {
416-
key: p[key] if key in p else "<default>" for key in varying_params
417-
}
418-
)
363+
params_list = sub_df.params.tolist()
364+
diffs = get_params_diffs(params_list)
419365

420366
# return as a nicely formatted dataframe
421-
diffs_df = pd.DataFrame.from_dict(diffs.tolist(), dtype=object).set_index(
422-
diffs.index
423-
)
367+
diffs_df = pd.DataFrame.from_dict(diffs, dtype=object).set_index(sub_df.index)
424368

425369
return diffs_df
426370

mesmerize_core/utils.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import sys
1616
from tempfile import NamedTemporaryFile
1717
from subprocess import check_call
18+
from copy import deepcopy
19+
import pandas as pd
1820
import shlex
1921
import mslex
2022

@@ -174,3 +176,50 @@ def _organize_coordinates(contour: dict):
174176
coors = coors[~np.isnan(coors).any(axis=1)]
175177

176178
return coors
179+
180+
181+
def flatten_params(params_dict: dict) -> dict:
182+
"""
183+
Produce a flat dict with one entry for each parameter in the passed dict.
184+
If params_dict['main'] is nested one level (e.g., {'init': {'K': 5}, 'merging': {'merge_thr': 0.85}}...),
185+
each key in the output is <outerKey>.<innerKey>, e.g., [(init.K, 5), (merging.merge_thr, 0.85)]
186+
"""
187+
params = {}
188+
for key1, val1 in params_dict.items():
189+
if key1 == "main":
190+
# recursively step into "main" params
191+
params.update(flatten_params(val1))
192+
elif isinstance(val1, dict): # nested
193+
for key2, val2 in val1.items():
194+
params[f"{key1}.{key2}"] = val2
195+
else:
196+
params[key1] = val1
197+
return params
198+
199+
200+
def get_params_diffs(params: Sequence[dict]) -> list[dict]:
201+
"""Compute differences between params used for mesmerize"""
202+
# get flattened parameters for each of the filtered items
203+
params_flat = list(map(flatten_params, params))
204+
205+
# build list of params that differ between different parameter sets
206+
common_params = deepcopy(params_flat[0]) # holds the common value for parameters found in all sets (so far)
207+
varying_params = set() # set of parameter keys that appear in not all sets or with varying values
208+
209+
for this_params in params_flat[1:]:
210+
# first, anything that's not in both this dict and the common set is considered varying
211+
common_paramset = set(common_params.keys())
212+
for not_common_key in common_paramset.symmetric_difference(this_params.keys()):
213+
varying_params.add(not_common_key)
214+
if not_common_key in common_paramset:
215+
del common_params[not_common_key]
216+
common_paramset.remove(not_common_key)
217+
218+
# second, look at params in the common set and remove any that differ for this set
219+
for key in common_paramset: # iterate over this set rather than dict itself to avoid issues when deleting entries
220+
if not np.array_equal(common_params[key], this_params[key]): # (should also work for scalars/arbitrary objects)
221+
varying_params.add(key)
222+
del common_params[key]
223+
224+
# gives a list where each item is a dict that has the unique params that correspond to a row
225+
return [{key: p[key] if key in p else "<default>" for key in varying_params} for p in params_flat]

0 commit comments

Comments
 (0)