-
Notifications
You must be signed in to change notification settings - Fork 95
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
[REF] Modularize metric calculation #436
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
57f95f1
Reorganize metrics.
tsalo 2a27d7d
Merge remote-tracking branch 'ME-ICA/master' into modularize-metrics
tsalo 7e136e8
Some more work on organizing metrics.
tsalo aae8a94
Add signal_minus_noise_z metric.
tsalo cca8d55
Variable name change.
tsalo 2a78cb9
Move comptable.
tsalo 02c377d
Move T2* cap into decay function.
tsalo 66b8726
Merge remote-tracking branch 'ME-ICA/master' into modularize-metrics
tsalo a175191
Split up metric files.
tsalo 870207e
Adjust cluster-extent thresholding to match across maps.
tsalo 6793694
Partially address review.
tsalo 21db69a
Make DICE broadcastable.
tsalo 9a968e1
Clean up signal-noise metrics and fix compute_countnoise.
tsalo c49ac58
Simplify calculate_z_maps.
tsalo 710fe1b
Fix dice (thanks @rmarkello)
tsalo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
""" | ||
Misc. utils for metric calculation. | ||
""" | ||
import numpy as np | ||
from scipy import stats | ||
|
||
from tedana.stats import computefeats2 | ||
|
||
|
||
def determine_signs(weights, axis=0): | ||
""" | ||
Determine component-wise optimal signs using voxel-wise parameter estimates. | ||
|
||
Parameters | ||
---------- | ||
weights : (S x C) array_like | ||
Parameter estimates for optimally combined data against the mixing | ||
matrix. | ||
|
||
Returns | ||
------- | ||
signs : (C) array_like | ||
Array of 1 and -1 values corresponding to the appropriate flips for the | ||
mixing matrix's component time series. | ||
""" | ||
# compute skews to determine signs based on unnormalized weights, | ||
signs = stats.skew(weights, axis=axis) | ||
signs /= np.abs(signs) | ||
return signs | ||
|
||
|
||
def flip_components(*args, signs): | ||
# correct mixing & weights signs based on spatial distribution tails | ||
return [arg * signs for arg in args] | ||
|
||
|
||
def sort_df(df, by='kappa', ascending=False): | ||
""" | ||
Sort DataFrame and get index. | ||
""" | ||
# Order of kwargs is preserved at 3.6+ | ||
argsort = df[by].argsort() | ||
if not ascending: | ||
argsort = argsort[::-1] | ||
df = df.loc[argsort].reset_index(drop=True) | ||
return df, argsort | ||
|
||
|
||
def apply_sort(*args, sort_idx, axis=0): | ||
""" | ||
Apply a sorting index. | ||
""" | ||
for arg in args: | ||
assert arg.shape[axis] == len(sort_idx) | ||
return [np.take(arg, sort_idx, axis=axis) for arg in args] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
""" | ||
Collect metrics. | ||
""" | ||
import logging | ||
|
||
import numpy as np | ||
import pandas as pd | ||
|
||
from . import dependence | ||
from ._utils import determine_signs, flip_components, sort_df, apply_sort | ||
|
||
|
||
LGR = logging.getLogger(__name__) | ||
RepLGR = logging.getLogger('REPORT') | ||
RefLGR = logging.getLogger('REFERENCES') | ||
|
||
|
||
def generate_metrics(comptable, data_cat, data_optcom, mixing, mask, tes, ref_img, mixing_z=None, | ||
metrics=None, sort_by='kappa', ascending=False): | ||
""" | ||
Fit TE-dependence and -independence models to components. | ||
|
||
Parameters | ||
---------- | ||
data_cat : (S x E x T) array_like | ||
Input data, where `S` is samples, `E` is echos, and `T` is time | ||
data_optcom : (S x T) array_like | ||
Optimally combined data | ||
mixing : (T x C) array_like | ||
Mixing matrix for converting input data to component space, where `C` | ||
is components and `T` is the same as in `data_cat` | ||
mask : img_like | ||
Mask | ||
tes : list | ||
List of echo times associated with `data_cat`, in milliseconds | ||
ref_img : str or img_like | ||
Reference image to dictate how outputs are saved to disk | ||
reindex : bool, optional | ||
Whether to sort components in descending order by Kappa. Default: False | ||
mixing_z : (T x C) array_like, optional | ||
Z-scored mixing matrix. Default: None | ||
algorithm : {'kundu_v2', 'kundu_v3', None}, optional | ||
Decision tree to be applied to metrics. Determines which maps will be | ||
generated and stored in seldict. Default: None | ||
label : :obj:`str` or None, optional | ||
Prefix to apply to generated files. Default is None. | ||
out_dir : :obj:`str`, optional | ||
Output directory for generated files. Default is current working | ||
directory. | ||
verbose : :obj:`bool`, optional | ||
Whether or not to generate additional files. Default is False. | ||
|
||
Returns | ||
------- | ||
comptable : (C x X) :obj:`pandas.DataFrame` | ||
Component metric table. One row for each component, with a column for | ||
each metric. The index is the component number. | ||
seldict : :obj:`dict` or None | ||
Dictionary containing component-specific metric maps to be used for | ||
component selection. If `algorithm` is None, then seldict will be None as | ||
well. | ||
betas : :obj:`numpy.ndarray` | ||
mmix_new : :obj:`numpy.ndarray` | ||
""" | ||
if metrics is None: | ||
metrics = [] | ||
RepLGR.info('The following metrics were calculated: {}.'.format(', '.join(metrics))) | ||
|
||
if not (data_cat.shape[0] == data_optcom.shape[0] == mask.sum()): | ||
raise ValueError('First dimensions (number of samples) of data_cat ({0}), ' | ||
'data_optcom ({1}), and mask ({2}) do not ' | ||
'match'.format(data_cat.shape[0], data_optcom.shape[0], | ||
mask.shape[0])) | ||
elif data_cat.shape[1] != len(tes): | ||
raise ValueError('Second dimension of data_cat ({0}) does not match ' | ||
'number of echoes provided (tes; ' | ||
'{1})'.format(data_cat.shape[1], len(tes))) | ||
elif not (data_cat.shape[2] == data_optcom.shape[1] == mixing.shape[0]): | ||
raise ValueError('Number of volumes in data_cat ({0}), ' | ||
'data_optcom ({1}), and mixing ({2}) do not ' | ||
'match.'.format(data_cat.shape[2], data_optcom.shape[1], mixing.shape[0])) | ||
|
||
mixing = mixing.copy() | ||
comptable = pd.DataFrame(index=np.arange(n_components, dtype=int)) | ||
|
||
# Metric maps | ||
weights = dependence.calculate_weights(data_optcom, mixing_z) | ||
signs = determine_signs(weights, axis=0) | ||
weights, mixing = flip_components(weights, mixing, signs=signs) | ||
optcom_betas = dependence.calculate_betas(data_optcom, mixing) | ||
PSC = dependence.calculate_psc(data_optcom, optcom_betas) | ||
|
||
# compute betas and means over TEs for TE-dependence analysis | ||
Z_maps = dependence.calculate_z_maps(weights) | ||
F_T2_maps, F_S0_maps = dependence.calculate_f_maps(mixing, data_cat, tes, Z_maps) | ||
|
||
(Z_clmaps, F_T2_clmaps, F_S0_clmaps, | ||
Br_T2_clmaps, Br_S0_clmaps) = dependence.spatial_cluster( | ||
F_T2_maps, F_S0_maps, Z_maps, optcom_betas, mask, n_echos) | ||
|
||
# Dependence metrics | ||
if any([v in metrics for v in ['kappa', 'rho']]): | ||
comptable['kappa'], comptable['rho'] = dependence.calculate_dependence_metrics( | ||
F_T2_maps, F_S0_maps, Z_maps) | ||
|
||
# Generic metrics | ||
if 'variance explained' in metrics: | ||
comptable['variance explained'] = dependence.calculate_varex(optcom_betas) | ||
|
||
if 'normalized variance explained' in metrics: | ||
comptable['normalized variance explained'] = dependence.calculate_varex_norm(weights) | ||
|
||
# Spatial metrics | ||
if 'dice_FT2' in metrics: | ||
comptable['dice_FT2'] = dependence.compute_dice(Br_T2_clmaps, F_T2_clmaps, axis=0) | ||
|
||
if 'dice_FS0' in metrics: | ||
comptable['dice_FS0'] = dependence.compute_dice(Br_S0_clmaps, F_S0_clmaps, axis=0) | ||
|
||
if any([v in metrics for v in ['signal-noise_t', 'signal-noise_p']]): | ||
(comptable['signal-noise_t'], | ||
comptable['signal-noise_p']) = dependence.compute_signal_minus_noise_t( | ||
Z_maps, Z_clmaps, F_T2_maps) | ||
|
||
if 'countnoise' in metrics: | ||
comptable['countnoise'] = dependence.compute_countnoise(Z_maps, Z_clmaps) | ||
|
||
if 'countsigFT2' in metrics: | ||
comptable['countsigFT2'] = dependence.compute_countsignal(F_T2_clmaps) | ||
|
||
if 'countsigFS0' in metrics: | ||
comptable['countsigFS0'] = dependence.compute_countsignal(F_S0_clmaps) | ||
|
||
if 'd_table_score' in metrics: | ||
comptable['d_table_score'] = dependence.generate_decision_table_score( | ||
comptable['kappa'], comptable['dice_FT2'], | ||
comptable['signal_minus_noise_t'], comptable['countnoise'], | ||
comptable['countsigFT2']) | ||
|
||
# TODO: move sorting out of this function and only return comptable | ||
comptable, sort_idx = sort_df(comptable, by='kappa', ascending=ascending) | ||
mixing = apply_sort(mixing, sort_idx=sort_idx, axis=1) | ||
return comptable, mixing |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this copy is unnecessary, here, since the next time it's used is providing
mixing
toflip_components ()
which will trigger a copy.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we should add a check for
if mixing_z is None
and assignmixing
tomixing_z
.