scipy / scipy

SciPy library main repository
https://scipy.org
BSD 3-Clause "New" or "Revised" License
12.79k stars 5.09k forks source link

ENH: Dedicated Function for Envelope Extraction #19634

Open yagizolmez opened 8 months ago

yagizolmez commented 8 months ago

Is your feature request related to a problem? Please describe.

It has come to my attention that scipy.signal does not have a dedicated function for extracting the envelope of a signal. It would be nice to have a function scipy.signal.envelope that implements several envelope extraction methods. MATLAB Signal Processing Toolbox has this functionality:

https://www.mathworks.com/help/signal/ref/envelope.html

Describe the solution you'd like.

I could implement this. I do not have experience contributing to open source, but this would be a good start.

Describe alternatives you've considered.

No response

Additional context (e.g. screenshots, GIFs)

No response

yagizolmez commented 7 months ago

I would like to follow up on this. Could you assign me this issue, if this is a function you would want to have?

lucascolley commented 7 months ago

Hi @yagizolmez, please see the docs for contributing new code. The first step is to put your proposal to the mailing list.

gideonKogan commented 7 months ago

Seems like Matlab's function is a wrapper. Some of the functionalities that are suggested are implemented in signal.hilbert or pandas. @yagizolmez, are you suggesting to create a wrapper?

ilayn commented 7 months ago

Regardless of the algorithm, please make sure that you do not check matlab's sourcecode (not sure if it is even possible) but same with octave and other incompatible licensed sources. It is OK to have a similar function signature but we cannot accept a derivative of such codebase and/or algorithm.

yagizolmez commented 7 months ago

@lucascolley I shared it in the email group, but the discussion seems to be happening here.

@gideonKogan I would be happy if you could share the functions in pandas. signal.hilbert indeed implements the 'analytic' option of the MATLAB function but does not implement the other methods ('rms', 'peak').

@ilayn Thank you for the warning. I will not check MATLAB's codebase.

Please let me know what you think. Happy New Year to all of you and thanks for commenting.

ggkogan commented 7 months ago
import pandas as pd
import numpy as np

fs = 1000
time = 10
freq = 1
window = 50
t = np.arange(time * fs) / fs

x = pd.Series((np.sin(2 * np.pi * freq * t) + 1) * np.random.normal(size=t.size))

ax = x.plot() #original
((x**2).rolling(window=window).mean()**0.5).plot(ax = ax) #rms
x.rolling(window=window).max().plot(ax = ax, grid=True) #peak

image

yagizolmez commented 7 months ago

@ggkogan Thanks for sharing this. Your solution looks sound to me, but as far as I know scipy does not have a dependency on pandas. Would people be OK to add such dependency for this one function? @ilayn

Your code also needs to be slightly modified to support upper and lower envelopes.

We could also add additional envelope extraction methods that are not covered by the MATLAB function such as rectify and low pass filter (also known as linear envelope), which is commonly used in EMG analysis.

rkern commented 7 months ago

No, we will not be depending on pandas.

yagizolmez commented 7 months ago

@ggkogan, @rkern I have written a function that depends only on numpy and scipy, that follows MATLAB's function signature, and seems to work well.

import numpy as np
from scipy.signal import hilbert,find_peaks
from scipy.interpolate import UnivariateSpline

def envelope(x,N = None,method = 'analytic'):

    #Assert that x is a 1D numpy array
    assert isinstance(x, np.ndarray) and x.ndim == 1, 'x must be a 1D NumPy array'

    if method == 'analytic':

        #Calculate the mean of x and remove it
        x_mean = np.mean(x)
        x_zero_mean = x-x_mean

        #Take the absolute value of the Hilbert transform
        #to calculate the analytical envelope of the zero mean
        #version of x
        zero_mean_envelope = np.abs(hilbert(x_zero_mean,N = N))

        #Calculate and return upper and lower envelopes
        return x_mean+zero_mean_envelope,x_mean-zero_mean_envelope

    elif method == 'rms':

        #Assert that N is specified and is an integer
        #For the rms method N serves as window size
        #for the moving average
        assert N and isinstance(N,int), 'N must be an integer'

        #Calculate the mean of x and remove it
        x_mean = np.mean(x)
        x_zero_mean = x-x_mean

        #Calculate the RMS envelope of the zero mean version of x.
        #Moving average is calculated using np.convolve
        zero_mean_envelope = np.sqrt(np.convolve(x_zero_mean**2,np.ones(N)/N,mode = 'same'))

        #Calculate and return upper and lower envelopes
        return x_mean+zero_mean_envelope,x_mean-zero_mean_envelope

    elif method == 'peak':

        #Assert that N is specified and is an integer
        #For the peak method N serves as the minimum number of
        #samples that seperate local maxima
        assert N and isinstance(N,int), 'N must be an integer'

        #Calculate local maxima and minima which will serve
        #as peaks for upper and lower envelopes respectively
        peaks_upper,_ = find_peaks(x,distance = N)
        peaks_lower,_ = find_peaks(-x,distance = N)

        #Calculate upper and lower envelopes by interpolating
        #peaks using a Univariate spline
        upper_spline = UnivariateSpline(peaks_upper,x[peaks_upper])
        lower_spline = UnivariateSpline(peaks_lower,x[peaks_lower])
        upper_envelope = upper_spline(np.arange(x.shape[0]))
        lower_envelope = lower_spline(np.arange(x.shape[0]))

        #Return the envelopes
        return upper_envelope,lower_envelope

    else:

        raise ValueError('%s is not a valid method' %(method))

Test for analytical envelope extraction:

import matplotlib.pyplot as plt

t = np.arange(0, 2, 1/2000)
signal = (1+0.5*np.cos(2*np.pi*1*t))*np.cos(2*np.pi*10*t)+5
upper,lower = envelope(signal)

plt.plot(t, signal)
plt.plot(t, upper)
plt.plot(t, lower)
plt.show()

analytic

Test for RMS envelope extraction:

t = np.arange(0, 2, 1/2000)
signal = (1+0.5*np.cos(2*np.pi*1*t))*np.random.normal(size = t.shape[0])+5

upper,lower = envelope(signal,N = 150,method = 'rms')

plt.plot(t, signal)
plt.plot(t, upper)
plt.plot(t, lower)
plt.show()

rms

Test for peak envelope extraction

from scipy.io.wavfile import read

fs,signal = read('FK61_01.wav')
t = np.linspace(1,signal.shape[0],signal.shape[0])/fs

upper,lower = envelope(signal,N = 200,method = 'peak')

plt.plot(t, signal)
plt.plot(t, upper)
plt.plot(t, lower)
plt.show()

peak

Please let me know what you think!

gideonKogan commented 7 months ago

@yagizolmez your analytic filtering should be in time domain. There might appear other deviatios. I suggest you try to test for bit-exactness.

Are we sure that we want to replicate Matlab's function? Why?

I am not sure whether we should try to reduce running time by Cython implementation.

yagizolmez commented 7 months ago

@ggkogan I do not understand your comment about the analytical filtering. I simply use signal.hilbert as you suggested in your first comment. I would be happy if you could be a bit more specific or provide some code.

I think using similar function signature with MATLAB would make it easier for people to migrate their codes into scipy. MATLAB Signal Processing Toolbox is unfortunately still more efficient than scipy.signal and it remains more widely used especially in the sciences. Many functions in scipy.signal have the same signature as their MATLAB counter parts (hilbert,butter,filtfilt,chirp etc).

Admittedly, I do not have much experience with cython. However, I would like to note that the code I provided does not have any python level loops. It relies on other functions in numpy and scipy. Considering that numpy is already partially written in C, I don't know how much of an improvement cython would provide.

ilayn commented 7 months ago

I think using similar function signature with MATLAB would make it easier for people to migrate their codes into scipy

That is correct but we all are in a way, past matlab survivors. Being similar is good but if matlab signature is not good we don't insist on the affinity. This is particularly the case especially for matlab's legacy functions, wrong defaults, terrible option names and so on.

Admittedly, I do not have much experience with cython

Let's get there when it is needed; we can help you with all that. A bit of benchmarking would go a long way, even if it is done on a jupyter notebook with %timeit magic.

gideonKogan commented 7 months ago

@yagizolmez signal.hilbert gives envelope but if we try to reproduce the envelope you presented, we should implement in time domain. I suspect that the original implementation was designed to maintain causality, which is not maintained by using signal.hilbert.

yagizolmez commented 7 months ago

@gideonKogan Do you suggest that I should implement the 'analytical' option with a causal filter? Causal filters introduce lags, so the filtered envelope would be lagged. I don't think we should do that.

@ilayn I personally like the MATLAB signature in this case, but I am open to suggestions. I will post the benchmarks shortly.

yagizolmez commented 7 months ago

@ilayn I did some benchmarking on the code I have provided. %timeit gave the following outputs for the analytic, rms and peak respectively:

202 µs ± 16.3 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each) 210 µs ± 3.45 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) 122 ms ± 18.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

I have written the following code to benchmark the MATLAB function


%analytic

% Your code block here
t = 0:1/2000:2;
signal = (1 + 0.5 * cos(2 * pi * 1 * t)) .* cos(2 * pi * 10 * t) + 5;

% Number of iterations
numIterations = 1000;

% Measure execution time for multiple runs
totalTime = 0;
for i = 1:numIterations
    tic;
    upper = envelope(signal);
    elapsedTime = toc;
    totalTime = totalTime + elapsedTime;
end

% Calculate the average execution time
averageTime = totalTime / numIterations;

% Display the average execution time
fprintf('Average execution time of analytic over %d iterations: %.2f microseconds\n', numIterations, averageTime*10^6);

%rms

t = 0:1/2000:2; % Create a time vector from 0 to 2 with a step of 1/2000
signal = (1 + 0.5 * cos(2 * pi * 1 * t)) .* randn(size(t)) + 5; % Define the signal using random normal distribution

% Measure execution time for multiple runs
totalTime = 0;
for i = 1:numIterations
    tic;
    upper = envelope(signal,150,'rms');
    elapsedTime = toc;
    totalTime = totalTime + elapsedTime;
end

% Calculate the average execution time
averageTime = totalTime / numIterations;

% Display the average execution time
fprintf('Average execution time of rms over %d iterations: %.2f microseconds\n', numIterations, averageTime*10^6);

%peak

% Read the WAV file
[signal, fs] = audioread('FK61_01.wav');

% Create a time vector
t = (1:length(signal)) / fs;

% Measure execution time for multiple runs
totalTime = 0;
for i = 1:numIterations
    tic;
    upper = envelope(signal,200,'peak');
    elapsedTime = toc;
    totalTime = totalTime + elapsedTime;
end

% Calculate the average execution time
averageTime = totalTime / numIterations;

% Display the average execution time
fprintf('Average execution time of peak over %d iterations: %.2f milliseconds\n', numIterations, averageTime*10^3);

The output of this code was:

Average execution time of analytic over 1000 iterations: 572.22 microseconds Average execution time of rms over 1000 iterations: 130.67 microseconds Average execution time of peak over 1000 iterations: 40.49 milliseconds

The peak method seems to be 3x faster in MATLAB. Please let me know what you think.

gideonKogan commented 7 months ago

@yagizolmez , I argue that we should decide if we want Matlab to be our reference. If we do, we should aim to bit-accurate. If we do not, we should decide how the function should be and ignore Matlab's implementation. As far as I understand, your motivation is copying Matlab, right? In such a case, we should consider the deviation...

rkern commented 7 months ago

Compatibility with MATLAB, either in terms of naming, function arguments, or bit-accuracy is an explicit non-goal of the scipy project. Looking at MATLAB is at most market research: we can infer from the presence of an envelope function with those three methods that having those three methods exposed is desirable. Once we get that nugget of information, we start from scratch and ignore the details of the MATLAB implementation. If warranted, we can sometimes use MATLAB to generate reference values for our tests, but bit-level accuracy is unnecessary (which is good, because it's unattainable in general).

yagizolmez commented 7 months ago

I have worked on this a little, bit and I need some help. Please see the commit in my fork:

https://github.com/scipy/scipy/compare/main...yagizolmez:scipy:signal-envelope?expand=1

I have not opened a pull request yet, because I have some problems:

  1. When I first ran 'python dev.py doc' in my local machine, I was able to generate the docs, but the documentation for my function was missing. When I tried again, I have got the following error:

Traceback (most recent call last): File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/events.py", line 97, in emit results.append(listener.handler(self.app, *args)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/pydata_sphinx_theme/init.py", line 145, in update_templates if theme_css_name in context["css_files"]: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/html/_assets.py", line 40, in eq warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. ' sphinx.deprecation.RemovedInSphinx90Warning: The str interface for _CascadingStyleSheet objects is deprecated. Use css.filename instead.

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/cmd/build.py", line 298, in build_main app.build(args.force_all, args.filenames) File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/application.py", line 355, in build self.builder.build_update() File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/init.py", line 293, in build_update self.build(to_build, File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/init.py", line 363, in build self.write(docnames, list(updated_docnames), method) File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/init.py", line 571, in write self._write_serial(sorted(docnames)) File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/init.py", line 581, in _write_serial self.write_doc(docname, doctree) File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/html/init.py", line 655, in write_doc self.handle_page(docname, ctx, event_arg=doctree) File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/builders/html/init.py", line 1107, in handle_page newtmpl = self.app.emit_firstresult('html-page-context', pagename, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/application.py", line 492, in emit_firstresult return self.events.emit_firstresult(event, args, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/events.py", line 118, in emit_firstresult for result in self.emit(name, args, allowed_exceptions=allowed_exceptions): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/yagizolmez/miniforge3/envs/scipy-dev/lib/python3.11/site-packages/sphinx/events.py", line 108, in emit raise ExtensionError(__("Handler %r for event %r threw an exception") % sphinx.errors.ExtensionError: Handler <function update_templates at 0x7fa2e5df1a80> for event 'html-page-context' threw an exception (exception: The str interface for _CascadingStyleSheet objects is deprecated. Use css.filename instead.)

I am thinking that I initially failed to create documentation for my function due to lines 4702,4703:

signal = (1+0.5np.cos(2np.pi1t))* np.random.normal(size = t.shape[0])+5

Later I fixed this, but I am still getting the error. This is my first time working with docstrings, so any help would be appreciated.

  1. Is there any audio dataset in scipy that I can use for the documentation? I think the 'peak' method would be best demonstrated with audio.

  2. I have tested all three methods locally using the examples I have provided earlier. I have also written some unit tests and added them to my commit. These tests only check for correct inputs, and they pass. Do you have any recommendations for any other tests?

  3. In the documentation, I give the default value for the 'analytic' method as 'x.shape[0]' based on hilbert's default. Is this good practice? Should I change the code and set N = x.shape[0] for hilbert, so that my documentation would not be effected even if hilbert's default changes.

  4. I am currently importing find_peaks within the envelope function. Doing this outside the function throws a circular import error. Is there a better way to handle circular imports? This is also something that I am not really familiar with.

These are my questions for now. Sorry for the long post. I am looking forward to your responses.

lucascolley commented 7 months ago
  1. I also saw an error like this last night so chances are it is unrelated to your changes. I think it should be fixed by upgrading version of the theme after gh-16660 (in the meantime, I think downgrading your version of sphinx might work).

  2. I don't think so - see http://scipy.github.io/devdocs/reference/datasets.html

yagizolmez commented 7 months ago

@lucascolley Downgrading sphinx by one version did not work. I tried deleting doc/build, and building the whole html again. This worked, but it is too time consuming. Also, the documentation still does not include the new envelope function I have added. Do you have any idea why this might be? My terminal's output is as follows:

💻  ninja -C /home/yagizolmez/git-repos/scipy/build -j8
ninja: Entering directory `/home/yagizolmez/git-repos/scipy/build'
[4/4] Generating scipy/generate-version with a custom command
Build OK
💻  meson install -C build --only-changed
Installing, see meson-install.log...
Installation OK
# for testing
# @echo installed scipy 58e580e matches git version 58e580e; exit 1
mkdir -p build/html build/doctrees
LANG=C /home/yagizolmez/miniforge3/envs/scipy-dev/bin/python -msphinx -WT --keep-going  -b html -d build/doctrees -j1  source build/html 
Running Sphinx v7.2.5
SciPy (VERSION 1.13.0.dev)
[autosummary] generating autosummary for: building/blas_lapack.rst, building/compilers_and_options.rst, building/cross_compilation.rst, building/distutils_equivalents.rst, building/index.rst, building/introspecting_a_build.rst, building/redistributable_binaries.rst, building/understanding_meson.rst, dev/api-dev/api-dev-toc.rst, dev/api-dev/array_api.rst, ..., tutorial/stats/discrete_zipf.rst, tutorial/stats/discrete_zipfian.rst, tutorial/stats/resampling.rst, tutorial/stats/sampling.rst, tutorial/stats/sampling_dau.rst, tutorial/stats/sampling_dgt.rst, tutorial/stats/sampling_hinv.rst, tutorial/stats/sampling_pinv.rst, tutorial/stats/sampling_srou.rst, tutorial/stats/sampling_tdr.rst
[autosummary] generating autosummary for: /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.LowLevelCallable.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.optimize.LbfgsInvHessProduct.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.optimize.OptimizeResult.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.optimize.RootResults.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.bsr_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.bsr_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.coo_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.coo_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.csc_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.csc_matrix.rst, ..., /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.csr_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.dia_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.dia_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.dok_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.dok_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.lil_array.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.lil_matrix.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.sparse.linalg.LaplacianNd.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.stats._result_classes.PearsonRResult.rst, /home/yagizolmez/git-repos/scipy/doc/source/reference/generated/scipy.stats._result_classes.TtestResult.rst
loading intersphinx inventory from https://docs.python.org/3/objects.inv...
loading intersphinx inventory from https://numpy.org/devdocs/objects.inv...
loading intersphinx inventory from https://numpy.org/neps/objects.inv...
loading intersphinx inventory from https://matplotlib.org/stable/objects.inv...
loading intersphinx inventory from https://asv.readthedocs.io/en/stable/objects.inv...
loading intersphinx inventory from https://www.statsmodels.org/stable/objects.inv...
myst v2.0.0: MdParserConfig(commonmark_only=False, gfm_only=False, enable_extensions=set(), disable_syntax=[], all_links_external=False, url_schemes=('http', 'https', 'mailto', 'ftp'), ref_domains=None, fence_as_directive=set(), number_code_blocks=[], title_to_header=False, heading_anchors=0, heading_slug_func=None, html_meta={}, footnote_transition=True, words_per_minute=200, substitutions={}, linkify_fuzzy_links=True, dmath_allow_labels=True, dmath_allow_space=True, dmath_allow_digits=True, dmath_double_inline=False, update_mathjax=True, mathjax_classes='tex2jax_process|mathjax_process|math|output_area', enable_checkboxes=False, suppress_warnings=[], highlight_code_blocks=True)
myst-nb v1.0.0: NbParserConfig(custom_formats={}, metadata_key='mystnb', cell_metadata_key='mystnb', kernel_rgx_aliases={}, eval_name_regex='^[a-zA-Z_][a-zA-Z0-9_]*$', execution_mode='auto', execution_cache_path='', execution_excludepatterns=(), execution_timeout=30, execution_in_temp=False, execution_allow_errors=False, execution_raise_on_error=False, execution_show_tb=False, merge_streams=False, render_plugin='default', remove_code_source=False, remove_code_outputs=False, code_prompt_show='Show code cell {type}', code_prompt_hide='Hide code cell {type}', number_source_lines=False, output_stderr='show', render_text_lexer='myst-ansi', render_error_lexer='ipythontb', render_image_options={}, render_figure_options={}, render_markdown_format='commonmark', output_folder='build', append_css=True, metadata_to_fm=False)
Using jupyter-cache at: /home/yagizolmez/git-repos/scipy/doc/build/.jupyter_cache
building [mo]: targets for 0 po files that are out of date
writing output... 
building [html]: targets for 4539 source files that are out of date
updating environment: [new config] 4539 added, 0 changed, 0 removed
/home/yagizolmez/git-repos/scipy/doc/source/notebooks/interp_transition_guide.md: Executing notebook using local CWD [mystnb]
0.00s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
0.00s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
/home/yagizolmez/git-repos/scipy/doc/source/notebooks/interp_transition_guide.md: Executed notebook in 6.03 seconds [mystnb]
<string>:46: DeprecationWarning: invalid escape sequence '\,'dtr
<string>:46: DeprecationWarning: invalid escape sequence '\,'dtrc
<string>:50: DeprecationWarning: invalid escape sequence '\d'uber
<string>:30: DeprecationWarning: invalid escape sequence '\i'tairy
<string>:25: DeprecationWarning: invalid escape sequence '\i'ti0k0
<string>:25: DeprecationWarning: invalid escape sequence '\i'tj0y0
<string>:42: DeprecationWarning: invalid escape sequence '\c've
<string>:42: DeprecationWarning: invalid escape sequence '\c've
<string>:22: DeprecationWarning: invalid escape sequence '\P'dtr
<string>:50: DeprecationWarning: invalid escape sequence '\d'seudo_huber
<string>:46: DeprecationWarning: invalid escape sequence '\l'klmbda
<string>:19: DeprecationWarning: invalid escape sequence '\s'
reading sources... [100%] tutorial/stats/sampling_tdr
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... copying static files... done
copying extra files... done
done
writing output... [100%] tutorial/stats/sampling_tdr
generating indices... genindex done
writing additional pages... search done
copying images... [100%] ../build/plot_directive/tutorial/stats/sampling_srou-1.png
dumping search index in English (code: en)... done
dumping object inventory... done
build succeeded.

The HTML pages are in build/html.
lucascolley commented 7 months ago

the documentation still does not include the new envelope function I have added. Do you have any idea why this might be?

You need to add your new function to the toc in scipy/signal/__init__.py

yagizolmez commented 7 months ago

I have completed the implementation and opened a pull request. I would be happy if you could review my code!

https://github.com/scipy/scipy/pull/19814

lucascolley commented 7 months ago

Enhancement request issues typically stay open until the enhancement has been merged (or decided against) 👍