Base functionality, sphinx and PyPi config#3
Conversation
gen_op_test.ipynb
Outdated
There was a problem hiding this comment.
This file will be removed after I've created the examples for EEGPhasePy usage. Keeping it here for now for reference
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
christianbrodbeck
left a comment
There was a problem hiding this comment.
Added some generic comments. For more detailed review, could you fill in the sphinx API documentation page, and add some usage examples? Sphinx-gallery may work well.
I've completed the full documentation + examples and applied the generic code fixes |
christianbrodbeck
left a comment
There was a problem hiding this comment.
Just partial, but wanted to let you know my comments so far. Will continue when I get another chance.
Co-authored-by: Christian Brodbeck <christianmbrodbeck@gmail.com>
Co-authored-by: Christian Brodbeck <christianmbrodbeck@gmail.com>
Co-authored-by: Christian Brodbeck <christianmbrodbeck@gmail.com>
…ePy into feat/new-estimators
Thanks @christianbrodbeck, I've updated the PR based on your comments |
mus1223
left a comment
There was a problem hiding this comment.
Left comments for sections: EEGPhasePy installation, ETP-based phase estimation, and the Alpha phase estimation using ETP pages.
For installation, main points are describing prior packages needed, and ensuring latest version of eegphasepy installs with provided command.
For ETP-based phase estimation page, main points are ensuring operability with Python 3.10 (or disclaimer to upgrade), some formatting notes, and additional recommendations to add.
For Alpha phase estimation using ETP page, main points are to reduce redundancy of repeating same code blocks, and displaying the expected figures with interpretation.
|
|
||
| Getting started | ||
| ---------------- | ||
| To get started with EEGPhasePy, you will first need to install the packaeg from PyPi by running the command: |
There was a problem hiding this comment.
Adding a note that scipy, matplotlib packages are required would help
There was a problem hiding this comment.
Added a requirements.txt file to auto-install these packages
| .. code-block:: Python | ||
| :caption: Install EEGPhasePy | ||
|
|
||
| pip install eegphasepy |
There was a problem hiding this comment.
this prompt automatically installs version 0.02. Suggest revising to the following to explicitly download latest version:
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple eegphasepy==0.0.4
There was a problem hiding this comment.
I think this is related to the fact that I skipped version 0.0.3. When publishing on public pypi will ensure continuity in version numbers
| import numpy as np | ||
| import scipy.signal as signal | ||
|
|
||
| from EEGPhasePy.estimators import ETP |
There was a problem hiding this comment.
This could not be recognized in Python 3.10.
Using Python 3.11 allowed this to work.
There was a problem hiding this comment.
Removed typing issue that caused this issue in 3.10 and below. EEGPhasePy should be compatible with Python 3.9 and above. Thinking of also adding a job for each major Python version supported. Looks like MNE does something similar https://github.com/mne-tools/mne-python/blob/main/.github/workflows/tests.yml, will update our GH actions to do this as well
| while window_start + window_len < len(testing_signal): | ||
| window = testing_signal[window_start:window_start + window_len] | ||
|
|
||
| if window_start + window_len + etp.predict(window, 0) < len(testing_signal) - window_len: |
There was a problem hiding this comment.
In Python 3.10, etp.predict(window, 0) gave the error: raise TypeError: Value must be a float type. Fix was to change to etp.predict(window, 0.0)
In Python 3.11 however, this error did not occur
There was a problem hiding this comment.
Updated to allow both float and int
| window_start += window_step | ||
|
|
||
| polar_hist_fig = etp.polar_histogram_from_triggers(testing_signal, triggers) | ||
| waveform_fig = etp.plot_mean_std_waveform_from_triggers(testing_signal, triggers, tmin=0.1, tmax=0.1) |
There was a problem hiding this comment.
It would be helpful to display the output figures on the tutorial itself, followed by a brief interpretation of each figure for the reader to understand)
| # | ||
| # *A note on window length and edge effects:* | ||
| # | ||
| # When looking at other frequency bands that have more phase instability (as is the case with theta) or in cases of lower SNR, such as extracting phase data from the leisoned cortex of stroke patients, adjusting the window length and edge removed can significantly improve ETP performance. By increasing window length, more data is included in the sliding window allowing for a higher order filter to be used. This will help to more effectively extract the "signal" from "noise". However, increasing filter order will also increase the amount of data that is effected by filtering. Thus, you will need to increase the amount of signal removed from the edge of the window to compensate. This can potentially compromise ETP performance. It is important to balance all 3 of these factors |
There was a problem hiding this comment.
affected* instead of effected
|
|
||
| if window_i + window_len + etp.predict(window, 0) < len(testing_signal) - 2*window_len: | ||
| triggers.append(window_i + window_len + | ||
| etp.predict(window, (3/2)*np.pi)) |
There was a problem hiding this comment.
It may be helpful to put a comment on this line, along the lines of: # change the target phase to your desired value here
| print("Accuracy: " + str(100*accuracy)) | ||
|
|
||
| # %% [markdown] | ||
| # Similarly, to target troughs, instead of peaks, we just change the `target_phase` to :math:`\pi` |
There was a problem hiding this comment.
Repeating this whole block seems redundant, when the only change being made is to one line to adjust target phase. Would it be better to just list the one line where the change is made? Perhaps this whole chunk of code being unchanged can just be displayed once on this page, and subsequent changes to the triggers.append(window_i + window_len + etp.predict(window, np.pi)) line can just be displayed.
| print("Accuracy: " + str(100*accuracy)) | ||
|
|
||
| # %% [markdown] | ||
| # Above, we estimated peaks using ETP. However, with ETP you can easily estimate any other phase by changing the `target_phase` argument to the desired phase in radians. Below we estimate alpha rising phase by simply changing `target_phase` to :math:`3\pi/2` |
There was a problem hiding this comment.
Same comment as below. Repeating this whole block of code seems redundant and can be viewed as a unnecessary burden for the reader. Better approach may be to just show the one line where the change is made, and list the rest of this needed code once above.
| triggers.append(window_i + window_len + etp.predict(window, np.pi)) | ||
| window_i += window_step | ||
|
|
||
| polar_hist_fig = etp.polar_histogram_from_triggers(testing_signal, triggers) |
There was a problem hiding this comment.
It would be helpful for the output figures to be displayed, and a brief interpretation offered for each.
mus1223
left a comment
There was a problem hiding this comment.
Review done for the following pages:
Alpha phase estimation using ETP
Autoregressive phase estimation
Bayesian optimization of PHASTIMATE parameters
Genetic optimization of PHASTIMATE parameters
Alpha phase estimation using PHASTIMATE
Optimizing PHASTIMATE parameters
Some main points are adding further explanation/rationale when needed, adding the output figures/stats themselves on to the tutorial along with their explanation, some potential inaccuracies with the stats values printed in some examples, and other considerations.
| filter edge effects and tens of milliseconds ahead of the current point in time, to account for Hilbert transform edge effects. AR was first introduced | ||
| by :cite:t:`Zrenner2018-hh` and a toolbox for AR phase estimation known as PHASTIMATE was published by :cite:t:`Zrenner2020-zb` | ||
|
|
||
| AR is best used in environments that make gaurentees about communication delays as foreward forecasting beyond the current point in time significantly reduces |
|
|
||
| AR is best used in environments that make gaurentees about communication delays as foreward forecasting beyond the current point in time significantly reduces | ||
| AR phase estimation performance. Further, to the best of our knowledge, AR has not been applied as a phase estimation algorithm in environments | ||
| where delay gaurentees were made. It is also important to note that AR requires a high packet send rate from your EEG amplifier. This is due to the |
| time_data = np.arange(0, 200, 1/fs) | ||
|
|
||
| signal_clean = np.sin(2 * np.pi * 10 * time_data) | ||
| signal_noisy = training_signal_clean + np.random.normal(0, 2, len(time_data)) |
There was a problem hiding this comment.
the 'training_signal_clean' should just be 'signal_clean'
| window_start += window_step | ||
|
|
||
| polar_hist_fig = phastimate.polar_histogram_from_triggers(signal_noisy, triggers) | ||
| waveform_fig = phastimate.plot_mean_std_waveform_from_triggers(signal_noisy, triggers, tmin=0.1, tmax=0.1) |
There was a problem hiding this comment.
Displaying both output figures on the tutorial page, along with an explanation/interpretation of the figures would help.
|
|
||
| AR is best used in environments that make gaurentees about communication delays as foreward forecasting beyond the current point in time significantly reduces | ||
| AR phase estimation performance. Further, to the best of our knowledge, AR has not been applied as a phase estimation algorithm in environments | ||
| where delay gaurentees were made. It is also important to note that AR requires a high packet send rate from your EEG amplifier. This is due to the |
| signal_noisy, triggers, degree=True) | ||
| accuracy = phastimate.phase_accuracy_from_triggers(signal_noisy, triggers, 270) | ||
|
|
||
| print("Mean phase: " + str(phase_mean)) |
There was a problem hiding this comment.
My output:
Mean phase: 1.5927433244327647 --> is this in degrees? if so, shouldn't it be closer to 270?
Std phase: 1.8415718460550798
Accuracy: 99.527849327296
| signal_noisy, triggers, degree=True) | ||
| accuracy = phastimate.phase_accuracy_from_triggers(signal_noisy, triggers, 180) | ||
|
|
||
| print("Mean phase: " + str(phase_mean)) |
There was a problem hiding this comment.
My output:
Mean phase: 5.507510749476268 --> Is this in degrees? Shouldn't it be closer to 180?
Std phase: 2.3668169640597494
Accuracy: 99.85186634671345
| testing_signal, triggers, degree=True) | ||
| accuracy = etp.phase_accuracy_from_triggers(testing_signal, triggers, 270) | ||
|
|
||
| print("Mean phase: " + str(phase_mean)) |
There was a problem hiding this comment.
My output:
Mean phase: 2.5134114207524596 --> is this in radians? if so, shouldn't the numeric value be closer to 3pi/2?
Std phase: 3.266817681123747
Accuracy: 99.9852204969867
| testing_signal, triggers, degree=True) | ||
| accuracy = etp.phase_accuracy_from_triggers(testing_signal, triggers, 180) | ||
|
|
||
| print("Mean phase: " + str(phase_mean)) |
There was a problem hiding this comment.
My output:
Mean phase: 1.0619444569613414 --> is this in radians? If so, shouldn't the numeric value be close to pi?
Std phase: 2.707560758360248
Accuracy: 99.98973798577927
| @@ -0,0 +1,14 @@ | |||
| Optimizing PHASTIMATE parameters | |||
There was a problem hiding this comment.
I'd suggest just making this a subpage under 'Autoregressive phase estimation' for better organization.
mus1223
left a comment
There was a problem hiding this comment.
Completed review for the following pages:
Plotting
Phase estimation statistics
Polar histograms
API reference
Main comments including providing more explanation where needed, typos, and missing information (particularly on API reference page)
|
|
||
| Here you can find the API reference for EEGPhasePy. | ||
|
|
||
| Estimators |
There was a problem hiding this comment.
There is nothing displayed below this heading
| :inherited-members: | ||
|
|
||
|
|
||
| Visualization |
There was a problem hiding this comment.
There is nothing displayed below this heading
| .. code-block:: Python | ||
| :caption: Plotting polar histogram | ||
|
|
||
| polar_hist_fig = etp.polar_histogram_from_triggers(testing_signal, triggers) |
There was a problem hiding this comment.
Perhaps further instructions on modifying the look of the figure (if the user desires) can be helpful (e.g. colour, range of numbers to be displayed)
| ------------------ | ||
| Plotting the average waveform with standard deivation highlights follows similar logic to the polar histogram. With the waveform average | ||
| however, you must specify how much time before and after the stimulus your plot should show. The time before stimulus delievery can be specified | ||
| using ``tmin`` (always positive, larger values will encompass a greater amount of pre-stimulus time) and ``tmax``. You can |
There was a problem hiding this comment.
You can say "(for e.g. tmin = 0.01 sec indicates 0.01 sec before t=0 sec)"
|
|
||
| Accuracy | ||
| --------- | ||
| Accuracy is defined as 50% begin completely random, 0% begin completely opposite to the target phase and 100% being exactly at the target phase. |
| Accuracy | ||
| --------- | ||
| Accuracy is defined as 50% begin completely random, 0% begin completely opposite to the target phase and 100% being exactly at the target phase. | ||
| We calculate accuracy based no the equation described by :cite:t:`Shirinpour2020-ef`. You can specify the target phase when you call the :py:meth:`EEGPhasePy.estimators.Estimator.phase_accuracy_from_triggers` |
| .. code-block:: Python | ||
| :caption: Computing accuracy to target phase from triggers | ||
|
|
||
| accuracy = etp.phase_accuracy_from_triggers(testing_signal, triggers, 0) |
There was a problem hiding this comment.
Add a comment: "in this example, the target phase is 0"
| fs = 2000 | ||
| time_data = np.arange(0, 200, 1/fs) | ||
|
|
||
| signal_clean = np.sin(2 * np.pi * 10 * time_data) |
There was a problem hiding this comment.
add a brief sentence explaining this block of code
| phase_data = np.angle(signal.hilbert(signal_clean)) | ||
|
|
||
| polar_hist = viz.plot_polar_histogram( | ||
| phase_data[peaks] + np.random.normal(0, 0.7, len(peaks))) |
There was a problem hiding this comment.
It would help to explain this line, such as: What is 0 and 0.7 representing here?
…e checking to account for float and int
This PR includes all of the base EEGPhasePy functionality:
API reference is also included in this PR using the readthedocs template