"""
Module containing Experiment object and supporting functions.
"""
# Authors: Joseph Knox <josephk@alleninstitute.org>
# License: Allen Institute Software License
from __future__ import division
from functools import partial
import numpy as np
from .utils import compute_centroid, get_injection_hemisphere_id
def _pull_grid_data(cache, experiment_id):
"""Pulls data volumes using VoxelModelCache object.
Parameters
----------
cache : VoxelModelCache or MouseConnectivityCache instance.
Object used to pull grid data.
experiment_id : int
Experiment id of the experiment from which to pull grid data.
Returns
-------
dict
Container of relevant data volumes.
See allensdk.core.mouse_connectivity_cache for description of volumes.
Notes
-----
voxel_model_cache.get_<data_volume> returns a tuple (data_volume, meta_data).
We only care about the data volume.
"""
return {
"data_mask" : cache.get_data_mask(experiment_id)[0],
"injection_density" : cache.get_injection_density(experiment_id)[0],
"injection_fraction" : cache.get_injection_fraction(experiment_id)[0],
"projection_density" : cache.get_projection_density(experiment_id)[0]
}
def _mask_data_volume(data_volume, data_mask, tolerance=0.0):
"""Masks a given data volume in place.
Parameters
----------
data_volume : array, shape (x_ccf, y_ccf, z_ccf)
Data volume to be masked.
data_mask : array, shape (x_ccf, y_ccf, z_ccf)
data_mask for given experiment (values in [0,1])
See allensdk.core.mouse_connectivity_cache for more info.
tolerance : float, optional (default=0.0)
tolerance with which to define bad voxels in data_mask.
Returns
-------
data_volume
data_volume parameter masked in place.
"""
if data_volume.shape != data_mask.shape:
raise ValueError("data_volume (%s) and data_mask (%s) must be the same "
"shape!" % (data_volume.shape, data_mask.shape))
# mask data volume
data_volume[data_mask < tolerance] = 0.0
return data_volume
def _compute_true_injection_density(injection_density, injection_fraction, inplace=False):
"""Computes 'true' injection_density.
Takes into consideration injection fracion (proportion of pixels in the
annotated injection site).
Parameters
----------
injection_density : array, shape (x_ccf, y_ccf, z_ccf)
injection_density data volume.
injection_fraction : array, shape (x_ccf, y_ccf, z_ccf)
injection_fraction data volume.
inplace : boolean
If True, overwrites injection_density parameter, else returns new array.
Returns
-------
array, shape (x_ccf, y_ccf, z_ccf)
'true' injection density : injection_density * injection_fraction
"""
if injection_density.shape != injection_fraction.shape:
raise ValueError("injection_density (%s) and injection_fraction "
"(%s) must be the same shape!"
% (injection_density.shape, injection_fraction.shape))
if inplace:
np.multiply(injection_density, injection_fraction, injection_density)
return injection_density
return np.multiply(injection_density, injection_fraction)
[docs]class Experiment(object):
"""Class containing the data from an anterograde injection
Experiment conveniently compiles the relevant information from a given
anterograde viral tracing experiment data.
Parameters
----------
voxel_model_cache : VoxelModelCache object
This supplies the interface for pulling experimental data.
experiment_id : int
AllenSDK id assigned to given experiment
Examples
--------
>>> from mcmodels.core import Experiment, VoxelModelCache
>>> cache = VoxelModelCache()
>>> eid = 100141273
>>> exp = Experiment(voxel_model_cache, eid)
>>> exp.injection_density.shape
(132,80,114)
"""
DEFAULT_DATA_MASK_TOLERANCE = 0.5
[docs] @classmethod
def from_cache(cls, cache, experiment_id, data_mask_tolerance=None):
"""Alternative constructor allowing for pulling grid data.
Parameters
----------
cache : VoxelModelCache or MouseConnectivityCache instance.
Object used to pull grid data.
experiment_id : int
Experiment id of the experiment from which to pull grid data.
data_mask_tolerance : float, optional (default = None)
Tolerance with which to mask 'bad' data. data_mask array has values
on the interval [0,1], where a nonzero element indicates a 'bad'
voxel. If None is passed, the parameter defaults to
DEFAULT_DATA_MASK_TOLERANCE (0.5).
"""
if data_mask_tolerance is None:
data_mask_tolerance = cls.DEFAULT_DATA_MASK_TOLERANCE
try:
# pull data
data_volumes = _pull_grid_data(cache, experiment_id)
except AttributeError:
raise ValueError('cache must be a MouseConnectivityCache or '
'VoxelModelCache object, not %s' % type(cache))
# compute 'true' injection density (inplace)
_compute_true_injection_density(data_volumes["injection_density"],
data_volumes["injection_fraction"],
inplace=True)
# mask data in place
mask_func = partial(_mask_data_volume,
data_mask=data_volumes["data_mask"],
tolerance=data_mask_tolerance)
injection_density = mask_func(data_volumes["injection_density"])
projection_density = mask_func(data_volumes["projection_density"])
return cls(injection_density, projection_density)
[docs] def __init__(self, injection_density=None, projection_density=None):
# assume numpy array
if injection_density.shape != projection_density.shape:
raise ValueError("injection_density (%s) and projection_density "
"(%s) must be the same shape!"
% (injection_density.shape, projection_density.shape))
self.injection_density = injection_density
self.projection_density = projection_density
def __repr__(self):
return '{0}(volume_shape={1})'.format(
self.__class__.__name__, self.injection_density.shape)
@property
def injection_hemisphere_id(self):
"""Returns injection hemisphere"""
return get_injection_hemisphere_id(self.injection_density, majority=True)
@property
def bilateral_injection(self):
"""Returns injection hemisphere"""
return get_injection_hemisphere_id(self.injection_density) == 3
@property
def injection_volume(self):
"""Returns total injection volume = sum(injection_density)"""
return self.injection_density.sum()
@property
def projection_volume(self):
"""Returns total projection volume = sum(projection_density)"""
return self.projection_density.sum()
@property
def centroid(self):
"""Returns centroid of the injection density."""
return compute_centroid(self.injection_density)
@property
def normalized_injection_density(self):
"""Returns injection density normalized to have unit sum."""
return self.injection_density / self.injection_volume
@property
def normalized_projection_density(self):
"""Returns projection_density normalized by the total injection_density"""
return self.projection_density / self.injection_volume
def get_injection(self, normalized=False):
if normalized:
return self.normalized_injection_density
return self.injection_density
def get_projection(self, normalized=False):
if normalized:
return self.normalized_projection_density
return self.projection_density
[docs] def flip(self):
"""Reflects experiment along midline.
Returns
-------
self - flipped experiment
"""
self.injection_density = self.injection_density[..., ::-1]
self.projection_density = self.projection_density[..., ::-1]
return self