示例#1
0
def test_ROISetDict_exceptions():

    roi_set = dc_types.ROISetDict()
    with pytest.raises(NotImplementedError):
        roi_set['roi'] = dc_types.ROIDict()

    with pytest.raises(KeyError):
        roi_set[9]
    def get_trace(self, roi_list: List[OphysROI]) -> dc_types.ROISetDict:
        """
        Extract the traces from a movie as defined by the ROIs in roi_list

        Parameters
        ----------
        roi_list -- a list of OphysROI instantiations
                    specifying the ROIs from which to
                    extract traces

        Returns
        -------
        output -- a decrosstalk_types.ROISetDict containing the ROI and
                  neuropil traces associated with roi_list. For each ROI
                  in the ROISetDict, only the 'signal' channel will be
                  populated, this with the trace extracted from the movie.
        """
        motion_border = [
            self._motion_border['x0'], self._motion_border['x1'],
            self._motion_border['y0'], self._motion_border['y1']
        ]

        height = self.data.shape[1]
        width = self.data.shape[2]

        roi_mask_list = []
        for roi in roi_list:
            pixels = np.argwhere(roi.mask_matrix)
            pixels[:, 0] += roi.y0
            pixels[:, 1] += roi.x0
            mask = roi_masks.create_roi_mask(width,
                                             height,
                                             motion_border,
                                             pix_list=pixels[:, [1, 0]],
                                             label=str(roi.roi_id),
                                             mask_group=-1)

            roi_mask_list.append(mask)

        _traces = roi_masks.calculate_roi_and_neuropil_traces(
            self.data, roi_mask_list, motion_border)
        roi_traces = _traces[0]
        neuropil_traces = _traces[1]

        output = dc_types.ROISetDict()
        for i_roi, roi in enumerate(roi_list):

            trace = dc_types.ROIChannels()
            trace['signal'] = roi_traces[i_roi]
            output['roi'][roi.roi_id] = trace

            trace = dc_types.ROIChannels()
            trace['signal'] = neuropil_traces[i_roi]
            output['neuropil'][roi.roi_id] = trace

        return output
示例#3
0
def test_ROISetDict():

    rng = np.random.RandomState(53124)
    signals = list([rng.random_sample(10) for ii in range(4)])
    crosstalks = list([rng.random_sample(10) for ii in range(4)])

    roi_set_dict = dc_types.ROISetDict()

    for ii in range(2):
        c = dc_types.ROIChannels()
        c['signal'] = signals[ii]
        c['crosstalk'] = crosstalks[ii]
        roi_set_dict['roi'][ii] = c

    for ii in range(2, 4):
        c = dc_types.ROIChannels()
        c['signal'] = signals[ii]
        c['crosstalk'] = crosstalks[ii]
        roi_set_dict['neuropil'][ii-2] = c

    np_almost(roi_set_dict['roi'][0]['signal'],
              signals[0], decimal=10)

    np_almost(roi_set_dict['roi'][1]['signal'],
              signals[1], decimal=10)

    np_almost(roi_set_dict['roi'][0]['crosstalk'],
              crosstalks[0], decimal=10)

    np_almost(roi_set_dict['roi'][1]['crosstalk'],
              crosstalks[1], decimal=10)

    np_almost(roi_set_dict['neuropil'][0]['signal'],
              signals[2], decimal=10)

    np_almost(roi_set_dict['neuropil'][1]['signal'],
              signals[3], decimal=10)

    np_almost(roi_set_dict['neuropil'][0]['crosstalk'],
              crosstalks[2], decimal=10)

    np_almost(roi_set_dict['neuropil'][1]['crosstalk'],
              crosstalks[3], decimal=10)
示例#4
0
def test_clean_negative_traces():

    rng = np.random.RandomState(88123)
    input_data = dc_types.ROISetDict()
    trace = rng.normal(7.0, 0.2, size=1000)
    trace[77] = 15.0
    trace[99] = 1.0
    roi = dc_types.ROIChannels()
    roi['signal'] = trace
    roi['crosstalk'] = rng.random_sample(1000)
    input_data['roi'][0] = roi

    trace = rng.normal(11.0, 0.2, size=1000)
    trace[44] = 22.0
    trace[88] = 1.0
    roi = dc_types.ROIChannels()
    roi['signal'] = trace
    roi['crosstalk'] = rng.random_sample(1000)
    input_data['neuropil'][0] = roi

    # make sure traces with NaNs are left untouched
    nan_trace = rng.normal(7.0, 0.2, size=1000)
    nan_trace[11] = 17.0
    nan_trace[33] = -1.0
    nan_trace[44] = np.NaN
    roi = dc_types.ROIChannels()
    roi['signal'] = nan_trace
    roi['crosstalk'] = rng.random_sample(1000)
    input_data['roi'][1] = roi

    cleaned = decrosstalk.clean_negative_traces(input_data)
    assert cleaned['roi'][0]['signal'].min() > 6.0
    assert cleaned['roi'][0]['signal'].min() < 7.0
    assert abs(cleaned['roi'][0]['signal'][77] - 15.0) < 0.001
    assert not np.isnan(cleaned['roi'][0]['signal']).any()

    np.testing.assert_array_equal(cleaned['roi'][1]['signal'], nan_trace)

    assert cleaned['neuropil'][0]['signal'].min() > 10.0
    assert cleaned['neuropil'][0]['signal'].min() < 11.0
    assert abs(cleaned['neuropil'][0]['signal'][44] - 22.0) < 0.001
示例#5
0
def get_raw_traces(signal_plane: DecrosstalkingOphysPlane,
                   ct_plane: DecrosstalkingOphysPlane) -> dc_types.ROISetDict:
    """
    Get the raw signal and crosstalk traces comparing
    this plane to another plane

    Parameters
    ----------
    signal_plane -- an instance of DecrosstalkingOphysPlane which will
    be taken as the source of the signal

    ct_plane -- another instance of DecrosstalkingOphysPlane which will
    be taken as the source of the crosstalk

    Returns
    -------
    An decrosstalk_types.ROISetDict containing the raw trace data for
    the ROIs in the signal plane.
    """

    signal_traces = signal_plane.movie.get_trace(signal_plane.roi_list)
    crosstalk_traces = ct_plane.movie.get_trace(signal_plane.roi_list)

    output = dc_types.ROISetDict()

    for roi_id in signal_traces['roi'].keys():
        _roi = dc_types.ROIChannels()
        _neuropil = dc_types.ROIChannels()

        _roi['signal'] = signal_traces['roi'][roi_id]['signal']
        _roi['crosstalk'] = crosstalk_traces['roi'][roi_id]['signal']
        output['roi'][roi_id] = _roi

        _neuropil['signal'] = signal_traces['neuropil'][roi_id]['signal']
        _neuropil['crosstalk'] = crosstalk_traces['neuropil'][roi_id]['signal']
        output['neuropil'][roi_id] = _neuropil

    return output
示例#6
0
def run_decrosstalk(signal_plane: DecrosstalkingOphysPlane,
                    ct_plane: DecrosstalkingOphysPlane,
                    cache_dir: str = None, clobber: bool = False,
                    new_style_output: bool = False) -> Tuple[dict, dc_types.ROISetDict]:  # noqa: E501
    """
    Actually run the decrosstalking pipeline, comparing two
    DecrosstalkingOphysPlanes

    Parameters
    ----------
    signal_plane -- the DecrosstalkingOphysPlane characterizing the
                    signal plane

    ct_plane -- the DecrosstalkingOphysPlane characterizing the crosstalk plane

    cache_dir -- the directory in which to write the QC output
    (if None, the output does not get written)

    clobber -- a boolean indicating whether or not to overwrite
    pre-existing output files (default: False)

    new_style_output -- a boolean (default: False)

    Returns
    -------
    roi_flags -- a dict listing the ROI IDs of ROIs that were
    ruled invalid for different reasons, namely:

        'decrosstalk_ghost' -- ROIs that are ghosts

        'decrosstalk_invalid_raw' -- ROIs with invalid
                                     raw traces

        'decrosstalk_invalid_raw_active' -- ROIs with invalid
                                            raw active traces

        'decrosstalk_invalid_unmixed' -- ROIs with invalid
                                         unmixed traces

        'decrosstalk_invalid_unmixed_active' -- ROIs with invalid
                                                unmixed active traces

    unmixed_traces -- a decrosstalk_types.RoiSetDict containing
                      the unmixed trace data for the ROIs
    """

    # kwargs for output classes that write QC output
    output_kwargs = {'cache_dir': cache_dir,
                     'signal_plane': signal_plane,
                     'crosstalk_plane': ct_plane,
                     'clobber': clobber}

    # writer class that will be used to write dict of ROIs
    # that have been invalidated for various reasons by
    # this module
    flag_writer_class = io_utils.InvalidFlagWriter

    roi_flags: Dict[str, List[int]] = {}

    ghost_key = 'decrosstalk_ghost'
    raw_key = 'decrosstalk_invalid_raw'
    raw_active_key = 'decrosstalk_invalid_raw_active'
    unmixed_key = 'decrosstalk_invalid_unmixed'
    unmixed_active_key = 'decrosstalk_invalid_unmixed_active'

    roi_flags[ghost_key] = []
    roi_flags[raw_key] = []
    roi_flags[unmixed_key] = []
    roi_flags[raw_active_key] = []
    roi_flags[unmixed_active_key] = []

    # If there are no ROIs in the signal plane,
    # just return a set of empty outputs
    if len(signal_plane.roi_list) == 0:
        return roi_flags, dc_types.ROISetDict()

    ###############################
    # extract raw traces

    raw_traces = get_raw_traces(signal_plane, ct_plane)

    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.RawH5Writer
        else:
            writer_class = io_utils.RawH5WriterOld
        writer = writer_class(data=raw_traces, **output_kwargs)
        writer.run()
        del writer
        del writer_class

    raw_trace_validation = d_utils.validate_traces(raw_traces)
    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.ValidJsonWriter
        else:
            writer_class = io_utils.ValidJsonWriterOld
        writer = writer_class(data=raw_trace_validation, **output_kwargs)
        writer.run()
        del writer
        del writer_class

    # remove invalid raw traces
    invalid_raw_trace = []
    for roi_id in raw_trace_validation:
        if not raw_trace_validation[roi_id]:
            invalid_raw_trace.append(roi_id)
            raw_traces['roi'].pop(roi_id)
            raw_traces['neuropil'].pop(roi_id)
    roi_flags[raw_key] += invalid_raw_trace

    if len(raw_traces['roi']) == 0:
        msg = 'No raw traces were valid when applying '
        msg += 'decrosstalk to ophys_experiment_id: '
        msg += '%d (%d)' % (signal_plane.experiment_id,
                            ct_plane.experiment_id)
        logger.error(msg)

        writer = flag_writer_class(data=roi_flags,
                                   **output_kwargs)
        writer.run()
        return roi_flags, dc_types.ROISetDict()

    #########################################
    # detect activity in raw traces

    raw_trace_events = get_trace_events(raw_traces['roi'])

    # For each ROI, calculate a random seed based on the flux
    # in all timestamps *not* chosen as events (presumably,
    # random noise)
    roi_to_seed = {}
    two_to_32 = 2**32
    for roi_id in raw_trace_events.keys():
        flux_mask = np.ones(len(raw_traces['roi'][roi_id]['signal']),
                            dtype=bool)
        if len(raw_trace_events[roi_id]['signal']['events']) > 0:
            flux_mask[raw_trace_events[roi_id]['signal']['events']] = False
        _flux = np.abs(raw_traces['roi'][roi_id]['signal'][flux_mask])
        flux_sum = np.round(_flux.sum()).astype(int)
        roi_to_seed[roi_id] = flux_sum % two_to_32

    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.RawATH5Writer
        else:
            writer_class = io_utils.RawATH5WriterOld
        writer = writer_class(data=raw_trace_events, **output_kwargs)
        writer.run()
        del writer
        del writer_class

    raw_trace_crosstalk_ratio = get_crosstalk_data(raw_traces['roi'],
                                                   raw_trace_events)

    # remove ROIs with invalid active raw traces
    roi_id_list = list(raw_trace_events.keys())
    for roi_id in roi_id_list:
        signal = raw_trace_events[roi_id]['signal']['trace']
        if len(signal) == 0 or np.isnan(signal).any():
            roi_flags[raw_active_key].append(roi_id)
            raw_trace_events.pop(roi_id)
            raw_traces['roi'].pop(roi_id)
            raw_traces['neuropil'].pop(roi_id)

    del raw_trace_events

    # if there was no activity in the raw traces, return an
    # empty ROISetDict because none of the ROIs were valid
    if len(raw_traces['roi']) == 0:
        return roi_flags, dc_types.ROISetDict()

    ###########################################################
    # use Independent Component Analysis to separate out signal
    # and crosstalk

    (ica_converged,
     unmixed_traces) = unmix_all_ROIs(raw_traces,
                                      roi_to_seed)

    # clip dips in signal channel
    clipped_traces = clean_negative_traces(unmixed_traces)

    # save old signal to 'unclipped_signal'
    # save new signal to 'signal'
    for obj in ('roi', 'neuropil'):
        for roi_id in unmixed_traces[obj].keys():
            s = unmixed_traces[obj][roi_id]['signal']
            unmixed_traces[obj][roi_id]['unclipped_signal'] = s
            s = clipped_traces[obj][roi_id]['signal']
            unmixed_traces[obj][roi_id]['signal'] = s

    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.OutH5Writer
        else:
            writer_class = io_utils.OutH5WriterOld
        writer = writer_class(data=unmixed_traces, **output_kwargs)
        writer.run()
        del writer
        del writer_class

    if not ica_converged:
        for roi_id in unmixed_traces['roi'].keys():
            roi_flags[unmixed_key].append(roi_id)

        msg = 'ICA did not converge for any ROIs when '
        msg += 'applying decrosstalk to ophys_experiment_id: '
        msg += '%d (%d)' % (signal_plane.experiment_id,
                            ct_plane.experiment_id)
        logger.error(msg)
        writer = flag_writer_class(data=roi_flags,
                                   **output_kwargs)
        writer.run()
        return roi_flags, unmixed_traces

    unmixed_trace_validation = d_utils.validate_traces(unmixed_traces)
    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.OutValidJsonWriter
        else:
            writer_class = io_utils.OutValidJsonWriterOld
        writer = writer_class(data=unmixed_trace_validation,
                              **output_kwargs)
        writer.run()
        del writer
        del writer_class

    # remove invalid unmixed traces
    invalid_unmixed_trace = []
    for roi_id in unmixed_trace_validation:
        if not unmixed_trace_validation[roi_id]:
            invalid_unmixed_trace.append(roi_id)
            unmixed_traces['roi'].pop(roi_id)
            unmixed_traces['neuropil'].pop(roi_id)
    roi_flags[unmixed_key] += invalid_unmixed_trace

    if len(unmixed_traces['roi']) == 0:
        msg = 'No unmixed traces were valid when applying '
        msg += 'decrosstalk to ophys_experiment_id: '
        msg += '%d (%d)' % (signal_plane.experiment_id,
                            ct_plane.experiment_id)
        logger.error(msg)
        writer = flag_writer_class(data=roi_flags,
                                   **output_kwargs)
        writer.run()
        return roi_flags, unmixed_traces

    ###################################################
    # Detect activity in unmixed traces

    unmixed_trace_events = get_trace_events(unmixed_traces['roi'])

    # Sometimes, unmixed_trace_events will return an array of NaNs.
    # Until we can debug that behavior, we will log those errors,
    # store the relevaten ROIs as decrosstalk_invalid_unmixed_trace,
    # and cull those ROIs from the data

    invalid_active_trace: Dict[str, List[int]] = {}
    invalid_active_trace['signal'] = []
    invalid_active_trace['crosstalk'] = []
    active_trace_had_NaNs = False
    for roi_id in unmixed_trace_events.keys():
        local_traces = unmixed_trace_events[roi_id]
        for channel in ('signal', 'crosstalk'):
            is_valid = True
            if len(local_traces[channel]['trace']) == 0:
                is_valid = False
            else:
                nan_trace = np.isnan(local_traces[channel]['trace']).any()
                nan_events = np.isnan(local_traces[channel]['events']).any()
                if nan_trace or nan_events:
                    is_valid = False
                    active_trace_had_NaNs = True

            if not is_valid:
                invalid_active_trace[channel].append(roi_id)

    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.OutATH5Writer
        else:
            writer_class = io_utils.OutATH5WriterOld
        writer = writer_class(data=unmixed_trace_events,
                              **output_kwargs)
        writer.run()
        del writer
        del writer_class

    if active_trace_had_NaNs:
        msg = 'ophys_experiment_id: %d (%d) ' % (signal_plane.experiment_id,
                                                 ct_plane.experiment_id)
        msg += 'had ROIs with active event channels that contained NaNs'
        logger.error(msg)

    # remove ROIs with empty or NaN active trace signal channels
    # from the data being processed
    for roi_id in invalid_active_trace['signal']:
        roi_flags[unmixed_active_key].append(roi_id)
        unmixed_trace_events.pop(roi_id)
        unmixed_traces['roi'].pop(roi_id)
        unmixed_traces['neuropil'].pop(roi_id)

    if cache_dir is not None:
        writer = io_utils.InvalidATJsonWriter(data=invalid_active_trace,
                                              **output_kwargs)
        writer.run()
        del writer

    unmixed_ct_ratio = get_crosstalk_data(unmixed_traces['roi'],
                                          unmixed_trace_events)

    ########################################################
    # For each ROI, assess whether or not it is a "ghost"
    # (i.e. whether any of its activity is due to the signal,
    # independent of the crosstalk; if not, it is a ghost)

    independent_events = {}
    ghost_roi_id = []
    for roi_id in unmixed_trace_events.keys():
        signal = unmixed_trace_events[roi_id]['signal']
        crosstalk = unmixed_trace_events[roi_id]['crosstalk']

        (is_a_cell,
         ind_events) = d_utils.validate_cell_crosstalk(signal, crosstalk)

        local = {'is_a_cell': is_a_cell,
                 'independent_events': ind_events}

        independent_events[roi_id] = local
        if not is_a_cell:
            ghost_roi_id.append(roi_id)
    roi_flags[ghost_key] += ghost_roi_id

    if cache_dir is not None:
        if new_style_output:
            writer_class = io_utils.ValidCTH5Writer
        else:
            writer_class = io_utils.ValidCTH5WriterOld
        writer = writer_class(data=independent_events,
                              **output_kwargs)
        writer.run()
        del writer
        del writer_class

        crosstalk_ratio = {}
        for roi_id in unmixed_ct_ratio:
            _out = {'raw': raw_trace_crosstalk_ratio[roi_id],
                    'unmixed': unmixed_ct_ratio[roi_id]}
            crosstalk_ratio[roi_id] = _out

        if new_style_output:
            writer_class = io_utils.CrosstalkJsonWriter
        else:
            writer_class = io_utils.CrosstalkJsonWriterOld
        writer = writer_class(data=crosstalk_ratio, **output_kwargs)
        writer.run()
        del writer
        del writer_class

    writer = flag_writer_class(data=roi_flags,
                               **output_kwargs)
    writer.run()

    return roi_flags, unmixed_traces
示例#7
0
def clean_negative_traces(trace_dict: dc_types.ROISetDict) -> dc_types.ROISetDict:  # noqa: E501
    """
    Parameters
    ----------
    trace_dict -- a decrosstalk_types.ROISetDict containing the traces
                   that need to be clipped

    Returns
    -------
    A decrosstalk_types.ROISetDict containing the clipped traces
    """
    active_trace_dict = get_trace_events(trace_dict['roi'])
    output_trace_dict = dc_types.ROISetDict()
    roi_id_list = trace_dict['roi'].keys()
    roi_id_list.sort()
    for roi_id in roi_id_list:
        n_t = len(trace_dict['roi'][roi_id]['signal'])

        # try to select only inactive timesteps;
        # if there are none, select all timesteps
        # (that would be an unexpected edge case)
        mask = np.ones(n_t, dtype=bool)
        mask[active_trace_dict[roi_id]['signal']['events']] = False
        if mask.sum() == 0:
            mask[:] = True

        for obj in ('roi', 'neuropil'):

            # because we are running this step before culling
            # traces that failed ICA, sometimes, ROIs without
            # valid neuropil traces will get through
            if roi_id not in trace_dict[obj]:
                continue

            # again: there may be traces with NaNs; these are
            # going to get failed, anyway; just ignore them
            # for now
            if np.isnan(trace_dict[obj][roi_id]['signal']).any():
                dummy = dc_types.ROIChannels()
                dummy['signal'] = trace_dict[obj][roi_id]['signal']
                output_trace_dict[obj][roi_id] = dummy
                continue

            (mean,
             std) = _centered_rolling_mean(trace_dict[obj][roi_id]['signal'],
                                           mask,
                                           1980)

            threshold = mean-2.0*std

            if (threshold < 0.0).any():
                msg = 'The unmixed "%s" trace for roi %d ' % (obj, roi_id)
                msg += 'contained negative flux values'
                logger.warning(msg)

            # if there were no valid timesteps when calculating the rolling
            # mean, set the threshold to a very negative value
            threshold = np.where(np.logical_not(np.isnan(threshold)),
                                 threshold,
                                 -999.0)

            if np.isnan(threshold).any():
                raise RuntimeError("There were NaNs in "
                                   "clean_negative_traces.threshold")

            # clip the trace at threshold
            trace = trace_dict[obj][roi_id]['signal']
            trace = np.where(trace > threshold, trace, threshold)
            channel = dc_types.ROIChannels()
            channel['signal'] = trace
            output_trace_dict[obj][roi_id] = channel

    return output_trace_dict
示例#8
0
def unmix_all_ROIs(raw_roi_traces: dc_types.ROISetDict,
                   seed_lookup: Dict[int, int]) -> Tuple[bool, dc_types.ROISetDict]:  # noqa: E501
    """
    Unmix all of the ROIs in this DecrosstalkingOphysPlane.

    Parameters
    ----------
    raw_roi_traces -- a decrosstalk_types.ROISetDict containing the
                      raw trace data for all the ROIs to be unmixed

    seed_lookup -- a dict that maps roi_id to a seed for np.RandomState

    Returns
    -------
    A boolean indicating whether or not we were able to successfully
    unmix the ROIs in this input ROISetDict

    A decrosstalk_types.ROISetDict containing the unmixed trace data for the
    ROIs.

    Notes
    -----
    This method makes two attempts at decrosstalking each ROI using
    Independent Component Analysis. On the first attempt, each ROI
    (not neuropil) is decrosstalked as an independent entity. It is
    possible that this process will not converge for any given ROI.

    On the second attempt, an average demixing matrix is constructed
    from the demixing matrices of those ROIs for which the first attempt
    did converge. This average demixing matrix is to decrosstalk any
    ROIs for which the first attempt did not converge.

    Neuropils are then decrosstalked using the demixing matrix
    corresponding to their associated ROI (whether the ROI-specific
    demixing matrix or, in the case that the first attempt did not
    converge, the average demixing matrix).

    If the first attempt does not converge for any ROIs, there are no
    demixing matrices from which to construct an average demixing
    matrix and it is impossible to salvage any of the ROIs. In this case,
    the boolean that is the first returned object of this method will
    be set to False and the output ROISetDict will be emtpy.

    If decrosstalking converged for any ROIs (and an average demixing
    matrix is thus possible), that boolean will be set to True.

    The unmixed traces, however they were achieved, will be saved in
    the output ROISetDict.
    """

    output = dc_types.ROISetDict()

    # first pass naively unmixing ROIs with ICA
    for roi_id in raw_roi_traces['roi'].keys():

        unmixed_roi = unmix_ROI(raw_roi_traces['roi'][roi_id],
                                seed=seed_lookup[roi_id],
                                iters=10)

        if not unmixed_roi['use_avg_mixing_matrix']:
            _out = unmixed_roi
        else:
            _out = dc_types.ROIChannels()
            _out['use_avg_mixing_matrix'] = True
            for k in unmixed_roi.keys():
                if k == 'use_avg_mixing_matrix':
                    continue
                _out['poorly_converged_%s' % k] = unmixed_roi[k]
                _out[k] = np.NaN*np.zeros(unmixed_roi[k].shape, dtype=float)
        output['roi'][roi_id] = _out

    # calculate avg mixing matrix from successful iterations
    just_roi = output['roi']
    alpha_arr = np.array([min(just_roi[roi_id]['mixing_matrix'][0, 0],
                              just_roi[roi_id]['mixing_matrix'][0, 1])
                          for roi_id in just_roi.keys()
                          if not just_roi[roi_id]['use_avg_mixing_matrix']])
    beta_arr = np.array([min(just_roi[roi_id]['mixing_matrix'][1, 0],
                             just_roi[roi_id]['mixing_matrix'][1, 1])
                         for roi_id in just_roi.keys()
                         if not just_roi[roi_id]['use_avg_mixing_matrix']])

    assert alpha_arr.shape == beta_arr.shape
    if len(alpha_arr) == 0:
        return False, output

    mean_alpha = alpha_arr.mean()
    mean_beta = beta_arr.mean()
    mean_mixing_matrix = np.zeros((2, 2), dtype=float)
    mean_mixing_matrix[0, 0] = 1.0-mean_alpha
    mean_mixing_matrix[0, 1] = mean_alpha
    mean_mixing_matrix[1, 0] = mean_beta
    mean_mixing_matrix[1, 1] = 1.0-mean_beta
    inv_mean_mixing_matrix = np.linalg.inv(mean_mixing_matrix)

    for roi_id in raw_roi_traces['roi'].keys():
        inv_mixing_matrix = None
        mixing_matrix = None
        if not output['roi'][roi_id]['use_avg_mixing_matrix']:
            mixing_matrix = output['roi'][roi_id]['mixing_matrix']
            inv_mixing_matrix = np.linalg.inv(mixing_matrix)
        else:
            mixing_matrix = mean_mixing_matrix
            inv_mixing_matrix = inv_mean_mixing_matrix

            # assign missing outputs to ROIs that failed to converge
            output['roi'][roi_id]['mixing_matrix'] = mixing_matrix
            _roi_traces = raw_roi_traces['roi'][roi_id]
            unmixed_signals = np.dot(inv_mixing_matrix,
                                     np.array([_roi_traces['signal'],
                                               _roi_traces['crosstalk']]))
            output['roi'][roi_id]['signal'] = unmixed_signals[0, :]
            output['roi'][roi_id]['crosstalk'] = unmixed_signals[1, :]

        # assign outputs to 'neuropils'
        _np_traces = raw_roi_traces['neuropil'][roi_id]
        unmixed_signals = np.dot(inv_mixing_matrix,
                                 np.array([_np_traces['signal'],
                                           _np_traces['crosstalk']]))

        neuropil = dc_types.ROIChannels()
        neuropil['signal'] = unmixed_signals[0, :]
        neuropil['crosstalk'] = unmixed_signals[1, :]
        output['neuropil'][roi_id] = neuropil

    return True, output