Skip to content

Commit 526c747

Browse files
authored
Merge pull request #1529 from AllenInstitute/rc/1.8.0
Release candidate branch 1.8.0
2 parents 9d95966 + 532c729 commit 526c747

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+11494
-197
lines changed

CHANGELOG.md

100755100644
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4-
## [ 1.8.0]
4+
## [1.8.0] = 2020-06-06
5+
6+
### Added
7+
- The biophysical module can now run both current and legacy all-active models.
8+
- Internal users can now access `date_of_acquisition` for behavior-only Session data.
9+
- A pull request template was added to the repository.
10+
11+
### Changed
12+
- The CSV log was removed from `BehaviorProjectCache` (internal users).
13+
- Duplicated demixer module was deprecated, and test coverage was added.
14+
- Docker image for AllenSDK was updated.
15+
516
### Bug Fixes
617
- Internal LIMS data served to `BehaviorDataSession` class now all use the same timestamp source
718

allensdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import logging
3737

3838

39-
__version__ = '1.7.1'
39+
__version__ = '1.8.0'
4040

4141

4242
try:

allensdk/brain_observatory/behavior/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
IMAGE_SETS = {'Natural_Images_Lum_Matched_set_ophys_6_2017.07.14': '//allen/programs/braintv/workgroups/nc-ophys/Doug/Stimulus_Code/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_6_2017.07.14.pkl',
33
'Natural_Images_Lum_Matched_set_training_2017.07.14': '//allen/programs/braintv/workgroups/nc-ophys/Doug/Stimulus_Code/image_dictionaries/Natural_Images_Lum_Matched_set_training_2017.07.14.pkl',
44
'Natural_Images_Lum_Matched_set_training_2017.07.14_2': '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_training_2017.07.14.pkl',
5-
'Natural_Images_Lum_Matched_set_ophys_6_2017.07.14_2': '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_6_2017.07.14.pkl'}
5+
'Natural_Images_Lum_Matched_set_ophys_6_2017.07.14_2': '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_6_2017.07.14.pkl',
6+
'Natural_Images_Lum_Matched_set_ophys_H_2019.05.26': '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_H_2019.05.26.pkl',
7+
'Natural_Images_Lum_Matched_set_ophys_G_2019.05.26': '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_G_2019.05.26.pkl'}
68

79

810

allensdk/brain_observatory/behavior/behavior_project_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
class BehaviorProjectCache(Cache):
2121

22-
MANIFEST_VERSION = "0.0.1-alpha.1"
22+
MANIFEST_VERSION = "0.0.1-alpha.2"
2323
OPHYS_SESSIONS_KEY = "ophys_sessions"
2424
BEHAVIOR_SESSIONS_KEY = "behavior_sessions"
2525
OPHYS_EXPERIMENTS_KEY = "ophys_experiments"

allensdk/brain_observatory/behavior/behavior_project_lims_api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def __init__(self, lims_engine, mtrain_engine, app_engine):
2929
Typically want to construct an instance of this class by calling
3030
`BehaviorProjectLimsApi.default()`.
3131
32+
Set log level to debug to see SQL queries dumped by
33+
"BehaviorProjectLimsApi" logger.
34+
3235
Note -- Currently the app engine is unused because we aren't yet
3336
supporting the download of stimulus templates for visual behavior
3437
data. This feature will be added at a later date.
@@ -196,11 +199,14 @@ def _get_behavior_summary_table(self,
196199
bs.ophys_session_id,
197200
bs.behavior_training_id,
198201
equipment.name as equipment_name,
202+
bs.date_of_acquisition,
199203
d.id as donor_id,
200204
d.full_genotype,
201205
reporter.reporter_line,
202206
driver.driver_line,
203207
g.name AS sex,
208+
DATE_PART('day', bs.date_of_acquisition - d.date_of_birth)
209+
AS age_in_days,
204210
bs.foraging_id
205211
FROM behavior_sessions bs
206212
JOIN donors d on bs.donor_id = d.id
@@ -214,6 +220,7 @@ def _get_behavior_summary_table(self,
214220
JOIN equipment ON equipment.id = bs.equipment_id
215221
{session_sub_query}
216222
"""
223+
self.logger.debug(f"get_behavior_session_table query: \n{query}")
217224
return self.lims_engine.select(query)
218225

219226
def _get_foraging_ids_from_behavior_session(
@@ -242,6 +249,7 @@ def _get_behavior_stage_table(
242249
if behavior_session_ids:
243250
foraging_ids = self._get_foraging_ids_from_behavior_session(
244251
behavior_session_ids)
252+
foraging_ids = [f"'{fid}'" for fid in foraging_ids]
245253
# Otherwise just get the full table from mtrain
246254
else:
247255
foraging_ids = None
@@ -257,6 +265,7 @@ def _get_behavior_stage_table(
257265
JOIN stages ON stages.id = bs.state_id
258266
{foraging_ids_query};
259267
"""
268+
self.logger.debug(f"_get_behavior_stage_table query: \n {query}")
260269
return self.mtrain_engine.select(query)
261270

262271
def get_session_data(self, ophys_session_id: int) -> BehaviorOphysSession:

allensdk/brain_observatory/behavior/stimulus_processing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def convert_filepath_caseinsensitive(filename_in):
1818
return '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_training_2017.07.14.pkl'
1919
elif filename_in == '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_6_2017.07.14.pkl':
2020
return '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_6_2017.07.14.pkl'
21+
elif filename_in == '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_G_2019.05.26.pkl':
22+
return '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_G_2019.05.26.pkl'
23+
elif filename_in == '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_H_2019.05.26.pkl':
24+
return '//allen/programs/braintv/workgroups/nc-ophys/visual_behavior/image_dictionaries/Natural_Images_Lum_Matched_set_ophys_H_2019.05.26.pkl'
2125
else:
2226
raise NotImplementedError(filename_in)
2327

allensdk/brain_observatory/demixer.py

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,100 @@
3333
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3434
# POSSIBILITY OF SUCH DAMAGE.
3535
#
36+
from typing import Tuple, Optional
37+
import os
38+
import logging
39+
40+
import numpy as np
3641
import scipy.sparse as sparse
3742
import scipy.linalg as linalg
38-
import numpy as np
39-
import os
4043
import matplotlib.pyplot as plt
41-
import logging
4244
import matplotlib.colors as colors
45+
46+
import allensdk.internal.brain_observatory.mask_set as mask_set
4347
from allensdk.config.manifest import Manifest
4448

45-
def demix_time_dep_masks(raw_traces, stack, masks):
46-
'''
4749

48-
:param raw_traces: extracted traces
49-
:param stack: movie (same length as traces)
50-
:param masks: binary roi masks
51-
:return: demixed traces
52-
'''
50+
def identify_valid_masks(mask_array):
51+
ms = mask_set.MaskSet(masks=mask_array.astype(bool))
52+
valid_masks = np.ones(mask_array.shape[0]).astype(bool)
53+
54+
# detect duplicates
55+
duplicates = ms.detect_duplicates(overlap_threshold=0.9)
56+
if len(duplicates) > 0:
57+
valid_masks[duplicates.keys()] = False
58+
59+
# detect unions, only for remaining valid masks
60+
valid_idxs = np.where(valid_masks)
61+
ms = mask_set.MaskSet(masks=mask_array[valid_idxs].astype(bool))
62+
unions = ms.detect_unions()
63+
64+
if len(unions) > 0:
65+
un_idxs = unions.keys()
66+
valid_masks[valid_idxs[0][un_idxs]] = False
67+
68+
return valid_masks
69+
70+
71+
def _demix_point(source_frame: np.ndarray, mask_traces: np.ndarray,
72+
flat_masks: sparse,
73+
pixels_per_mask: np.ndarray) -> Optional[np.ndarray]:
74+
"""
75+
Helper function to run demixing for single point in time for a
76+
source with overlapping traces.
77+
78+
Parameters
79+
==========
80+
source_frame: values of movie source at the single time point,
81+
unraveled in the x-y dimension (1d array of length HxW )
82+
flat_masks: 2d-array of binary masks unraveled in the x-y dimension
83+
mask traces: values of mask trace at single time point (1d-array of
84+
length n, where `n` is number of masks)
85+
pixels_per_mask: Number of pixels for each mask associated with
86+
trace (1d-array of length `n`)
87+
88+
Returns
89+
=======
90+
Array of demixed trace values for each mask if all trace data is
91+
nonzero. Otherwise, returns None.
92+
"""
93+
mask_weighted_trace = mask_traces * pixels_per_mask
94+
95+
# Skip if there is zero signal anywhere in one of the traces
96+
if (mask_weighted_trace == 0).any():
97+
return None
98+
norm_mat = sparse.diags(pixels_per_mask / mask_weighted_trace, offsets=0)
99+
source_mat = sparse.diags(source_frame, offsets=0)
100+
source_mask_projection = flat_masks.dot(source_mat)
101+
weighted_masks = norm_mat.dot(source_mask_projection)
102+
# cast to dense numpy array for linear solver because solution is dense
103+
overlap = flat_masks.dot(weighted_masks.T).toarray()
104+
try:
105+
demix_traces = linalg.solve(overlap, mask_weighted_trace)
106+
except linalg.LinAlgError:
107+
logging.warning("Singular matrix, using least squares to solve.")
108+
x, _, _, _ = linalg.lstsq(overlap, mask_weighted_trace)
109+
demix_traces = x
110+
return demix_traces
111+
112+
113+
def demix_time_dep_masks(raw_traces: np.ndarray, stack: np.ndarray,
114+
masks: np.ndarray) -> Tuple[np.ndarray, list]:
115+
"""
116+
Demix traces of potentially overlapping masks extraced from a single
117+
2p recording.
118+
119+
:param raw_traces: 2d array of traces for each mask, of dimensions
120+
(t, n), where `t` is the number of time points and `n` is the
121+
number of masks.
122+
:param stack: 3d array representing a 1p recording movie, of
123+
dimensions (t, H, W).
124+
:param masks: 3d array of binary roi masks, of shape (n, H, W),
125+
where `n` is the number of masks, and HW are the dimensions of
126+
an individual frame in the movie `stack`.
127+
:return: Tuple of demixed traces and whether each frame was skipped
128+
in the demixing calculation.
129+
"""
53130
N, T = raw_traces.shape
54131
_, x, y = masks.shape
55132
P = x * y
@@ -58,8 +135,6 @@ def demix_time_dep_masks(raw_traces, stack, masks):
58135
stack = stack.reshape(T, P)
59136

60137
num_pixels_in_mask = np.sum(masks, axis=(1, 2))
61-
F = raw_traces.T * num_pixels_in_mask # shape (T,N)
62-
F = F.T
63138

64139
flat_masks = masks.reshape(N, P)
65140
flat_masks = sparse.csr_matrix(flat_masks)
@@ -68,31 +143,16 @@ def demix_time_dep_masks(raw_traces, stack, masks):
68143
demix_traces = np.zeros((N, T))
69144

70145
for t in range(T):
71-
72-
weighted_mask_sum = F[:, t]
73-
drop_test = (weighted_mask_sum == 0)
74-
75-
if np.sum(drop_test == 0):
76-
norm_mat = sparse.diags(num_pixels_in_mask / weighted_mask_sum, offsets=0)
77-
stack_t = sparse.diags(stack[t], offsets=0)
78-
79-
flat_weighted_masks = norm_mat.dot(flat_masks.dot(stack_t))
80-
81-
overlap = flat_masks.dot(flat_weighted_masks.T).toarray() # cast to dense numpy array for linear solver because solution is dense
82-
try:
83-
demix_traces[:, t] = linalg.solve(overlap, F[:, t])
84-
except linalg.LinAlgError as e:
85-
logging.warning("singular matrix, using least squares")
86-
x, _, _, _ = linalg.lstsq(overlap, F[:, t])
87-
demix_traces[:, t] = x
88-
146+
demixed_point = _demix_point(
147+
stack[t], raw_traces[:, t], flat_masks, num_pixels_in_mask)
148+
if demixed_point is not None:
149+
demix_traces[:, t] = demixed_point
89150
drop_frames.append(False)
90-
91151
else:
92152
drop_frames.append(True)
93-
94153
return demix_traces, drop_frames
95154

155+
96156
def plot_traces(raw_trace, demix_trace, roi_id, roi_ind, save_file):
97157
fig, ax = plt.subplots()
98158

@@ -103,12 +163,15 @@ def plot_traces(raw_trace, demix_trace, roi_id, roi_ind, save_file):
103163
plt.savefig(save_file)
104164
plt.close(fig)
105165

166+
106167
def find_zero_baselines(traces):
107168
means = traces.mean(axis=1)
108-
stds = traces.std(axis=1)
169+
stds = traces.std(axis=1)
109170
return np.where((means-stds) < 0)
110-
111-
def plot_negative_baselines(raw_traces, demix_traces, mask_array, roi_ids_mask, plot_dir, ext='png'):
171+
172+
173+
def plot_negative_baselines(raw_traces, demix_traces, mask_array,
174+
roi_ids_mask, plot_dir, ext='png'):
112175
N, T = raw_traces.shape
113176
_, x, y = mask_array.shape
114177

@@ -134,11 +197,10 @@ def plot_negative_baselines(raw_traces, demix_traces, mask_array, roi_ids_mask,
134197
overlap_inds.update(zero_inds)
135198

136199
return list(overlap_inds)
137-
138200

139-
140-
141-
def plot_negative_transients(raw_traces, demix_traces, valid_roi, mask_array, roi_ids_mask, plot_dir, ext='png'):
201+
202+
def plot_negative_transients(raw_traces, demix_traces, valid_roi, mask_array,
203+
roi_ids_mask, plot_dir, ext='png'):
142204

143205
N, T = raw_traces.shape
144206
_, x, y = mask_array.shape
@@ -227,6 +289,7 @@ def find_negative_baselines(trace):
227289
stds = trace.std(axis=1)
228290
return np.where((means+stds) < 0)
229291

292+
230293
def find_negative_transients_threshold(trace, window=500, length=10, std_devs=3):
231294
trace = np.pad(trace, pad_width=(window-1, 0), mode='constant', constant_values=[np.mean(trace[:window])])
232295
rolling_mean = np.mean(rolling_window(trace, window), -1)
@@ -332,4 +395,3 @@ def plot_transients(roi_ind, t_trans, masks, traces, demix_traces, savefile):
332395

333396
plt.savefig(savefile)
334397
plt.close(fig)
335-

0 commit comments

Comments
 (0)