Repository: neuropsychology/NeuroKit (language: python)

Issue:
Confusion between exhalation peaks/troughs and inhale/exhale onsets [RSP]
Neurokit labels peaks in the respiration signal as inhalation peaks and troughs as exhalation troughs (e.g. the rsp_plot graph). The example https://neurokit2.readthedocs.io/en/latest/examples/rrv.html doesn't specify the nature of the respiration measurment (but perhaps should), though here refers to inspiration peaks and exhalation troughs. 
This had potential for confusion, as peaks and troughs in a respiration signal have different meaning depending on the type of recording and the measure (i.e. volume, spirometer or breathing belt). 
The code which calculates whether a sample is inspiration or expiration assumes that a trough is the start of inhalation, which would the case for breathing belt data (and I assume spirometer users might not be using neurokit for analysis). 

However, some of the code (if I am reading it right) seems to assume that "peaks" are inhalation onsets. For example, 
https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_rrv.html#rsp_rrv
bbi = np.peaks(peaks) / sampling rate * 1000
Here bbi is breath to interval, which is used to calculate a number of measures. 
Traditionally these measures would use the onset of the inhalation as the start of a breath cycle not the start of the exhale (i.e. the "inspiration peak" in neurokit terminology), so it seems the code should be **np.peaks(troughs)**. 
So this suggests a few possible changes - fixing the code to use troughs not peaks (and I think a few other functions are affected), being more explicit about the respiration recording type assumed in the docs, and perhaps shifting more to labelling peaks and troughs as inhale onsets and exhale onsets instead of inspiration/exhalation troughs and inspiration peaks (which would reduce ambiguity of the meaning of a "inspiration/exhalation peak/trough").
[note, some functions might work very similar regardless of using peaks and troughs, but in terms of reporting of measures inhale onsets would be better to fit with standard methods)


Confusion between exhalation peaks/troughs and inhale/exhale onsets [RSP]
Neurokit labels peaks in the respiration signal as inhalation peaks and troughs as exhalation troughs (e.g. the rsp_plot graph). The example https://neurokit2.readthedocs.io/en/latest/examples/rrv.html doesn't specify the nature of the respiration measurment (but perhaps should), though here refers to inspiration peaks and exhalation troughs. 
This had potential for confusion, as peaks and troughs in a respiration signal have different meaning depending on the type of recording and the measure (i.e. volume, spirometer or breathing belt). 
The code which calculates whether a sample is inspiration or expiration assumes that a trough is the start of inhalation, which would the case for breathing belt data (and I assume spirometer users might not be using neurokit for analysis). 

However, some of the code (if I am reading it right) seems to assume that "peaks" are inhalation onsets. For example, 
https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_rrv.html#rsp_rrv
bbi = np.peaks(peaks) / sampling rate * 1000
Here bbi is breath to interval, which is used to calculate a number of measures. 
Traditionally these measures would use the onset of the inhalation as the start of a breath cycle not the start of the exhale (i.e. the "inspiration peak" in neurokit terminology), so it seems the code should be **np.peaks(troughs)**. 
So this suggests a few possible changes - fixing the code to use troughs not peaks (and I think a few other functions are affected), being more explicit about the respiration recording type assumed in the docs, and perhaps shifting more to labelling peaks and troughs as inhale onsets and exhale onsets instead of inspiration/exhalation troughs and inspiration peaks (which would reduce ambiguity of the meaning of a "inspiration/exhalation peak/trough").
[note, some functions might work very similar regardless of using peaks and troughs, but in terms of reporting of measures inhale onsets would be better to fit with standard methods)




Discussion / hints:
Hi 👋 Thanks for reaching out and opening your first issue here! We'll try to come back to you as soon as possible. ❤️ ![kenobi](https://media.giphy.com/media/Nx0rz3jtxtEre/giphy.gif)

Hi @shanelindsay, thanks for chiming in. It's true overall that our RSP processing hasn't received the same amount of love as others signals. So we're really glad to receive suggestions and feedback from experts such as yourself so that together we can make the RSP features top-notch!

> The example https://neurokit2.readthedocs.io/en/latest/examples/rrv.html doesn't specify the nature of the respiration measurment (but perhaps should)

by nature of measurement you mean like whether it comes from a respiration belt etc.?

> The code which calculates whether a sample is inspiration or expiration assumes that a trough is the start of inhalation, which would the case for breathing belt data

It is true that currently the code implicitly assumes respiration belt data, and we don't have options or methods more adapted to other techniques. To be honest, because we don't use other techniques in our research, our experience and understanding of the specificities are fairly limited (hence the importance of your suggestions). That said, I'm not entirely sure which processing steps in particular could be differentiated based on the recording technique... the peak detection? The filtering/cleaning? the features extraction 🤔 Perhaps we could list like all the techniques measuring "RSP" and for each, the specificities that we need either to specifically support, or that would make them incompatible with some of the existing NK algos.

> Traditionally these measures would use the onset of the inhalation as the start of a breath cycle not the start of the exhale

Good catch, we need to address that.

- [x] **code**: fixing the code to use troughs not peaks (and I think a few other functions are affected)
- [ ] **docs**: being more explicit about the respiration recording type assumed in the docs
- [x] **docs**: shifting more to labelling peaks and troughs as inhale onsets and exhale onsets instead of inspiration/exhalation troughs and inspiration peaks


Great call! Interesting that the shading of our rsp plot is actually in line with what was raised here, in that the trough is the start of the breath cycle (and hence inhalation onset), although our operationalization of bbi as `np.diff(peaks)` implies that each peak is the inhalation onset. 
![image](https://user-images.githubusercontent.com/54053748/131770355-6e926ec5-7800-4a87-9fac-8bd6aa386d05.png)

And so if I'm understanding correctly, here the labels of exhalation troughs should be inhalation onsets and inhalation peaks should be exhalation onsets. Adding this to our [general roadmap](https://github.com/neuropsychology/NeuroKit/projects/4)!



Thanks for the response guys. Yeah @zen-juen I went through the code to double check and saw that the graph and other code that does assume troughs as inhale onsets.  The reason why I looked into it is that I ran some analysis that showed expiration to shorter than inspiration, which is biologically implausible in normal populations, and just wanted to double check the code. Which checked out for this. I found the probable reason for my issue that was pauses being coded as inhales thus inflating mean inhale length, suggesting to me a need to code pauses (which I will return to below). 

One suggestion would be to add a default argument to some of the functions of type = "bb" or "RIP" for breathing belt data, which would make things more explicit, and open the possibility in future of other types of signals. 

>> by nature of measurement you mean like whether it comes from a respiration belt etc.?

Yes. It could also perhaps mention that it assumes a single belt recording and the user would need to combine dual recordings (i.e. thorax and ab) before inputting into neurokit. 

>> That said, I'm not entirely sure which processing steps in particular could be differentiated based on the recording technique... the peak detection? The filtering/cleaning? the features extraction 🤔 Perhaps we could list like all the techniques measuring "RSP" and for each, the specificities that we need either to specifically support, or that would make them incompatible with some of the existing NK algos.

There are a lot of methods for measuring RSP! See https://iopscience.iop.org/article/10.1088/1361-6579/ab299e

For example, khodadad2018 algorithm is based on electrical impedance tomography. While the algorithm seems to work ok, there are some features that are very different compared with breathing belt data, e.g. apneas here:

![https://thorax.bmj.com/content/thoraxjnl/72/1/83/F2.large.jpg](url)

I am guessing the main demand for neurokit users is breathing belt data i.e. respiratory inductance plethysmography (RIP). Potentially spirometry and estimation from RR (such as from PPG) might also be of interest (see link above for developments in that area). 

For RIP data, the ideal would be to have have algorithms better adapted or validated for this method, compared with khodadad2018 and the other one. I will add a feature suggestion on a separate post about this. 

But briefly, the Breathmetrics algorithm is designed for spirometry but easily adapted for RIP, so if that was implemented in neurokit, along with some additional metrics for volume measurement/estimation, then it would have support then for RIP and spirometry (two for the price of one). But supporting pauses could be a headache for existing code. 

RIP data also can be quite prone to movement artefacts (say, compared to spirometry) and improved outlier detection would be beneficial here. I will see if I can add another post on that. 


Also to add a separate but related point:

In this code https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_findpeaks.html :
```
_rsp_findpeaks_extrema(rsp_cleaned):
    # Detect zero crossings (note that these are zero crossings in the raw
    # signal, not in its gradient).
    greater = rsp_cleaned > 0
```
I think it might be based on khodadad2018 ?

For RIP measurements are often in arbitrary units where zero has no meaning. So the raw signal might not have a zero, unless it is z-transformed. This differs from other methods like spirometry and electrical impedance tomography where 0 crossing has a specific meaning e.g. in spirometry downwards zero crossing marks exhale onset. So I haven't thought through the consequences of this code but raising it as a potential issue. 

Hi @shanelindsay, in the previous PR (ref above) I've changed all instances of "inhalation peaks" and "exhalation troughs", to "peaks (exhalation onsets)" and "troughs (inhalation onsets)" respectively in the docstrings. The explicit labelling of onsets is done only in `rsp_plot` and I left the namings in the generated signal columns/dictionary info as they are (i.e., `RSP_Peaks` and `RSP_Troughs`) since it seems more consistent with our API like that. `rsp_rrv()` also now correctly uses troughs rather than peaks. 

However, I have one question regarding the computing of respiratory rate - as you can see in our 
[code](
https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_rate.html), there are currently two methods to do so:
https://github.com/neuropsychology/NeuroKit/blob/c1104386655724a5c624e740abf651c939fc5e48/neurokit2/rsp/rsp_rate.py#L24-L28
The latter is independent of the indices but for the former, does it matter whether periods are computed from peaks or troughs?





Regarding the above-mentioned issue about respiratory rate, see https://www.mdpi.com/1424-8220/20/6/1607 who concurs with the use of peaks from BIOPAC belt measurements, but with an additional threshold implemented:

> The respiration rate was calculated by counting the peaks from the respiration belt, which were used as the ground
truth. The threshold for detecting peaks in the respiration belt data was calculated using Equation (9). The term RGnd(n) is the signal measured from the respiration belt. If the peak was larger than the threshold, it was detected, and the peak was counted only when the interval between the peaks was 2.5 s or more.
Threshold = 0.8 × (1/(max(RGnd(n)) − min(RGnd(n)))) (9)

as well as in https://www.mdpi.com/795524

> Then, the average breath frequency has been extracted from the respiratory signal acquired through
the wearable belt and compared with the one recorded by the spirometer. The average frequency has
been extracted from the average breath-to-breath interval, estimated on the basis of the peak-to-peak
distance.

and https://www.sciencedirect.com/science/article/pii/S2352648320300015 (also with BIOPAC belt sensor)
![image](https://user-images.githubusercontent.com/54053748/133188489-bb21a763-e685-4904-bf0f-56acb47c7e70.png)



Great to see those changes.

In answer to your question, in one sense it might not matter, since you are
going to get similar results either way. But I think it does matter in a
couple of ways. In that first paper you cite, they only care about respiration
rate, nothing else. And it is primarily about using radar to detect
respiration rate, and maybe peaks are better here for that method. The others seem similar in that they are engineering solutions, whereas I would see as neurokit more aimed at scientific solutions, for psychophysiology research. 

In neurokit, the respiration analysis emcompasses lots of aspects of the
respiration signal.

So it seems a bit weird (and a reviewer might think it weird) in a paper to
say we calculated the start of the breath as inspiration onset (for various
metrics), but for calculation of respiratory rate we counted exhalation
onset.

The code seems to suggest it would be straightforward to change peaks to
troughs, so this is what I would advocate for.


On Tue, 14 Sept 2021 at 03:45, Zen ***@***.***> wrote:

> Regarding the above-mentioned issue about respiratory rate, see
> https://www.mdpi.com/1424-8220/20/6/1607 who concurs with the use of
> peaks from belt measurements, but with an additional threshold implemented:
>
> The respiration rate was calculated by counting the peaks from the
> respiration belt, which were used as the ground
> truth. The threshold for detecting peaks in the respiration belt data was
> calculated using Equation (9). The term RGnd(n) is the signal measured from
> the respiration belt. If the peak was larger than the threshold, it was
> detected, and the peak was counted only when the interval between the peaks
> was 2.5 s or more.
> Threshold = 0.8 × (1/(max(RGnd(n)) − min(RGnd(n)))) (9)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/neuropsychology/NeuroKit/issues/528#issuecomment-918748791>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAVKQFEIRCUBVRJN4EN4ANDUB2ZNBANCNFSM5DG2X23A>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

Yes it's indeed true that many studies pit more modern/rare respiration measurement techniques (like radar) against the respiratory signal as measured by a belt as a reference. Based on a breadth-focused search, I haven't yet found any papers that are purely focused on evaluating the latter.

I also agree that it is concerning if our operationalization of BBI is inconsistent with the way we compute respiratory rate, as you correctly highlighted above. Given that respiratory rate is akin to the number of respiration events in time, it makes sense to compute it based on periods between successive inspirations (inspiration onset-to-inspiration onset) - which this [paper](https://annals-csis.org/Volume_8/pliks/329.pdf) also does. What do you think? @DominiqueMakowski 
i.e. based on troughs?
> i.e. based on troughs?

Yes 😄 
Hi 👋 Thanks for reaching out and opening your first issue here! We'll try to come back to you as soon as possible. ❤️ ![kenobi](https://media.giphy.com/media/Nx0rz3jtxtEre/giphy.gif)

Hi @shanelindsay, thanks for chiming in. It's true overall that our RSP processing hasn't received the same amount of love as others signals. So we're really glad to receive suggestions and feedback from experts such as yourself so that together we can make the RSP features top-notch!

> The example https://neurokit2.readthedocs.io/en/latest/examples/rrv.html doesn't specify the nature of the respiration measurment (but perhaps should)

by nature of measurement you mean like whether it comes from a respiration belt etc.?

> The code which calculates whether a sample is inspiration or expiration assumes that a trough is the start of inhalation, which would the case for breathing belt data

It is true that currently the code implicitly assumes respiration belt data, and we don't have options or methods more adapted to other techniques. To be honest, because we don't use other techniques in our research, our experience and understanding of the specificities are fairly limited (hence the importance of your suggestions). That said, I'm not entirely sure which processing steps in particular could be differentiated based on the recording technique... the peak detection? The filtering/cleaning? the features extraction 🤔 Perhaps we could list like all the techniques measuring "RSP" and for each, the specificities that we need either to specifically support, or that would make them incompatible with some of the existing NK algos.

> Traditionally these measures would use the onset of the inhalation as the start of a breath cycle not the start of the exhale

Good catch, we need to address that.

- [x] **code**: fixing the code to use troughs not peaks (and I think a few other functions are affected)
- [ ] **docs**: being more explicit about the respiration recording type assumed in the docs
- [x] **docs**: shifting more to labelling peaks and troughs as inhale onsets and exhale onsets instead of inspiration/exhalation troughs and inspiration peaks


Great call! Interesting that the shading of our rsp plot is actually in line with what was raised here, in that the trough is the start of the breath cycle (and hence inhalation onset), although our operationalization of bbi as `np.diff(peaks)` implies that each peak is the inhalation onset. 
![image](https://user-images.githubusercontent.com/54053748/131770355-6e926ec5-7800-4a87-9fac-8bd6aa386d05.png)

And so if I'm understanding correctly, here the labels of exhalation troughs should be inhalation onsets and inhalation peaks should be exhalation onsets. Adding this to our [general roadmap](https://github.com/neuropsychology/NeuroKit/projects/4)!



Thanks for the response guys. Yeah @zen-juen I went through the code to double check and saw that the graph and other code that does assume troughs as inhale onsets.  The reason why I looked into it is that I ran some analysis that showed expiration to shorter than inspiration, which is biologically implausible in normal populations, and just wanted to double check the code. Which checked out for this. I found the probable reason for my issue that was pauses being coded as inhales thus inflating mean inhale length, suggesting to me a need to code pauses (which I will return to below). 

One suggestion would be to add a default argument to some of the functions of type = "bb" or "RIP" for breathing belt data, which would make things more explicit, and open the possibility in future of other types of signals. 

>> by nature of measurement you mean like whether it comes from a respiration belt etc.?

Yes. It could also perhaps mention that it assumes a single belt recording and the user would need to combine dual recordings (i.e. thorax and ab) before inputting into neurokit. 

>> That said, I'm not entirely sure which processing steps in particular could be differentiated based on the recording technique... the peak detection? The filtering/cleaning? the features extraction 🤔 Perhaps we could list like all the techniques measuring "RSP" and for each, the specificities that we need either to specifically support, or that would make them incompatible with some of the existing NK algos.

There are a lot of methods for measuring RSP! See https://iopscience.iop.org/article/10.1088/1361-6579/ab299e

For example, khodadad2018 algorithm is based on electrical impedance tomography. While the algorithm seems to work ok, there are some features that are very different compared with breathing belt data, e.g. apneas here:

![https://thorax.bmj.com/content/thoraxjnl/72/1/83/F2.large.jpg](url)

I am guessing the main demand for neurokit users is breathing belt data i.e. respiratory inductance plethysmography (RIP). Potentially spirometry and estimation from RR (such as from PPG) might also be of interest (see link above for developments in that area). 

For RIP data, the ideal would be to have have algorithms better adapted or validated for this method, compared with khodadad2018 and the other one. I will add a feature suggestion on a separate post about this. 

But briefly, the Breathmetrics algorithm is designed for spirometry but easily adapted for RIP, so if that was implemented in neurokit, along with some additional metrics for volume measurement/estimation, then it would have support then for RIP and spirometry (two for the price of one). But supporting pauses could be a headache for existing code. 

RIP data also can be quite prone to movement artefacts (say, compared to spirometry) and improved outlier detection would be beneficial here. I will see if I can add another post on that. 


Also to add a separate but related point:

In this code https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_findpeaks.html :
```
_rsp_findpeaks_extrema(rsp_cleaned):
    # Detect zero crossings (note that these are zero crossings in the raw
    # signal, not in its gradient).
    greater = rsp_cleaned > 0
```
I think it might be based on khodadad2018 ?

For RIP measurements are often in arbitrary units where zero has no meaning. So the raw signal might not have a zero, unless it is z-transformed. This differs from other methods like spirometry and electrical impedance tomography where 0 crossing has a specific meaning e.g. in spirometry downwards zero crossing marks exhale onset. So I haven't thought through the consequences of this code but raising it as a potential issue. 

Hi @shanelindsay, in the previous PR (ref above) I've changed all instances of "inhalation peaks" and "exhalation troughs", to "peaks (exhalation onsets)" and "troughs (inhalation onsets)" respectively in the docstrings. The explicit labelling of onsets is done only in `rsp_plot` and I left the namings in the generated signal columns/dictionary info as they are (i.e., `RSP_Peaks` and `RSP_Troughs`) since it seems more consistent with our API like that. `rsp_rrv()` also now correctly uses troughs rather than peaks. 

However, I have one question regarding the computing of respiratory rate - as you can see in our 
[code](
https://neurokit2.readthedocs.io/en/latest/_modules/neurokit2/rsp/rsp_rate.html), there are currently two methods to do so:
https://github.com/neuropsychology/NeuroKit/blob/c1104386655724a5c624e740abf651c939fc5e48/neurokit2/rsp/rsp_rate.py#L24-L28
The latter is independent of the indices but for the former, does it matter whether periods are computed from peaks or troughs?





Regarding the above-mentioned issue about respiratory rate, see https://www.mdpi.com/1424-8220/20/6/1607 who concurs with the use of peaks from BIOPAC belt measurements, but with an additional threshold implemented:

> The respiration rate was calculated by counting the peaks from the respiration belt, which were used as the ground
truth. The threshold for detecting peaks in the respiration belt data was calculated using Equation (9). The term RGnd(n) is the signal measured from the respiration belt. If the peak was larger than the threshold, it was detected, and the peak was counted only when the interval between the peaks was 2.5 s or more.
Threshold = 0.8 × (1/(max(RGnd(n)) − min(RGnd(n)))) (9)

as well as in https://www.mdpi.com/795524

> Then, the average breath frequency has been extracted from the respiratory signal acquired through
the wearable belt and compared with the one recorded by the spirometer. The average frequency has
been extracted from the average breath-to-breath interval, estimated on the basis of the peak-to-peak
distance.

and https://www.sciencedirect.com/science/article/pii/S2352648320300015 (also with BIOPAC belt sensor)
![image](https://user-images.githubusercontent.com/54053748/133188489-bb21a763-e685-4904-bf0f-56acb47c7e70.png)



Great to see those changes.

In answer to your question, in one sense it might not matter, since you are
going to get similar results either way. But I think it does matter in a
couple of ways. In that first paper you cite, they only care about respiration
rate, nothing else. And it is primarily about using radar to detect
respiration rate, and maybe peaks are better here for that method. The others seem similar in that they are engineering solutions, whereas I would see as neurokit more aimed at scientific solutions, for psychophysiology research. 

In neurokit, the respiration analysis emcompasses lots of aspects of the
respiration signal.

So it seems a bit weird (and a reviewer might think it weird) in a paper to
say we calculated the start of the breath as inspiration onset (for various
metrics), but for calculation of respiratory rate we counted exhalation
onset.

The code seems to suggest it would be straightforward to change peaks to
troughs, so this is what I would advocate for.


On Tue, 14 Sept 2021 at 03:45, Zen ***@***.***> wrote:

> Regarding the above-mentioned issue about respiratory rate, see
> https://www.mdpi.com/1424-8220/20/6/1607 who concurs with the use of
> peaks from belt measurements, but with an additional threshold implemented:
>
> The respiration rate was calculated by counting the peaks from the
> respiration belt, which were used as the ground
> truth. The threshold for detecting peaks in the respiration belt data was
> calculated using Equation (9). The term RGnd(n) is the signal measured from
> the respiration belt. If the peak was larger than the threshold, it was
> detected, and the peak was counted only when the interval between the peaks
> was 2.5 s or more.
> Threshold = 0.8 × (1/(max(RGnd(n)) − min(RGnd(n)))) (9)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/neuropsychology/NeuroKit/issues/528#issuecomment-918748791>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAVKQFEIRCUBVRJN4EN4ANDUB2ZNBANCNFSM5DG2X23A>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

Yes it's indeed true that many studies pit more modern/rare respiration measurement techniques (like radar) against the respiratory signal as measured by a belt as a reference. Based on a breadth-focused search, I haven't yet found any papers that are purely focused on evaluating the latter.

I also agree that it is concerning if our operationalization of BBI is inconsistent with the way we compute respiratory rate, as you correctly highlighted above. Given that respiratory rate is akin to the number of respiration events in time, it makes sense to compute it based on periods between successive inspirations (inspiration onset-to-inspiration onset) - which this [paper](https://annals-csis.org/Volume_8/pliks/329.pdf) also does. What do you think? @DominiqueMakowski 
i.e. based on troughs?
> i.e. based on troughs?

Yes 😄 

Fix patch:
diff --git a/NEWS.rst b/NEWS.rst
index f80b10e731..1d40cc9369 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -26,7 +26,8 @@ Fixes
 * Documentation of respiration from peak/trough terminology to inhale/exhale onsets
 * Change labelling in `rsp_plot()` from "inhalation peaks" and "exhalation troughs" to "peaks (exhalation onsets)" and "troughs (inhalation onsets)" respectively.
 * Change RSP_Amplitude_Mean/Min/Max parameters to be corrected based on value closest to t=0 in event-related analysis, rather than using all pre-zero values.
-* Have `rsp_rrv()` compute breath-to-breath intervals based on trough indices (inhalation onsets) rather than peak indices.
+* Have `rsp_rrv()` compute breath-to-breath intervals based on trough indices (inhalation onsets) rather than peak indices
+* Compute `rsp_rate()` based on trough indices (rather than peak indices) in 'periods' method
 
 
 0.1.4.1
diff --git a/neurokit2/rsp/rsp_amplitude.py b/neurokit2/rsp/rsp_amplitude.py
index ff6ae6b5af..42be307da2 100644
--- a/neurokit2/rsp/rsp_amplitude.py
+++ b/neurokit2/rsp/rsp_amplitude.py
@@ -16,10 +16,12 @@ def rsp_amplitude(rsp_cleaned, peaks, troughs=None, interpolation_method="monoto
     rsp_cleaned : Union[list, np.array, pd.Series]
         The cleaned respiration channel as returned by `rsp_clean()`.
     peaks : list or array or DataFrame or Series or dict
-        The samples at which the inhalation peaks occur. If a dict or a DataFrame is passed, it is
+        The samples at which the respiration peaks (exhalation onsets) occur.
+        If a dict or a DataFrame is passed, it is
         assumed that these containers were obtained with `rsp_findpeaks()`.
     troughs : list or array or DataFrame or Series or dict
-        The samples at which the inhalation troughs occur. If a dict or a DataFrame is passed, it is
+        The samples at which the respiration troughs (inhalation onsets) occur.
+        If a dict or a DataFrame is passed, it is
         assumed that these containers were obtained with `rsp_findpeaks()`.
     interpolation_method : str
         Method used to interpolate the amplitude between peaks. See `signal_interpolate()`. 'monotone_cubic' is chosen
@@ -44,9 +46,9 @@ def rsp_amplitude(rsp_cleaned, peaks, troughs=None, interpolation_method="monoto
     >>>
     >>> rsp = nk.rsp_simulate(duration=90, respiratory_rate=15)
     >>> cleaned = nk.rsp_clean(rsp, sampling_rate=1000)
-    >>> info, signals = nk.rsp_peaks(cleaned)
+    >>> peak_signals, info = nk.rsp_peaks(cleaned)
     >>>
-    >>> amplitude = nk.rsp_amplitude(cleaned, signals)
+    >>> amplitude = nk.rsp_amplitude(cleaned, peak_signals)
     >>> fig = nk.signal_plot(pd.DataFrame({"RSP": rsp, "Amplitude": amplitude}), subplots=True)
     >>> fig #doctest: +SKIP
 
diff --git a/neurokit2/rsp/rsp_peaks.py b/neurokit2/rsp/rsp_peaks.py
index 88d98b7dd4..92bdde8dc2 100644
--- a/neurokit2/rsp/rsp_peaks.py
+++ b/neurokit2/rsp/rsp_peaks.py
@@ -8,8 +8,9 @@
 def rsp_peaks(rsp_cleaned, sampling_rate=1000, method="khodadad2018", amplitude_min=0.3):
     """Identify extrema in a respiration (RSP) signal.
 
-    This function `rsp_findpeaks()` and `rsp_fixpeaks` to identify and process inhalation peaks and
-    exhalation troughs in a preprocessed respiration signal using different sets of parameters, such as:
+    This function `rsp_findpeaks()` and `rsp_fixpeaks` to identify and process peaks (exhalation onsets)
+    and troughs (inhalation onsets) in a preprocessed respiration signal using
+    different sets of parameters, such as:
 
     - `Khodadad et al. (2018)
     <https://iopscience.iop.org/article/10.1088/1361-6579/aad7e6/meta>`_
diff --git a/neurokit2/rsp/rsp_process.py b/neurokit2/rsp/rsp_process.py
index d81382623d..49ab70618a 100644
--- a/neurokit2/rsp/rsp_process.py
+++ b/neurokit2/rsp/rsp_process.py
@@ -74,7 +74,7 @@ def rsp_process(rsp_signal, sampling_rate=1000, method="khodadad2018"):
     # Get additional parameters
     phase = rsp_phase(peak_signal, desired_length=len(rsp_signal))
     amplitude = rsp_amplitude(rsp_cleaned, peak_signal)
-    rate = signal_rate(peak_signal, sampling_rate=sampling_rate, desired_length=len(rsp_signal))
+    rate = signal_rate(info["RSP_Troughs"], sampling_rate=sampling_rate, desired_length=len(rsp_signal))
 
     # Prepare output
     signals = pd.DataFrame(
diff --git a/neurokit2/rsp/rsp_rate.py b/neurokit2/rsp/rsp_rate.py
index 3f3d4f6f84..e50254093b 100644
--- a/neurokit2/rsp/rsp_rate.py
+++ b/neurokit2/rsp/rsp_rate.py
@@ -6,15 +6,18 @@
 from .rsp_peaks import rsp_peaks
 
 
-def rsp_rate(rsp_cleaned, peaks=None, sampling_rate=1000, window=10, hop_size=1, method="peak", peak_method="khodadad2018", interpolation_method="monotone_cubic"):
-    """Find respiration rate by cross-correlation method.
+def rsp_rate(rsp_cleaned, troughs=None, sampling_rate=1000, window=10, hop_size=1, method="trough", peak_method="khodadad2018", interpolation_method="monotone_cubic"):
+
+    """Find respiration rate.
+
     Parameters
     ----------
     rsp_cleaned : Union[list, np.array, pd.Series]
         The cleaned respiration channel as returned by `rsp_clean()`.
-    peaks : Union[list, np.array, pd.Series, pd.DataFrame]
-        The respiration peaks as returned by `rsp_peaks()`. If None (default), respiration peaks
-        will be automatically identified from the `rsp_clean` signal.
+    troughs : Union[list, np.array, pd.Series, pd.DataFrame]
+        The respiration troughs (inhalation onsets) as returned by `rsp_peaks()`.
+        If None (default), inhalation onsets will be automatically identified from the
+        `rsp_clean` signal.
     sampling_rate : int
         The sampling frequency of 'rsp_cleaned' (in Hz, i.e., samples/second).
     window : int
@@ -22,14 +25,15 @@ def rsp_rate(rsp_cleaned, peaks=None, sampling_rate=1000, window=10, hop_size=1,
     hop_size : int
         The number of samples between each successive window. Default to 1 sample.
     method : str
-        Method can be either "peak" or "xcorr". In "peak" method, rsp_rate is calculated from the
-        periods between respiration peaks. In "xcorr" method, cross-correlations between the changes
-        in respiration with a bank of sinusoids of different frequencies are caclulated to indentify
-        the principal frequency of oscillation.
+        Method can either be "trough" or "xcorr". In "trough" method, respiratory rate is calculated from the
+        periods between successive inspirations (i.e., inhalation onsets/troughs).
+        In "xcorr" method, cross-correlations between the changes in respiration with a bank of
+        sinusoids of different frequencies are caclulated to indentify the principal frequency of oscillation.
     peak_method : str
-        Method to identify respiration peaks. Can be one of "khodadad2018" (default) or "biosppy".
+        Method to identify successive respiratory inspirations, only relevant if method is "trough".
+        Can be one of "khodadad2018" (default) or "biosppy".
     interpolation_method : str
-        Method used to interpolate the rate between peaks. See `signal_interpolate()`. 'monotone_cubic'
+        Method used to interpolate the rate between inhalation onsets. See `signal_interpolate()`. 'monotone_cubic'
         is chosen as the default interpolation method since it ensures monotone interpolation between
         data points (i.e., it prevents physiologically implausible "overshoots" or "undershoots" in the
         y-direction). In contrast, the widely used cubic spline interpolation does not ensure
@@ -45,16 +49,16 @@ def rsp_rate(rsp_cleaned, peaks=None, sampling_rate=1000, window=10, hop_size=1,
     >>> import neurokit2 as nk
     >>> rsp_signal = nk.data("rsp_200hz.txt").iloc[:,0]
     >>> sampling_rate=200
-    >>> rsp_cleaned = nk.rsp_clean(rsp_signal)
-    >>> rsp_rate_peak = nk.rsp_rate(rsp_cleaned, sampling_rate=sampling_rate, method="peaks")
+    >>> rsp_cleaned = nk.rsp_clean(rsp_signal, sampling_rate=sampling_rate)
+    >>> rsp_rate_onsets = nk.rsp_rate(rsp_cleaned, sampling_rate=sampling_rate, method="trough")
     >>> rsp_rate_xcorr = nk.rsp_rate(rsp_cleaned, sampling_rate=sampling_rate, method="xcorr")
     """
 
-    if method.lower() in ["peak", "peaks", "signal_rate"]:
-        if peaks is None:
-            peaks, _ = rsp_peaks(rsp_cleaned, sampling_rate=sampling_rate, method=peak_method)
-        rsp_rate = signal_rate(peaks, sampling_rate=sampling_rate, desired_length=len(rsp_cleaned),
-                               interpolation_method=interpolation_method)
+    if method.lower() in ["period", "peak", "peaks", "trough", "troughs", "signal_rate"]:
+        if troughs is None:
+            _, troughs = rsp_peaks(rsp_cleaned, sampling_rate=sampling_rate, method=peak_method)
+        rsp_rate = signal_rate(troughs["RSP_Troughs"], sampling_rate=sampling_rate,
+                               desired_length=len(rsp_cleaned), interpolation_method=interpolation_method)
 
     elif method.lower() in ["cross-correlation", "xcorr"]:
         rsp_rate = _rsp_rate_xcorr(rsp_cleaned, sampling_rate=sampling_rate,
@@ -64,7 +68,7 @@ def rsp_rate(rsp_cleaned, peaks=None, sampling_rate=1000, window=10, hop_size=1,
     else:
         raise ValueError(
                 "NeuroKit error: rsp_rate(): 'method' should be"
-                " one of 'peak', or 'cross-correlation'."
+                " one of 'trough', or 'cross-correlation'."
                 )
 
     return rsp_rate
diff --git a/neurokit2/rsp/rsp_rrv.py b/neurokit2/rsp/rsp_rrv.py
index 729c7e6cd6..554ed3affa 100644
--- a/neurokit2/rsp/rsp_rrv.py
+++ b/neurokit2/rsp/rsp_rrv.py
@@ -271,11 +271,11 @@ def _rsp_rrv_formatinput(rsp_rate, troughs, sampling_rate=1000):
         df = rsp_rate.copy()
         cols = [col for col in df.columns if "RSP_Rate" in col]
         if len(cols) == 0:
-            cols = [col for col in df.columns if "RSP_Peaks" in col]
+            cols = [col for col in df.columns if "RSP_Troughs" in col]
             if len(cols) == 0:
                 raise ValueError(
                     "NeuroKit error: _rsp_rrv_formatinput(): Wrong input, "
-                    "we couldn't extract rsp_rate and peaks indices."
+                    "we couldn't extract rsp_rate and respiratory troughs indices."
                 )
             else:
                 rsp_rate = signal_rate(df[cols], sampling_rate=sampling_rate, desired_length=len(df))


Test patch:
diff --git a/tests/tests_rsp.py b/tests/tests_rsp.py
index 1723c08489..b3d09ee8e6 100644
--- a/tests/tests_rsp.py
+++ b/tests/tests_rsp.py
@@ -204,7 +204,7 @@ def test_rsp_rrv():
                                       random_state=42)
         short_cleaned90 = nk.rsp_clean(short_rsp90, sampling_rate=1000)
         _, short_peaks90 = nk.rsp_peaks(short_cleaned90)
-        short_rsp_rate90 = nk.signal_rate(short_peaks90, desired_length=len(short_rsp90))
+        short_rsp_rate90 = nk.signal_rate(short_peaks90["RSP_Troughs"], desired_length=len(short_rsp90))
 
         nk.rsp_rrv(short_rsp_rate90, short_peaks90)