-
Notifications
You must be signed in to change notification settings - Fork 584
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
[MRG] Fix: NaN problem in NiftiMapsMasker #1271
[MRG] Fix: NaN problem in NiftiMapsMasker #1271
Conversation
@@ -835,6 +836,10 @@ def clean_img(imgs, sessions=None, detrend=True, standardize=True, | |||
t_r: float, optional | |||
Repetition time, in second (sampling period). | |||
|
|||
ensure_finite: bool, optional | |||
If True, the non-infinite values (NANs and infs) found in the images |
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.
the infinite values (NANs and infs)
@darya-chyzhyk it would be great if you could look review the PR to see if NaN problem will be solved. Thanks |
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.
We should discuss the case of inifinity values before merging this PR. All NaN-related things are OK to me.
@@ -14,7 +14,7 @@ | |||
from .compat import _basestring, get_affine | |||
|
|||
|
|||
def _safe_get_data(img): | |||
def _safe_get_data(img, ensure_finite=False): |
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.
Please add the description of the parameter in the docstring.
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.
Sure
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.
Its done.
@@ -835,6 +836,10 @@ def clean_img(imgs, sessions=None, detrend=True, standardize=True, | |||
t_r: float, optional | |||
Repetition time, in second (sampling period). | |||
|
|||
ensure_finite: bool, optional | |||
If True, the infinite values (NANs and infs) found in the images | |||
will be replaced by zeros. |
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.
First, it is not about "infinite" values but "non-finite" values (because NaN is not an infinity). Second, you rely on np.nan_to_num
that set NaNs to 0, Inf to a very big value and NegInf to a very small value. So your doc is not correct.
Although we agree that NaN = 0 in this case, I don't know what we should do with infinities... Maybe raise an error?
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.
Although we agree that NaN = 0 in this case, I don't know what we should do with infinities... Maybe raise an error?
Rather than raising an error. We do what we have done for NaN case. May be convert them equivalently to zeros or less risk value. What do you say ? Can you say we occur infinity errors too more often ?
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.
First, it is not about "infinite" values but "non-finite" values (because NaN is not an infinity). Second, you rely on np.nan_to_num that set NaNs to 0, Inf to a very big value and NegInf to a very small value. So your doc is not correct.
I followed the same documentation which already written and exists for ensure_finite
.
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.
what are we decided upon ? what do we do with infinite values ?
@@ -106,6 +106,24 @@ def test_nifti_labels_masker(): | |||
get_affine(fmri11_img)) | |||
|
|||
|
|||
def test_nifti_labels_masker_with_nans(): |
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.
You don't test infinities, which tells me that this PR is more about NaNs than infinities.
b9e4c97
to
cab4b93
Compare
@aabadie Any hints how to fix this AppVeyor failure ? |
@KamalakerDadi, I don't know. This seems weird and here for a long time in nilearn. Googling a bit I found other projects complaining about PyQt installation on appveyor. Have you tried this kind of appveyor configuration ? |
@aabadie Thanks, I haven't but I would try. Not immediately but later. |
Can I have reviews please ? |
@@ -782,7 +780,8 @@ def math_img(formula, **imgs): | |||
|
|||
|
|||
def clean_img(imgs, sessions=None, detrend=True, standardize=True, | |||
confounds=None, low_pass=None, high_pass=None, t_r=2.5): | |||
confounds=None, low_pass=None, high_pass=None, t_r=2.5, | |||
ensure_finite=False): |
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.
Why does it default to False ? (just asking, I have no strong opinion on the question).
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 agreement was not fill zeros automatically by default. Full discussion is in #282
# if infinity | ||
data[:, 5, 5] = np.inf | ||
nan_img = nibabel.Nifti1Image(data, np.eye(4)) | ||
clean_im = image.clean_img(nan_img, ensure_finite=True) |
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.
How does this behave if ensure_fininte==False ?
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 image will still have non-finite values and might break somewhere in the analysis.
With the same test code it behaves like this:
nilearn/signal.py:146: RuntimeWarning: invalid value encountered in subtract
signals -= np.mean(signals, axis=0)
nilearn/signal.py:63: RuntimeWarning: invalid value encountered in less
std[std < np.finfo(np.float).eps] = 1. # avoid numerical problems
|
||
masker = NiftiLabelsMasker(labels_img, mask_img=mask_img) | ||
sig = masker.fit_transform(fmri_img) | ||
assert_equal(sig.shape, (length, n_regions)) |
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.
You don't test whether sig
is finite ?
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.
Done
mask_img = nibabel.Nifti1Image(mask_data, np.eye(4)) | ||
|
||
masker = NiftiMapsMasker(maps_img, mask_img=mask_img) | ||
sig = masker.fit_transform(fmri_img) |
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.
Idem
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.
Its done below.
maps_mask = _utils.as_ndarray(maps_mask, dtype=np.bool) | ||
else: | ||
maps_mask = np.ones(maps_data.shape[:3], dtype=np.bool) | ||
labels = np.arange(maps_data.shape[-1], dtype=np.int) | ||
|
||
data = imgs.get_data() | ||
data = _safe_get_data(imgs) |
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.
ensure_finite=True (?)
The way I am trying to fix this issue in particular NaN or infinite values in NiftiMapsMasker or NiftiLabelsMasker is by always keeping What I think would be instead of keeping This idea came up following discussion in issue #282 |
- Add "ensure_finite" to ilearn.image.clean_img - Deal automatically with NaNs in NiftiMapsMasker
cab4b93
to
5af0dcc
Compare
Any improvements or reviews on this please ? |
if np.any(np.isnan(region_signals)): | ||
ensure_finite = True | ||
else: | ||
ensure_finite = False |
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 that we should always have ensure_finite=True here: it costs as much to check if there is a problem as to fix it.
I'll push these changes directly in your branch, to save time.
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.
Actually, I've just realized that you are doing the ensure_finite on the signals, while it should really be done on the maps. I'll change that.
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.
Ok. I think we can remove this whole block ? Since signals that comes out are already replaced by zeros.
Yup. I am doing it right now. I'll push this to your branch
|
Done. Beware that I pushed changes to your branch. You need to pull. |
This PR also has NiftiLabelsMasker related changes. Should I have been specific to Maps masker only ? |
Indeed. The docstring was no longer valid. I have just fixed it. |
The changes requested have been performed.
This is good to go. Merging. |
Thank you very much for your contribution and merging @GaelVaroquaux |
@KamalakerDadi, thanks for doing the work! |
[MRG] Fix: NaN problem in NiftiMapsMasker
PR nilearn#1271 introduces this regression where ensure_finite=True modifies the data inplace instead of copying it (nan_to_num does an implicit copy). I preferred to get back to nan_to_num instead of just doing a copy because I believe that relying on numpy functions is safer and more future proof.
* Remove inplace modification in signal.clean PR #1271 introduces this regression where ensure_finite=True modifies the data inplace instead of copying it (nan_to_num does an implicit copy). I preferred to get back to nan_to_num instead of just doing a copy because I believe that relying on numpy functions is safer and more future proof. * Do the replacement manually to be compliant with older numpy * Added test to verify no inplace modification occurs when ensure_finite=True
Attempts to Fix #1249