-
Notifications
You must be signed in to change notification settings - Fork 19
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
Integrate physutils - Physio object usage #54
Conversation
Currently tests are failing, because the |
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 main thing that I think we need to discuss is checking for peaks/troughs where appropriate in the Physio object. I'm not sure the best way to do this (and we've talked about maintaining functionality for non-Physio object time-series, so we'll need to think about that in this context too). Many parameters e.g. TR could equally be associated with a Physio object, so we need a clear plan that checks for parameters not supplied explicitly but allows flexibility for a non-Physio implementation.
sample_rate : float | ||
Physio sample rate, in Hertz. | ||
data : Physio_like | ||
Object containing the timeseries of the recorded respiratory or cardiac signal |
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 would suggest including a bit more information here, especially that the object should also contain associated meta-data.
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 it's worth a discussion where we talk about advantages of forcing some parameters to be in the Physio object vs. explicitly specified before we go too far down this road!)
I believe the support for Physio and non-Physio inputs has been completed. @m-miedema @me-pic let me know your thoughts The unit tests now need updating. |
In CRF, iCRF and RRF there is this line: time_stamps = np.arange(0, time_length, 1 / samplerate) Since samplerate is in seconds, shouldn't it be time_stamps = np.arange(0, time_length, samplerate) Do you prefer changing the doc to Hz or is it more convinient in seconds? |
Also in general, shouldn't we opt in a single way to define sample rate? Is there any reason to not have everything in Hz? |
assert rrf_arr.ndim == 1 | ||
assert rrf_arr.size == pred_len | ||
|
||
|
||
@mark.xfail |
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.
Not sure why this was marked as xfail, but now it's fixed and it passes.
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.
That's also mentioned in #41
@m-miedema metrics calculations are already integrated with the |
Currently blocked by physiopy/physutils#4, due to important physio object fix (release needed): 0c324c5 and 2b0deb9 |
@smoia @me-pic @m-miedema I added a wrapper to sort out the returns (only metric or physio with metric), feel free to review.
Also, the following changes were made to deps:
|
I believe the Metric class will make it much easier for the pydra tasks to handle computing and exporting metrics |
install_requires = | ||
numpy >=1.9.3 | ||
matplotlib >=3.1.1 | ||
numpy >=1.9.3, <2 |
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.
Can you open an issue to state we need to refactor code to handle numpy >=2.0, please?
(It's good to leave it like this for the moment, though, 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.
Opened here: #62
@@ -48,7 +50,7 @@ test = | |||
%(style)s | |||
pytest >=5.3 | |||
pytest-cov | |||
peakdet | |||
peakdet>=0.5.0 |
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.
With the physutils integration, do we still need peakdet?
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.
yes, there is a unit test with peak finding
phys2denoise/metrics/utils.py
Outdated
""" | ||
|
||
def determine_return_type(func): | ||
metrics_with_lags = ["respiratory_variance_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.
We did not implemented it yet, but technically all metrics could have lags - and all metrics could be convolved. Is this a way to check the type of output you get?
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.
Could you instead make a check on the provided arguments to the function, to check whether convolution and/or lag happened?
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'll change to detect lagged metrics, since it's dependent on the argument. However convolution is now only done in some metrics without being specified in the arguments and can't be detected easily if you don't read the code. Also you can't deduce that the metric is convolved from the dimensions of the metric output.
I can open an issue about it if you want
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.
An issue sounds good to me, you're right that we can improve transparency and flexibility for those operations as we build up the toolbox. If you don't have time I can open one, let me know your preference @maestroque :)
@@ -155,30 +193,67 @@ def env(resp, samplerate, window=10): | |||
young adults scanned at rest, including systematic changes and 'missed' | |||
deep breaths," Neuroimage, vol. 204, 2020. | |||
""" | |||
|
|||
def _respiratory_pattern_variability(data, window): |
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.
Now that you can either input a physio object or a numpy array, do we still need to duplicate code for this application?
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.
That's true the relevant line in env()
could be changed as such:
env_arr = (
pd.Series(data.data)
.rolling(window=window, center=True)
.apply(respiratory_pattern_variability, args=(window,), kwargs=dict(return_physio=False))
)
However we should also include support for pandas.core.series.Series
types in the load_physio
function for that to work, which also impacts the other module. Still, I think it'd be better to open an issue about it, because it complicates the PRs
Wdyty @smoia
phys2denoise/metrics/chest_belt.py
Outdated
@return_physio_or_metric() | ||
@physio.make_operation() | ||
def respiratory_variance_time( | ||
data, fs=None, peaks=None, troughs=None, lags=(0, 4, 8, 12), **kwargs |
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.
data, fs=None, peaks=None, troughs=None, lags=(0, 4, 8, 12), **kwargs | |
data, peaks=None, troughs=None, fs=None, lags=(0, 4, 8, 12), **kwargs |
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.
It would be good to have peaks and troughs first when possible (and, in general, avoid changing the order of inputs as much as possible), but if it makes things too complicated with the wrapper, then no problem.
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
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.
Couple of notes for the moment!
phys2denoise/metrics/chest_belt.py
Outdated
@return_physio_or_metric() | ||
@physio.make_operation() | ||
def respiratory_variance_time( | ||
data, fs=None, peaks=None, troughs=None, lags=(0, 4, 8, 12), **kwargs |
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.
It would be good to have peaks and troughs first when possible (and, in general, avoid changing the order of inputs as much as possible), but if it makes things too complicated with the wrapper, then no problem.
phys2denoise/metrics/utils.py
Outdated
""" | ||
|
||
def determine_return_type(func): | ||
metrics_with_lags = ["respiratory_variance_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.
Could you instead make a check on the provided arguments to the function, to check whether convolution and/or lag happened?
phys2denoise/metrics/chest_belt.py
Outdated
troughs found by peakdet algorithm | ||
samplerate: float | ||
sample rate in hz of respiratory belt | ||
data : Physio_like |
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.
Could we change this to physutils.Physio, np.ndarray, or array-like object
instead?
That's also possible now, right?
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.
yeap, done
@@ -42,13 +47,38 @@ def respiratory_variance_time(resp, peaks, troughs, samplerate, lags=(0, 4, 8, 1 | |||
respiratory-variation-related fluctuations from neuronal-activity-related | |||
fluctuations in fMRI”, NeuroImage, vol.31, pp. 1536-1548, 2006. | |||
""" | |||
timestep = 1 / samplerate | |||
if isinstance(data, physio.Physio): |
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 was that you repeat this few following lines often in different metrics - if that is the case, I would suggest to make it a function on its own or even better to add it to the decorator you created. If you make it a function, the return can be done within the metric function itself (without the wrapper), if you move this to the decorator (better), you can revert to the original metric functions, and deal with the input and returns of the metrics within the wrapper!
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 tried to think of a cleaner way, but tbh the error handling cannot be abstracted efficiently, because there are some differences between functions using fs, peaks, troughs and combinations of them. Effectively, if an abstraction would be made it would almost have to be a separate handle case for each function. It could be done by parsing the argument name list in the wrapper but I don't think it can be much cleaner than what exists
@smoia please recheck when you find some 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.
With the updated physutils this looks good to me! I don't see a reason not to fix the one test I mentioned on the way out but (feedback from @smoia pending) I would be happy to see this move on and into the workflow.
@@ -20,7 +20,7 @@ def test_mirrorpad_exception(short_arr): | |||
"""When passing array that is too short to perform mirrorpadding, the | |||
function should give an error.""" | |||
arr = np.array(short_arr) | |||
with pytest.raises(Exception) as e_info: | |||
with pytest.raises(IndexError): |
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 get the expected logger warning here, so the test fails. I'm guessing this one is what you were referring to as the failed catch here? If so, I'm wondering what your suggestion is for handling it to catch the warning instead.
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.
Yes that's the one. It's not detected because it's being handled. I'll just add a check that the subsequent warning is raised
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.
It's a bit convoluted though and it's pending a full integration of loguru in this package as well. I'd say we merge and document in the issue/PR #53
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 did a quick fix and it detects the expected warning text (as in the peakdet warning assertion tests I did), however the full integration of loguru needs to be adressed: Relevant note
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.
Thanks @maestroque !
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #54 +/- ##
==========================================
+ Coverage 47.04% 53.85% +6.81%
==========================================
Files 7 8 +1
Lines 321 596 +275
==========================================
+ Hits 151 321 +170
- Misses 170 275 +105
|
Now, CI passes online as well, because of the |
Proposed Changes
Physio
object for the metric functionsChange Type
bugfix
(+0.0.1)minor
(+0.1.0)major
(+1.0.0)refactoring
(no version update)test
(no version update)infrastructure
(no version update)documentation
(no version update)other
Checklist before review