diff --git a/doc/source/README.rst b/doc/source/README.rst index 08bd26d89..78ef9a0d7 100644 --- a/doc/source/README.rst +++ b/doc/source/README.rst @@ -24,48 +24,52 @@ We strive to achieve the following goals: Getting Started --------------- -- Prerequisites: :doc:`Install Syncopy </setup>` -- Jumping right in: :doc:`Quickstart Guide <quickstart/quickstart>` + +.. toctree:: + :maxdepth: 1 + :caption: Getting Started + + Install Syncopy </setup> + Quickstart Guide <quickstart/quickstart> Want to contribute or just curious how the sausage is made? Take a look at our :doc:`Developer Guide <developer/developers>`. -In depth Guides and Tutorials ------------------------------ -* :doc:`Basic Concepts <user/concepts>` -* :doc:`Syncopy for FieldTrip Users <user/fieldtrip>` -* :doc:`Handling Data <user/data>` -* :doc:`Fooof <tutorials/fooof>` -* :doc:`Resampling <tutorials/resampling>` +Tutorials +--------- + +.. toctree:: + :maxdepth: 2 + :caption: Tutorials + + Resampling <tutorials/resampling> + Spectral Analysis <tutorials/freqanalysis> - -Auto-generate API Docs ------------------------ -* :doc:`User API <user/user_api>` +In depth Guides +--------------- + +.. toctree:: + :maxdepth: 2 + :caption: Guides + + Basic Concepts <user/concepts> + Syncopy for FieldTrip Users <user/fieldtrip> + Handling Data <user/data> + Parallel Processing <user/parallel> + Selections <user/selectdata> + +API +--- + +.. toctree:: + :maxdepth: 1 + :caption: API Reference + + User API <user/user_api> -Navigation ----------- -* :doc:`Sitemap <sitemap>` -* :ref:`genindex` -* :ref:`search` Contact ------- To report bugs or ask questions please use our `GitHub issue tracker <https://github.com/esi-neuroscience/syncopy/issues>`_. For general inquiries please contact syncopy (at) esi-frankfurt.de. - -.. Any sections to be included in the Documentation dropdown menu have to be in the toctree - -.. toctree:: - :hidden: - - quickstart/quickstart.rst - setup - user/concepts.rst - user/fieldtrip.rst - user/data.rst - user/user_api.rst - tutorials/fooof.rst - tutorials/resampling.rst - developer/developers.rst diff --git a/doc/source/_static/select_example1.png b/doc/source/_static/select_example1.png new file mode 100644 index 000000000..7ce19cea9 Binary files /dev/null and b/doc/source/_static/select_example1.png differ diff --git a/doc/source/_static/select_example2.png b/doc/source/_static/select_example2.png new file mode 100644 index 000000000..0e3479f20 Binary files /dev/null and b/doc/source/_static/select_example2.png differ diff --git a/doc/source/_static/synth_data_spec.png b/doc/source/_static/synth_data_spec.png new file mode 100644 index 000000000..2c5055bed Binary files /dev/null and b/doc/source/_static/synth_data_spec.png differ diff --git a/doc/source/_static/welch_basic_power.png b/doc/source/_static/welch_basic_power.png new file mode 100644 index 000000000..7c4de06be Binary files /dev/null and b/doc/source/_static/welch_basic_power.png differ diff --git a/doc/source/_static/welch_params.png b/doc/source/_static/welch_params.png new file mode 100644 index 000000000..3175ea531 Binary files /dev/null and b/doc/source/_static/welch_params.png differ diff --git a/doc/source/_static/welch_params.txt b/doc/source/_static/welch_params.txt new file mode 100644 index 000000000..1cddc0026 --- /dev/null +++ b/doc/source/_static/welch_params.txt @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +import syncopy as spy +import syncopy.tests.synth_data as synth_data +import numpy as np +import matplotlib.pyplot as plt + +sig_lengths = np.linspace(1000, 4000, num=4, dtype=int) +overlaps = np.linspace(0.0, 0.99, num=10) +variances = np.zeros((sig_lengths.size, overlaps.size), dtype=float) # Filled in loop below. + +foilim = [5, 200] # Frequency selection, shared between cases. +f_timwin = 0.2 # Window length in seconds, also shared. + +def get_welch_cfg(): + """ + Get a reasonable Welch cfg for testing purposes. + """ + cfg = spy.get_defaults(spy.freqanalysis) + cfg.method = "welch" + cfg.t_ftimwin = 0.5 # Window length in seconds. + cfg.toi = 0.0 # Overlap between periodograms (0.5 = 50 percent overlap). + return cfg + +for sigl_idx, sig_len in enumerate(sig_lengths): + for overl_idx, overlap in enumerate(overlaps): + wn = synth_data.white_noise(nTrials=20, nChannels=1, nSamples=sig_len, samplerate=1000) + + cfg = get_welch_cfg() + cfg.toi = overlap + cfg.t_ftimwin = f_timwin + cfg.foilim = foilim + + spec = spy.freqanalysis(cfg, wn) + + # We got one Welch estimate per trial so far. Now compute the variance over trials: + spec_var = spy.var(spec, dim='trials') + mvar = np.mean(spec_var.show(channel=0)) # We get one variance per frequency bin, and average over those. + variances[sigl_idx, overl_idx] = mvar + +fig = plt.figure() +ax = fig.add_subplot(projection='3d') +for row_idx in range(variances.shape[0]): + ax.scatter(np.tile(sig_lengths[row_idx], overlaps.size), overlaps, variances[row_idx, :], label=f"Signal len {sig_lengths[row_idx]}") +ax.set_xlabel('Signal length (number of samples)') +ax.set_ylabel('Window overlap') +ax.set_zlabel('var of Welch estimate') +ax.set_title('Variance of Welsh estimate as a function of signal length and overlap.\nColors represent different signal lengths.') \ No newline at end of file diff --git a/doc/source/_static/welch_raw_fft_power.png b/doc/source/_static/welch_raw_fft_power.png new file mode 100644 index 000000000..944fc66a4 Binary files /dev/null and b/doc/source/_static/welch_raw_fft_power.png differ diff --git a/doc/source/_static/workFlow.png b/doc/source/_static/workFlow.png new file mode 100644 index 000000000..d8bd9e841 Binary files /dev/null and b/doc/source/_static/workFlow.png differ diff --git a/doc/source/conf.py b/doc/source/conf.py index e025c8f3d..bcde77fa4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -15,14 +15,13 @@ import os import sys sys.path.insert(0, os.path.abspath(".." + os.sep + ".." + os.sep)) -import sphinx_bootstrap_theme import syncopy # -- Project information ----------------------------------------------------- project = 'Syncopy' copyright = '2020, Joscha Schmiedt and Stefan Fuertinger' -author = 'Joscha Schmiedt, Stefan Fuertinger and Gregor Mönke' +author = 'Joscha Schmiedt, Stefan Fuertinger, Tim Schäfer and Gregor Mönke' # The short X.Y version version = syncopy.__version__ @@ -52,6 +51,7 @@ 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram', 'sphinx_automodapi.automodapi', + 'sphinx.ext.graphviz', ] autodoc_default_options = { @@ -110,34 +110,19 @@ def setup(app): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# -html_theme = 'bootstrap' -html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() +# see https://sphinx-book-theme.readthedocs.io +html_theme = 'sphinx_book_theme' + # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # -html_logo = "_static/syncopy_icon.png" +html_logo = "_static/syncopy_logo.png" html_theme_options = { - "navbar_title": "Syncopy", - "navbar_site_name": "Documentation", - # Render the next and previous page links in navbar. (Default: true) - 'navbar_sidebarrel': False, - # Render the current pages TOC in the navbar. (Default: true) - 'navbar_pagenav': False, - # Tab name for the current pages TOC. (Default: "Page") - 'navbar_pagenav_name': "Current Page", - - # Global TOC depth for "site" navbar tab. (Default: 1) - # Switching to -1 shows all levels. - 'globaltoc_depth': 2, - # Currently, the supported themes are: - # - Bootstrap 3: https://bootswatch.com/3 - 'bootswatch_theme': "simplex", - 'navbar_links': [ - ("GitHub", "https://www.github.com/esi-neuroscience/syncopy", True), - ], + "repository_url": "https://github.com/esi-neuroscience/syncopy", + "use_issues_button": True, + } # Add any paths that contain custom static files (such as style sheets) here, @@ -207,7 +192,7 @@ def setup(app): # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Syncopy.tex', 'Syncopy Documentation', - 'Gregor Mönke, Joscha Schmiedt and Stefan Fuertinger', 'manual'), + 'Gregor Mönke, Tim Schäfer, Joscha Schmiedt and Stefan Fuertinger', 'manual'), ] diff --git a/doc/source/quickstart/quickstart.rst b/doc/source/quickstart/quickstart.rst index 19acf6c46..f604185b5 100644 --- a/doc/source/quickstart/quickstart.rst +++ b/doc/source/quickstart/quickstart.rst @@ -29,11 +29,15 @@ With this we have a dataset of type :class:`~syncopy.AnalogData`, which is inten By construction, we made the (white) noise of the same strength as the signal, hence by eye the oscillations present in channel1 are hardly visible. The parameter ``latency`` defines a time-interval selection here. +.. hint:: + How to plot and work with subsets of Syncopy data is described in :ref:`selections`. + To recap: we have generated a synthetic dataset white noise on both channels, and channel1 additionally carries the damped harmonic signal. .. hint:: Further details about artificial data generation can be found at the :ref:`synth_data` section. + Data Object Inspection ====================== @@ -52,7 +56,7 @@ which gives nicely formatted output: cfg : dictionary with keys '' channel : [2] element <class 'numpy.ndarray'> container : None - data : 50 trials of length 1000.0 defined on [50000 x 2] float64 Dataset of size 1.14 MB + data : 50 trials of length 1000.0 defined on [50000 x 2] float32 Dataset of size 1.14 MB dimord : time by channel filename : /xxx/xxx/.spy/spy_910e_572582c9.analog mode : r+ @@ -231,7 +235,6 @@ To have a synthetic albeit meaningful dataset to illustrate the different method We also right away calculated the respective power spectra ``spec``. We can quickly have a look at a snippet of the generated signals:: - data.singlepanelplot(trials=0, latency=[0, 0.5]) diff --git a/doc/source/scripts/select_example.py b/doc/source/scripts/select_example.py new file mode 100644 index 000000000..0585ced4a --- /dev/null +++ b/doc/source/scripts/select_example.py @@ -0,0 +1,17 @@ +import numpy as np +import syncopy as spy +from syncopy.tests import synth_data + +# 100 trials of two phase diffusing signals with 40Hz +adata = synth_data.phase_diffusion(nTrials=100, + freq=40, + samplerate=200, + nSamples=500, + nChannels=2, + eps=0.01) + +# coherence for full dataset +coh1 = spy.connectivityanalysis(adata, method='coh') + +# plot coherence of channel1 vs channel2 +coh1.singlepanelplot(channel_i='channel1', channel_j='channel2') diff --git a/doc/source/scripts/synth_data1.py b/doc/source/scripts/synth_data1.py index 4209f4e64..57d70cd14 100644 --- a/doc/source/scripts/synth_data1.py +++ b/doc/source/scripts/synth_data1.py @@ -7,30 +7,30 @@ def generate_noisy_harmonics(nSamples, nChannels, samplerate): f1, f2 = 20, 50 # the harmonic frequencies in Hz # the sampling times vector - tvec = np.arange(nSamples) * 1 / samplerate + tvec = np.arange(nSamples) * 1 / samplerate # define the two harmonics - harm1 = np.cos(2 * np.pi * f1 * tvec) - harm2 = np.cos(2 * np.pi * f2 * tvec) - + ch1 = np.cos(2 * np.pi * f1 * tvec) + ch2 = np.cos(2 * np.pi * f2 * tvec) + + # concatenate channels to to trial array + trial = np.column_stack([ch1, ch2]) + # add some white noise - trial = 0.5 * np.random.randn(nSamples, nChannels) - # add 1st harmonic to 1st channel - trial[:, 0] += harm1 - # add 2nd harmonic to 2nd channel - trial[:, 1] += 0.5 * harm2 - + trial += 0.5 * np.random.randn(nSamples, nChannels) + return trial nTrials = 50 nSamples = 1000 -nChannels = 3 +nChannels = 2 samplerate = 500 # in Hz +# collect trials trials = [] for _ in range(nTrials): trial = generate_noisy_harmonics(nSamples, nChannels, samplerate) trials.append(trial) - + synth_data = spy.AnalogData(trials, samplerate=samplerate) diff --git a/doc/source/setup.rst b/doc/source/setup.rst index 2e29e5f6b..90d32563e 100644 --- a/doc/source/setup.rst +++ b/doc/source/setup.rst @@ -21,7 +21,7 @@ Installing parallel processing engine ACME -------------------------------------------- To harness the parallel processing capabilities of Syncopy -it is necessary to install `ACME <https://github.com/esi-neuroscience/acme>`_. +it is helpful to install `ACME <https://github.com/esi-neuroscience/acme>`_. Again either via conda @@ -35,6 +35,8 @@ or pip pip install esi-acme +.. note:: + See :ref:`parallel` for details about parallel processing setup Importing Syncopy ----------------- @@ -59,25 +61,6 @@ To display your Syncopy version, run: spy.__version__ - -.. _start_parallel: - -Starting Up Parallel Workers ----------------------------- - -In Syncopy all computations are designed to run in parallel taking advantage of -modern multi-core system architectures. The simplest way to leverage any available -concurrent processing hardware is to use the `parallel` keyword, e.g., - -.. code-block:: python - - spy.freqanalysis(data, method="mtmfft", parallel=True) - -This will allocate a parallel worker for each trial defined in `data`. If your code -is running on the ESI cluster, Syncopy will automatically use the existing SLURM -scheduler, in a single-machine setup, any available local multi-processing resources -will be utilized. More details can be found in the :doc:`Data Analysis Guide <user/data>` - .. _setup_env: Setting Up Your Python Environment diff --git a/doc/source/tutorials/fooof.rst b/doc/source/tutorials/fooof.rst index 276102d47..91987d115 100644 --- a/doc/source/tutorials/fooof.rst +++ b/doc/source/tutorials/fooof.rst @@ -33,8 +33,8 @@ a single channel here (see :ref:`the Synthetic data tutorial<synth_data>` for de nSamples = 1000 samplerate = 1000 ar1_part = AR2_network(AdjMat=np.zeros(1), nSamples=nSamples, alphas=[0.9, 0], nTrials=nTrials) - pd1 = phase_diffusion(freq=30., eps=.1, fs=samplerate, nChannels=nChannels, nSamples=nSamples, nTrials=nTrials) - pd2 = phase_diffusion(freq=50., eps=.1, fs=samplerate, nChannels=nChannels, nSamples=nSamples, nTrials=nTrials) + pd1 = phase_diffusion(freq=30., eps=.1, samplerate=samplerate, nChannels=nChannels, nSamples=nSamples, nTrials=nTrials) + pd2 = phase_diffusion(freq=50., eps=.1, samplerate=samplerate, nChannels=nChannels, nSamples=nSamples, nTrials=nTrials) signal = ar1_part + .8 * pd1 + 0.6 * pd2 return signal @@ -144,5 +144,33 @@ Once more, we look at the FOOOFed spectrum: Note that the two tiny peaks have been removed. + +Programmatically accessing details on the FOOOF fit results +----------------------------------------------------------- + +All fitting results returned by FOOOF can be found in the `metadata` attribute of the returned `SpectralData` instance, which is a `dict`. This +includes the following entries: + +* aperiodic_params +* error +* gaussian_params +* n_peaks +* r_squared + +Please see the `official FOOOF documentation <https://fooof-tools.github.io/fooof/generated/fooof.FOOOF.html#fooof.FOOOF>`_ for the meaning. + +Note that in Syncopy, FOOOF can be run several times in a single frontend call (e.g., if you have several trials and `cfg.keeptrials=True`). +Therefore, you will see one instance of these fitting results *per FOOOF call* in the `metadata` dict. The trials (and chunk indices, if you used a non-default `chan_per_worker` setting) +are encoded in the keys of the metadata sub dictionaries in format `<result>__<trial>_<chunk>`. E.g., `peak_params__2__0` would be the peak params for trial 2 (and chunk 0). + +In the example above, the typical use case of trial averaging (`cfg.keeptrials=False`) was demonstrated, so FOOOF was called on the trial-averaged data (i.e., on a single trial), and only one entry is present: + +.. code-block:: python + :linenos: + + spec_fooof_tuned.metadata.aperiodic_params + # {'aperiodic_params': {'aperiodic_params__0_0': array([[0.8006], [1.4998]])} + + This concludes the tutorial on using FOOOF from Syncopy. Please do not forget to cite `Donoghue et al. 2020 <https://doi.org/10.1038/s41593-020-00744-x>`_ when using FOOOF. diff --git a/doc/source/tutorials/freqanalysis.rst b/doc/source/tutorials/freqanalysis.rst new file mode 100644 index 000000000..5773d33ed --- /dev/null +++ b/doc/source/tutorials/freqanalysis.rst @@ -0,0 +1,11 @@ +Spectral Analysis +================= + +Many signals in Neuroscience have oscillatory components, and hence an analysis in the *frequency domain* often is advised. The high-level function :func:`~syncopy.freqanalysis` offers many standard methods for spectral analysis, like multi-tapered Fourier transform or time-frequency methods like wavelet transforms. In the following we introduce each method individually: + +.. toctree:: + :maxdepth: 1 + + Welch's Method <welch> + Fooof <fooof> + diff --git a/doc/source/tutorials/resampling.rst b/doc/source/tutorials/resampling.rst index 3fc513e4f..fd91eacca 100644 --- a/doc/source/tutorials/resampling.rst +++ b/doc/source/tutorials/resampling.rst @@ -1,7 +1,7 @@ Resample data with Syncopy ========================== -Changing the sampling rate of a dataset is a common task in digital signal processing. Syncopy offers simple *downsampling* (decimation) with or without explicit low pass filtering, and the numerically more expensive *resampling* to arbitrary new sampling rates. +Changing the sampling rate of a dataset is a common task in digital signal processing. With :func:`~syncopy.resampledata` Syncopy offers simple *downsampling* (decimation) with or without explicit low pass filtering, and the numerically more expensive *resampling* to arbitrary new sampling rates. .. Note:: Our friends at FieldTrip also have a nice tutorial about resampling `here <https://www.fieldtriptoolbox.org/faq/resampling_lowpassfilter>`_ @@ -9,8 +9,8 @@ Changing the sampling rate of a dataset is a common task in digital signal proce .. contents:: Topics covered :local: -Synthetic Data --------------- +Create Example Data +------------------- To start with a clean slate, let's construct a synthetic signal with two harmonics, one at 200Hz and one at 1300Hz: @@ -58,7 +58,7 @@ Let's have a look at the new power spectrum:: .. image:: res_ds_spec.png -What happened? First we have to note that the frequency axis now goes from 0Hz to only 500Hz, which is the *new Nyquist frequency* :math:`1000Hz / 2`. We still see our expected peak at 200Hz, but there is also another one at 300Hz even though our signal never contained such oscillations! This phenomenon is called `aliasing <https://en.wikipedia.org/wiki/Aliasing>`_, basically meaning that frequencies which are present in the signal yet are higher than the Nyquist limit (:math:`1300 Hz > 500Hz`) "wrap around" and re-appear as spurious low-frequency components. This is a common problem of *digitization* of analog signals, think audio processing. The formula for the alias frequencies is: +What happened? First we have to note that the frequency axis now goes from 0Hz to only 500Hz, which is the *new Nyquist frequency* :math:`1000Hz / 2`. We still see our expected peak at 200Hz, but there is also another one at 300Hz even though our signal never contained such oscillations! This phenomenon is called `aliasing <https://en.wikipedia.org/wiki/Aliasing>`_, meaning that frequencies which are present in the signal yet are higher than the Nyquist limit (:math:`1300 Hz > 500Hz`) "wrap around" and re-appear as spurious low-frequency components. This is a common problem of *digitization* of analog signals, think audio processing. The formula for the alias frequencies is: .. math:: diff --git a/doc/source/tutorials/welch.rst b/doc/source/tutorials/welch.rst new file mode 100644 index 000000000..4421a3adb --- /dev/null +++ b/doc/source/tutorials/welch.rst @@ -0,0 +1,141 @@ +Running Welch's method for the estimation of power spectra in Syncopy +===================================================================== + +Welch's method for the estimation of power spectra based on time-averaging over short, modified periodograms +is described in the following publication (`DOI link <https://doi.org/10.1109/TAU.1967.1161901>`_): + +`Welch, P. (1967). The use of fast Fourier transform for the estimation of power spectra: +a method based on time averaging over short, modified periodograms. +IEEE Transactions on Audio and Electroacoustics, 15(2), 70-73.` + +In short, it splits the original time-domain signal into several, potentially overlapping, segments. A taper (window function) is then applied on each segment individually, +and each segment is transferred into the frequency domain by computing the FFT. Computing the squared magnitude results in one periodogram per segment. +Welch refers to these as *modified periodograms* in the publication, because of the taper that has been applied. These +powers are averaged over the windows to obtain the final estimate of the power spectrum. + +Due to the averaging, Welch's method works well with noisy data: the averaging reduces the variance of the estimator. The price to pay is a +reduced frequency resolution due to the short input segments compared to the single, full-sized signal. + +Generating Example Data +----------------------- + +Let us first prepare suitable data, we use white noise here: + +.. code-block:: python + :linenos: + + import syncopy as spy + import syncopy.tests.synth_data as synth_data + + wn = synth_data.white_noise(nTrials=2, nChannels=3, nSamples=20000, samplerate=1000) + +The return value `wn` is of type :class:`~syncopy.AnalogData` and contains 2 trials and 3 channels, +each consisting of 20 seconds of white noise: 20000 samples at a sample rate of 1000 Hz. We can show this easily: + + +.. code-block:: python + :linenos: + + wn.dimord # ['time', 'channel'] + wn.data.shape # (40000, 3) + wn.trialdefinition # array([[ 0., 20000., -1000.], [20000., 40000., -1000.]]) + + +Spectral Analysis using Welch's Method +-------------------------------------- + +We now create a config for running Welch's method and call `freqanalysis` with it: + +.. code-block:: python + :linenos: + + cfg = spy.StructDict() + cfg.method = "welch" + cfg.t_ftimwin = 0.5 # Window length in seconds. + cfg.toi = 0.5 # Overlap between windows, 0.5 = 50 percent overlap. + + welch_psd = spy.freqanalysis(cfg, wn) + + +Let's inspect the resulting `SpectralData` instance by looking at its dimensions, and then visualize it: + +.. code-block:: python + :linenos: + + welch_psd.dimord # ('time', 'taper', 'freq', 'channel',) + welch_psd.data.shape # (2, 1, 251, 3) + +The shape is as expected: + +* The `time` axis contains two entries, one per trial, because by default there is no trial averaging (`keeptrials` is `True`). With trial averaging, there would only be a single entry here. +* The `taper` axis will always have size 1 for Welch, even for multi-tapering, as tapers must be averaged for Welch's method (`keeptapers` must be `False`), as explained in the function documentation. +* The size of the frequency axis (`freq`, 251 here), i.e., the frequency resolution, depends on the signal length of the input windows and is thus a function of the input signal, `t_ftimwin` and `toi`, and potentially other settings (like a `foilim`, i.e. limiting the frequencies of interest). +* The channels are unchanged, as we receive one result per channel. + +We can also visualize the power spectrum. Here we select the first of the two trials: + +.. code-block:: python + :linenos: + + _, ax = welch_psd.singlepanelplot(trials=0, logscale=False) + +.. image:: ../_static/welch_basic_power.png + +We can see the estimated power flat spectrum for three channels of white noise. + +.. note:: + If you run the lines above in your Python interpreter but no plot window opens, you may need to first configure matplotlib for interactive plotting like this: ```import matplotlib.pyplot as plt; plt.ion()```. Then re-run the plotting commmands. + + +Available Settings +------------------ + +Many settings affect the outcome of a Welch run, including: + +* `t_ftimwin` : window length (a.k.a. segment length) in seconds. +* `toi` : overlap between windows, 0.5 = 50 percent overlap. +* `foilim` : frequencies of interest, a specific frequency range, e.g. set to ``[5, 100]`` to get results between 5 to 100 Hz. +* `taper` and `tapsmofrq` : for taper selection and multi-tapering. Note that in case of multi-tapering, the data in the windows will be averaged across the tapers first +* `keeptrials` : whether trials should be left as-is, or you want a trial-average. If ``False``, and thus trial-averaging is requested, it will happen on the raw data in the time domain, before Welch is run. + +Comparison with raw FFT +------------------------ + +Let's compare Welch's result with the raw FFT estimate:: + + fft_psd = spy.freqanalysis(wn) + fft_psd.singlepanelplot(trials=0, channel=0, logscale=False) + +.. image:: ../_static/welch_raw_fft_power.png + +The power spectral esimtate is much more noisy, meaning the variance per frequency bin is considerably larger compared to Welch's estimate. + +.. note:: + We don't need any parameters for ``freqanalysis`` here, as ``method='mtmfft'`` and ``tapsmofrq=None`` are the defaults. + +Note that the absolute power values are lower, as we have a lot more frequency bins when calculating the raw FFT at once for the unsegmented signal:: + + fft_psd.freq.shape # (10001,) + welch_psd.freq.shape # (251,) + +Syncopy normalizes spectral power per 1Hz bin, meaning that the noise power gets diluted over many more frequency bins when using the raw FFT. We can check that by summing over the frequency axis for both estimates:: + + np.sum(fft_psd.show(trials=0, channel=0)) # will be ~1 + np.sum(welch_psd.show(trials=0, channel=0)) # will also be ~1 + + +Investigating the Effects of the Overlap Parameter as a Function of Signal Length +--------------------------------------------------------------------------------- + +Here, we want to illustrate the effects of the chosen overlap between windows (`toi`), on signals of different lengths. + +For this, we investigate various combinations of signal length and overlap. For each combination, we realize several instantiations of white noise and run Welch's method to get an estimate of the power spectral density. We then compute the variance of the estimates. Here is a visualization of the result (`source <../_static/welch_params.txt>`_): + +.. image:: ../_static/welch_params.png + +From this plot we can conclude several things. First, as expected, with all settings fixed, a longer signal (and thus an increased number of segments) reduces the variance of the estimate. Second, up to a certain level (somewhere around 0.5 to 0.6), increasing the overlap also reduces the variance of the +estimator. However, if you go too high, the variance starts increasing again. The whole effect is most pronounced for short signals, but these are the typical case in neuroscience. + +The plot suggests that it may be helpful to try an overlap of around 0.5 for short signals, by setting ```cfg.toi=0.5```. + +This concludes the tutorial on using Welch's method in Syncopy. diff --git a/doc/source/user/WorkFlow.png b/doc/source/user/WorkFlow.png deleted file mode 100644 index c47952919..000000000 Binary files a/doc/source/user/WorkFlow.png and /dev/null differ diff --git a/doc/source/user/concepts.rst b/doc/source/user/concepts.rst index 93ed199d2..a8abb1387 100644 --- a/doc/source/user/concepts.rst +++ b/doc/source/user/concepts.rst @@ -18,10 +18,10 @@ General Workflow A typical analysis workflow with Syncopy might look like this: -.. image:: WorkFlow.png - :height: 320px +.. image:: /_static/workFlow.png + -We start with data import (or simply loading if already in ``.spy`` format) which will create one of Syncopy's dataypes like :class:`~syncopy.AnalogData`. Then actual (parallel) processing of the data is triggered by calling a *meta-function* (see also below), for example :func:`~syncopy.connectivityanalysis`. An analysis output often results in a different datatype, e.g. :class:`~syncopy.CrossSpectralData`. All indicated methods (:func:`~syncopy.show`, :func:`~syncopy.singlepanelplot` and :func:`~syncopy.save`) for data access are available for all of Syncopy's datatypes. Hence, at any processing step the data can be plotted, NumPy :class:`~numpy.ndarray`'s extracted or (intermediate) results saved to disc as ``.spy`` containers. +We start with data import (or simply loading if already in ``.spy`` format) which will create one of Syncopy's dataypes like :class:`~syncopy.AnalogData`. Then actual (parallel) processing of the data is triggered by calling a *meta-function* (see also below), for example :func:`~syncopy.freqanalysis`. An analysis output often results in a different datatype, e.g. :class:`~syncopy.SpectralData`. All indicated methods (:func:`~syncopy.show`, :func:`~syncopy.singlepanelplot` and :func:`~syncopy.save`) for data access are available for all of Syncopy's datatypes. Hence, at any processing step the data can be plotted, NumPy :class:`~numpy.ndarray`'s extracted or (intermediate) results saved to disc as ``.spy`` containers. .. note:: Have a look at :doc:`Data Basics <data_basics>` for further details about Syncopy's data formats and interfaces @@ -32,6 +32,7 @@ Memory Management One of the key concepts of Syncopy is mindful computing resource management, especially keeping a low **memory footprint**. In the depicted workflow, data processed :blue:`on disc` is indicated in :blue:`blue`, whereas potentially :red:`memory exhausting operations` are indicated in :red:`red`. So care has to be taken when using :func:`~syncopy.show` or the plotting routines :func:`~syncopy.singlepanelplot` and :func:`~syncopy.multipanelplot`, as these potentially pipe the whole dataset into the systems memory. It is advised to either perform some averaging beforehand, or cautiously only selecting a few channels/trials for these operations. +.. _meta_functions: Syncopy Meta-Functions ---------------------- @@ -55,64 +56,3 @@ or using a ``cfg`` configuration structure: cfg.tapsmofrq = 10; spec = spy.freqanalysis(cfg, data) - - -Serial and Parallel Processing ------------------------------- -By default, all computations in Syncopy are executed sequentially relying solely -on low-level built-in parallelization offered by external libraries like `NumPy <https://numpy.org/>`_. -The simplest way to enable full concurrency for a given Syncopy calculation -is by using the `parallel` keyword supported by all Syncopy meta-functions, i.e., - -.. code-block:: python - - spec = spy.freqanalysis(data, method="mtmfft", foilim=[1, 150], tapsmofrq=10, parallel=True) - -or - -.. code-block:: python - - cfg = spy.get_defaults(spy.freqanalysis) - cfg.method = 'mtmfft' - cfg.foilim = [1, 150] - cfg.tapsmofrq = 10 - cfg.parallel = True - spec = spy.freqanalysis(cfg, data) - -Default parallelization is over trials, additional parallelization over channels can be achieved by using the `chan_per_worker` keyword: - -.. code-block:: python - - spec = spy.freqanalysis(data, - method="mtmfft", - foilim=[1, 150], - tapsmofrq=10, - parallel=True, - chan_per_worker=40) - -This would allocate the computation for each trial and 40 channel chunk to an independent computing process. Note that the number of parallel processes is generally limited, depending on the computing resources available. Hence setting ``chan_per_worker=1`` can be actually quite inefficient when the data has say 200 channels but only 4 parallel processes are available at any given time. In general, if there are only few trials, it is safe and even recommended to set `chan_per_worker` to a fairly low number. On the other hand, depending on the compute cluster setup, being to greedy here might also spawn a lot of jobs and hence might induce long waiting times. - - -Manual Cluster Setup -~~~~~~~~~~~~~~~~~~~~ - -More fine-grained control over allocated resources and load-balancer options is available -via the routine :func:`~syncopy.esi_cluster_setup`. It permits to launch a custom-tailored -"cluster" of parallel workers (corresponding to CPU cores if run on a single machine, i.e., -laptop or workstation, or compute jobs if run on a cluster computing manager such as SLURM). -Thus, instead of simply "turning on" parallel computing via a keyword and letting -Syncopy choose an optimal setup for the computation at hand, more fine-grained -control over resource allocation and management can be achieved via running -:func:`~syncopy.esi_cluster_setup` **before** launching the actual calculation. -For example:: - - spyClient = spy.esi_cluster_setup(partition="16GBXL", n_jobs=10) - -starts 10 concurrent SLURM workers in the `16GBXL` queue if run on the ESI HPC -cluster. All subsequent invocations of Syncopy analysis routines will automatically -pick up ``spyClient`` and distribute any occurring computational payload across -the workers collected in ``spyClient``. - -.. hint:: - - If parallel processing is unavailable, have a look at :ref:`install_acme` diff --git a/doc/source/user/data_basics.rst b/doc/source/user/data_basics.rst index 74410f43b..3c36aa034 100644 --- a/doc/source/user/data_basics.rst +++ b/doc/source/user/data_basics.rst @@ -42,10 +42,11 @@ Plotting Functions Importing Data into Syncopy --------------------------- -Currently, Syncopy supports importing data from `FieldTrip raw data <https://www.fieldtriptoolbox.org/development/datastructure/>`_ format and from `NWB <https://www.nwb.org/>`_ +Currently, Syncopy supports importing data from `FieldTrip raw data <https://www.fieldtriptoolbox.org/development/datastructure/>`_ format, from `NWB <https://www.nwb.org/>`_ and `TDT <https://www.tdt.com/>`_: .. autosummary:: syncopy.io.load_ft_raw syncopy.io.load_nwb + syncopy.io.load_tdt diff --git a/doc/source/user/parallel.rst b/doc/source/user/parallel.rst new file mode 100644 index 000000000..8930d7ef4 --- /dev/null +++ b/doc/source/user/parallel.rst @@ -0,0 +1,116 @@ +.. _parallel: + +------------------------------ +Serial and Parallel Processing +------------------------------ + +.. contents:: Topics covered + :local: + +By default, all computations in Syncopy are executed sequentially relying solely +on low-level built-in parallelization offered by external libraries like `NumPy <https://numpy.org/>`_. +The simplest way to enable full concurrency for a given Syncopy calculation +is by using the `parallel` keyword supported by all Syncopy meta-functions, i.e., + +.. code-block:: python + + spec = spy.freqanalysis(data, method="mtmfft", foilim=[1, 150], tapsmofrq=10, parallel=True) + +or + +.. code-block:: python + + cfg = spy.get_defaults(spy.freqanalysis) + cfg.method = 'mtmfft' + cfg.foilim = [1, 150] + cfg.tapsmofrq = 10 + cfg.parallel = True + spec = spy.freqanalysis(cfg, data) + +Default parallelization is over trials, additional parallelization over channels can be achieved by using the `chan_per_worker` keyword: + +.. code-block:: python + + spec = spy.freqanalysis(data, + method="mtmfft", + foilim=[1, 150], + tapsmofrq=10, + parallel=True, + chan_per_worker=40) + +This would allocate the computation for each trial and 40 channel chunk to an independent computing process. Note that the number of parallel processes is generally limited, depending on the computing resources available. Hence setting ``chan_per_worker=1`` can be actually quite inefficient when the data has say 200 channels but only 4 parallel processes are available at any given time. In general, if there are only few trials, it is safe and even recommended to set `chan_per_worker` to a fairly low number. On the other hand, depending on the compute cluster setup, being to greedy here might also spawn a lot of jobs and hence might induce long waiting times. + + +ACME - Cluster Setup +~~~~~~~~~~~~~~~~~~~~ + +More fine-grained control over allocated resources and load-balancer options is available +via the routine :func:`~syncopy.esi_cluster_setup` which is available when +`ACME <https://github.com/esi-neuroscience/acme>`_ is installed. +It provides a convenient way to launch a custom-tailored +"cluster" of parallel workers (compute jobs if run on a cluster computing manager such as SLURM) +on the ESI HPC. +Thus, instead of simply "turning on" parallel computing via a keyword and letting +Syncopy choose an optimal setup for the computation at hand, more fine-grained +control over resource allocation and management can be achieved via running +:func:`~syncopy.esi_cluster_setup` **before** launching the actual calculation. +For example:: + + spyClient = spy.esi_cluster_setup(partition="16GBXL", n_jobs=10) + +starts 10 concurrent SLURM workers in the `16GBXL` queue if run on the ESI HPC +cluster. All subsequent invocations of Syncopy analysis routines will automatically +pick up ``spyClient`` and distribute any occurring computational payload across +the workers collected in ``spyClient``. + +.. hint:: + + If `esi_cluster_setup` is unavailable, have a look at :ref:`install_acme` For general deployment on other HPC systems please contact the ACME + team directly. + +Manual Dask cluster Setup +~~~~~~~~~~~~~~~~~~~~~~~~~ + +With ``parallel=True`` Syncopy looks for a running `Dask <https://dask.org/>`_ client to attach to, +and if none is found a Dask ``LocalCluster`` is started as a fallback to allow basic parallel execution on single machines. + +If you are working on a HPC system, you can configure your own Dask cluster **before** running any Syncopy +computations, and Syncopy will happily use the provided ressoures. See the `Dask tutorial <https://tutorial.dask.org/>`_ +for a general introduction about allocating distributed computing ressources. On a SLURM cluster, a basic setup +could look like this:: + + import dask.distributed as dd + import dask_jobqueue as dj + import syncopy as spy + + slurm_wdir = "/path/to/workdir/" + n_jobs = 8 + reqMem = 32 + queue = 'slurm_queue' + + cl = dj.SLURMCluster(cores=1, memory=f'{reqMem} GB', processes=1, + local_directory=slurm_wdir, + queue=queue) + + cl.scale(n_jobs) + client = dd.Client(cl) + + # now start syncopy computations, + # global dask `client` gets automatically recognized + # no need for `parallel=True` + spy.freqanalysis(...) + +If the Dask clurm cluster was freshly requested, we first have to wait until all workers are ready: + +.. code-block:: bash + + Syncopy <check_workers_available> INFO: 0/8 workers available, waiting.. 0s + Syncopy <check_workers_available> INFO: 3/8 workers available, waiting.. 2s + Syncopy <check_workers_available> INFO: 7/8 workers available, waiting.. 4s + Syncopy <parallel_client_detector> INFO: ..attaching to running Dask client: + <Client: 'tcp://10.100.32.3:42673' processes=8 threads=8, memory=238.40 GiB> + [################################### ] | 88% Completed | 52.3 + +.. hint:: + For a basic introduction to HPC computing see this `wiki <https://hpc-wiki.info>`_ + and/or the Slurm `documentation <https://slurm.schedmd.com/>`_. diff --git a/doc/source/user/selectdata.rst b/doc/source/user/selectdata.rst new file mode 100644 index 000000000..1091fa216 --- /dev/null +++ b/doc/source/user/selectdata.rst @@ -0,0 +1,297 @@ +.. _selections: + +*********** +Selections +*********** + +Basically every practical data analysis project involves working on subsets of the data. Syncopy offers the powerful :func:`~syncopy.selectdata` function to achieve exactly that. There are two distinct ways to apply a selection, either *in-place* or the chosen subset gets copied and returned as a new Syncopy data object. Additionally, every Syncopy *meta-function* (see :ref:`meta_functions`) supports the ``select`` keyword, which applies an in place selection on the fly when processing data. + +Selections are an important concept of Syncopy and are being re-used *under the hood* for plotting functions like :func:`~syncopy.singlepanelplot` and also for :func:`~syncopy.show`. Both plotting and numpy array extraction naturally operate on subsets of the data, and conveniently use the same selection criteria syntax as :func:`~syncopy.selectdata`. + +.. contents:: Topics covered + :local: + +.. _workflow: + + + +Creating Selections +=================== + +Syncopy data objects can be best understood as n-dimensional arrays or matrices, with each dimension holding a certain property of the data. For an :class:`~syncopy.AnalogData` object these dimensions would be ``time``, ``channel`` and ``trials``. Now we can define sub-slices of the data by combining index sets for each of those axes. + +A new selection can be created by calling :func:`~syncopy.selectdata`, here we want to select a single trial and two channels:: + + trial10 = AData.selectdata(trials=10, channel=["channel11", "channel17"]) + # alternatively + trial10 = spy.selectdata(AData, trials=10, channel=["channel02", "channel06"]) + +``AData`` is an :class:`~syncopy.AnalogData` object, and hence the resulting +``trial10`` data object will also be of that same data type. Inspecting the original +dataset by simply typing its name into the Python interpreter:: + + AData + +we see that we have 100 trials and 10 channels: + +.. code-block:: bash + + + Syncopy AnalogData object with fields + + cfg : dictionary with keys '' + channel : [10] element <class 'numpy.ndarray'> + container : None + data : 100 trials of length 500.0 defined on [50000 x 10] float32 Dataset of size 1.91 MB + ... + +If we now inspect our selection results:: + + trial10 + +we see that we are left with 1 trial and 2 channels: + +.. code-block:: bash + + + Syncopy AnalogData object with fields + + cfg : dictionary with keys '' + channel : [2] element <class 'numpy.ndarray'> + container : None + data : 1 trials of length 500.0 defined on [500 x 2] float32 Dataset of size 0.00 MB + ... + + +As we did not specify any selection criteria for the time axis (via ``latency``) every sample was selected. This is true in general: whenever a certain dimension has no selection specification the complete axis is selected. + +Finally by inspecting the ``.log`` (see also :ref:`logging`) we can see the selection settings used to create this dataset:: + + trials10.log + +.. code-block:: bash + + write_log: computed _selectdata with settings + inplace = False + clear = False + latency=None + trials = 10 + channel = ['channel02', 'channel06'] + +This log is persistent, meaning that when saving and later loading this reduced dataset the settings used for this selection can still be recovered. The table below summarizes all possible selection parameters and their availability for each datatype. + +.. _selections_table: + +Table of Selection Parameters +============================= + +There are various selection parameters +available, which each can accept a variety of Python datatypes like ``int``, ``str`` or ``list``. Some selection parameters are only available for data types which have the corresponding dimension, like ``frequency`` for ``SpectralData`` and ``CrossSpectralData``. + ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| **Parameter** | **Description** | **Accepted Types** | **Examples** | **Availability** | ++===================+=======================+=======================+=======================+=====================================+ +| trials | |trialsDesc| | |trialsVals| | |trialsEx1| | | +| | | | | | +| | | | |trialsEx2| | *all data types* | +| | | | | | +| | | | |trialsEx3| | | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| channel | |channelDesc| | |channelVals| | |channelEx1| | :class:`~syncopy.AnalogData` | +| | | | | | +| | | | |channelEx2| | :class:`~syncopy.SpectralData` | +| | | | | | +| | | | |channelEx3| | :class:`~syncopy.CrossSpectralData` | +| | | | | | +| | | | |channelEx4| | :class:`~syncopy.SpikeData` | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| latency | |latDesc| | |latVals| | |latEx1| | :class:`~syncopy.AnalogData` | +| | | | | | +| | | | |latEx2| | :class:`~syncopy.SpectralData` | +| | | | | | +| | | | |latEx3| | :class:`~syncopy.CrossSpectralData` | +| | | | | | +| | | | | :class:`~syncopy.SpikeData` | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| frequency | |freqDesc| | |freqVals| | |freqEx1| | | +| | | | | | +| | | | |freqEx2| | :class:`~syncopy.SpectralData` | +| | | | | | +| | | | | :class:`~syncopy.CrossSpectralData` | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| unit | |unitDesc| | |unitVals| | |unitEx1| | :class:`~syncopy.SpikeData` | +| | | | | | +| | | | |unitEx2| | | +| | | | | | +| | | | |unitEx3| | | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ +| eventid | |eventidDesc| | |eventidVals| | |eventidEx1| | | +| | | | | :class:`~syncopy.EventData` | +| | | | |eventidEx2| | | ++-------------------+-----------------------+-----------------------+-----------------------+-------------------------------------+ + +.. |trialsVals| replace:: *int, array, list* +.. |trialsDesc| replace:: *trial selection* +.. |trialsEx1| replace:: ``selectdata(trials=7)`` +.. |trialsEx2| replace:: ``selectdata(trials=[2, 9, 21])`` +.. |trialsEx3| replace:: ``selectdata(trials=np.arange(2, 10))`` + + +.. |channelDesc| replace:: *channel selection* +.. |channelVals| replace:: *int, str, list, array* +.. |channelEx1| replace:: ``selectdata(channel=7)`` +.. |channelEx2| replace:: ``selectdata(channel=[11, 16])`` +.. |channelEx3| replace:: ``selectdata(channel=np.arange(2, 10))`` +.. |channelEx4| replace:: ``selectdata(channel=["V1-11, "V2-12"])`` + +.. |latDesc| replace:: *time interval of interest in seconds* +.. |latVals| replace:: *list, float, 'maxperiod', 'minperiod', 'prestim', 'poststim'* +.. |latEx1| replace:: ``selectdata(latency=[0.2, 1.])`` +.. |latEx3| replace:: ``selectdata(latency='minperiod')`` +.. |latEx2| replace:: ``selectdata(latency=0.5)`` + +.. |freqDesc| replace:: *frequencies of interest in Hz* +.. |freqVals| replace:: *float, list* +.. |freqEx1| replace:: ``selectdata(frequency=20.5)`` +.. |freqEx2| replace:: ``selectdata(frequency=[5, 10, 15])`` + +.. |unitDesc| replace:: *unit selection* +.. |unitVals| replace:: *int, str, list, array* +.. |unitEx1| replace:: ``selectdata(unit=7)`` +.. |unitEx2| replace:: ``selectdata(unit=[11, 16, 32])`` +.. |unitEx3| replace:: ``selectdata(unit=["unit17", "unit3"])`` + +.. |eventidDesc| replace:: *eventid selection* +.. |eventidVals| replace:: *int, list, array* +.. |eventidEx1| replace:: ``selectdata(eventid=2)`` +.. |eventidEx2| replace:: ``selectdata(eventid=[2, 0, 1])`` + +.. note:: + Have a look at :doc:`Data Basics <data_basics>` for further details about Syncopy's data classes and interfaces + +Inplace Selections +================== + +An in-place selection can be understood as a mask being put onto the data. Meaning that the selected subset of the data is actually **not copied on disc**, but the selection criteria are applied *in place* to be used in a processing step. Inplace selections take two forms: either explicit via the ``inplace`` keyword ``selectdata(..., inplace=True)``, or implicit by passing a ``select`` keyword to a Syncopy meta-function. + +To illustrate this mechanic, let's create a simulated dataset with :func:`~syncopy.tests.synth_data.phase_diffusion` and compute the coherence for the full dataset: + +.. literalinclude:: /scripts/select_example.py + +.. image:: /_static/select_example1.png + :height: 220px + +Phase diffusing signals decorrelate over time, hence if we wait long enough we can't see any coherence. + +.. note:: + As an exercise you could use :func:`~syncopy.freqanalysis` to confirm that there is indeed strong oscillatory activity in the 40Hz band + +Explicit inplace Selection +-------------------------- + +To see if maybe for a shorter time period in the beginning of "the recording" the signals were actually more phase locked, we can use an **in-place latency selection**:: + + # note there is no return value here + spy.selectdata(adata, latency=[-1, 0], inplace=True) + +Inspecting the dataset: + +.. code-block:: bash + + + Syncopy AnalogData object with fields + + cfg : dictionary with keys 'selectdata' + channel : [2] element <class 'numpy.ndarray'> + container : None + data : 100 trials defined on [50000 x 3] float64 Dataset of size 1.14 MB + dimord : time by channel + filename : /home/whir/.spy/spy_3e83_a9c8b544.analog + info : dictionary with keys '' + mode : r+ + sampleinfo : [100 x 2] element <class 'numpy.ndarray'> + samplerate : 200.0 + selection : Syncopy AnalogData selector with all channels, 201 times, 100 trials + tag : None + time : 100 element list + trialinfo : [100 x 0] element <class 'numpy.ndarray'> + trialintervals : [100 x 2] element <class 'numpy.ndarray'> + trials : 100 element iterable + + +we can see that now the ``selection`` entry is filled with information, telling us we selected 201 time points. + +With that selection being active, let's repeat the connectivity analysis:: + + # coherence with active in-place selection + coh2 = spy.connectivityanalysis(adata, method='coh') + + # plot coherence of channel1 vs channel2 + coh2.singlepanelplot(channel_i='channel1', channel_j='channel2') + +.. image:: /_static/select_example2.png + :height: 220px + +Indeed, we now see some coherence around the 40Hz band. + +Finally, let's **wipe the inplace selection** before continuing:: + + # inspect active inplace selection + adata.selection + >>> Syncopy AnalogData selector with all channels, 201 times, 100 trials + + # wipe selection + adata.selection = None + +Inplace selection via ``select`` keyword +---------------------------------------- + +Alternatively, we can also give a dictionary of selection parameters directly to every Syncopy meta-function. These will then internally apply an inplace selection before performing the analysis:: + + # coherence only for selected time points + coh3 = spy.connectivityanalysis(adata, method='coh', select={'latency': [-1, 0]}) + + # plot coherence of channel1 vs channel2 + coh3.singlepanelplot(channel_i='channel1', channel_j='channel2') + +.. image:: /_static/select_example2.png + :height: 220px + +Hopefully not surprisingly we get to exactly the same result as with an explicit in-place selection above. The difference here however is, that after the analysis is done, there is no active in-place selection present:: + + adata.selection is None + >>> True + +Hence, it's important to note that implicit selections **get wiped automatically** after an analysis. + +In the end it is up to the user to decide which way of applying selections is most practical in their situation. + +Relation to :func:`~syncopy.show` and :func:`~syncopy.singlepanelplot` +====================================================================== + +As hinted on in the beginning of this chapter, both plotting and numpy array extraction adhere to the same syntax as :func:`~syncopy.selectdata`. Meaning that the following two arrays hold the same data:: + + # First explicitly select a subset + trial10 = spy.selectdata(AData, trials=10, channel=["channel02", "channel06"]) + # show everything of the subset + # WARNING: don't do this with large datasets! + arr1 = trial10.show() + + # calling show() with the same selection + # criteria directly on the original complete dataset + arr2 = AData.show(trials=10, channel=["channel02", "channel06"]) + + # this is True! + arr1 == arr2 + +And in the same spirit, both plotting commands below will produce the same figure:: + + # First explicitly select a subset + trial10 = spy.selectdata(AData, trials=10, channel=["channel02", "channel06"]) + # plot everything: only 1 trial and 2 channels left + trial10.singlepanelplot() + + # directly plot from full data set with same selection criteria + AData.singlepanelplot(trials=10, channel=["channel02", "channel06"]) + +This works *under the hood* by applying temporary in-place selections onto the data before plotting and/or extracting the numpy arrays. diff --git a/doc/source/user/synth_data.rst b/doc/source/user/synth_data.rst index 09e5d24ab..9e45d8eff 100644 --- a/doc/source/user/synth_data.rst +++ b/doc/source/user/synth_data.rst @@ -13,18 +13,20 @@ For testing and demonstrational purposes it is always good to work with syntheti General Recipe -------------- -To create a synthetic data set follow these steps: +We can easily create custom synthetic datasets using basic `NumPy <https://numpy.org>`_ functionality and Syncopy's :class:`~syncopy.AnalogData`. + +To create a synthetic timeseries data set follow these steps: -- write a function which returns a single trial with desired shape ``(nSamples, nChannels)``, such that each trial is a 2d-:class:`~numpy.ndarray` +- write a function which returns a single trial as a 2d-:class:`~numpy.ndarray` with desired shape ``(nSamples, nChannels)`` - collect all the trials into a Python ``list``, for example with a list comprehension or simply a for loop -- Instantiate an :class:`~syncopy.AnalogData` object by passing this list holding the trials and set the samplerate +- Instantiate an :class:`~syncopy.AnalogData` object by passing this list holding the trials as ``data`` and set the desired ``samplerate`` In (pseudo-)Python code: .. code-block:: python def generate_trial(nSamples, nChannels): - + trial = .. something fancy .. # These should evaluate to True @@ -33,47 +35,60 @@ In (pseudo-)Python code: return trial - # here we use a list comprehension - trial_list = [generate_trial(nSamples, nChannels) for _ in range(nTrials)] - - my_fancy_data = spy.AnalogData(trial_list, samplerate=my_samplerate) - -.. note:: - The same recipe can be used to generally instantiate Syncopy data objects from NumPy arrays. - + # collect the trials + nSamples = 1000 + nChannels = 2 + nTrials = 100 + trls = [] -Built-in Generators -------------------- + for _ in range(nTrials): + trial = generate_trial(Samples, nChannels) + # manipulate further as needed, e.g. add a constant + trial += 3 + trls.append(trial) -These generators return single-trial NumPy arrays, so to import them into Syncopy use the :ref:`gen_synth_recipe` described above. + # instantiate syncopy data object + my_fancy_data = spy.AnalogData(data=trls, samplerate=my_samplerate) -.. currentmodule:: syncopy.tests.synth_data -.. autofunction:: phase_diffusion -.. autofunction:: AR2_network +.. note:: + The same recipe can be used to generally instantiate Syncopy data objects from NumPy arrays. Example: Noisy Harmonics --------------------------- -We can easily create custom synthetic datasets using basic `NumPy <https://numpy.org>`_ functionality and Syncopy's :class:`~syncopy.AnalogData`. Let's create two harmonics and add some white noise to it: .. literalinclude:: /scripts/synth_data1.py -Here we first defined the number of trials and then the number of samples and channels per trial. With a sampling rate of 500Hz and 1000 samples this gives us a trial length of two seconds. The function ``generate_noisy_harmonics`` adds white noise to all channels, a 20Hz harmonic on the 1st channel and a 50Hz harmonic on the 2nd channel. Every trial got collected into a Python ``list``, which at the last line was used to initialize our :class:`~syncopy.AnalogData` object. Note that data instantiated that way always has a default trigger offset of -1 seconds. +Here we first defined the number of trials (``nTrials``) and then the number of samples (``nSamples``) and channels (``nChannels``) per trial. With a sampling rate of 500Hz and 1000 samples this gives us a trial length of two seconds. The function ``generate_noisy_harmonics`` adds a 20Hz harmonic on the 1st channel, a 50Hz harmonic on the 2nd channel and white noise to all channels, Every trial got collected into a Python ``list``, which at the last line was used to initialize our :class:`~syncopy.AnalogData` object ``synth_data``. Note that data instantiated that way always has a default trigger offset of -1 seconds. -Now we can directly run a simple FFT analysis and plot the power spectra of all 3 channels: +Now we can directly run a multi-tapered FFT analysis and plot the power spectra of all 2 channels: .. code-block:: python - spectrum = spy.freqanalysis(synth_data, foilim=[0,80], keeptrials=False) + spectrum = spy.freqanalysis(synth_data, foilim=[0,80], tapsmofrq=2, keeptrials=False) spectrum.singlepanelplot() -.. image:: synth_data_spec.png +.. image:: /_static/synth_data_spec.png :height: 300px -| - As constructed, we have two harmonic peaks at the respective frequencies (20Hz and 50Hz) and the white noise floor on all channels. + +Built-in Generators +------------------- + +These generators return single-trial NumPy arrays, so to import them into Syncopy use the :ref:`gen_synth_recipe` described above. + +.. autosummary:: + + .. currentmodule:: + syncopy.tests.synth_data.harmonic + syncopy.tests.synth_data.linear_trend + syncopy.tests.synth_data.phase_diffusion + syncopy.tests.synth_data.AR2_network + syncopy.tests.synth_data.white_noise + + diff --git a/doc/source/user/synth_data_plot.png b/doc/source/user/synth_data_plot.png deleted file mode 100644 index 2770baf75..000000000 Binary files a/doc/source/user/synth_data_plot.png and /dev/null differ diff --git a/doc/source/user/synth_data_spec.png b/doc/source/user/synth_data_spec.png deleted file mode 100644 index 453193abf..000000000 Binary files a/doc/source/user/synth_data_spec.png and /dev/null differ diff --git a/doc/source/user/user_api.rst b/doc/source/user/user_api.rst index 2d0cb31ff..5475fde0a 100644 --- a/doc/source/user/user_api.rst +++ b/doc/source/user/user_api.rst @@ -1,9 +1,77 @@ API for Users -------------- +============= + +This page gives an overview over all public functions and classes of Syncopy. .. contents:: Sections :local: -.. automodapi:: syncopy - :no-heading: - :skip: timelockanalysis +High-level functions +-------------------- +These *meta-functions* bundle many related analysis methods into one high-level function. + +.. autosummary:: + + syncopy.preprocessing + syncopy.resampledata + syncopy.freqanalysis + syncopy.connectivityanalysis + syncopy.timelockanalysis + +Descriptive Statistics +---------------------- +.. autosummary:: + + syncopy.mean + syncopy.var + syncopy.std + syncopy.median + syncopy.itc + syncopy.spike_psth + +Utility +-------- + +.. autosummary:: + + syncopy.definetrial + syncopy.selectdata + syncopy.show + syncopy.cleanup + +I/O +-------------------- +Functions to import and export data in Syncopy + +.. autosummary:: + + syncopy.load + syncopy.save + syncopy.load_ft_raw + syncopy.load_tdt + syncopy.load_nwb + syncopy.copy + + +Plotting +----------- + +These convenience function are intended to be used for a quick visual inspection of data and results. + +.. autosummary:: + + syncopy.singlepanelplot + syncopy.multipanelplot + +Data Types +-------------------- + +Syncopy data types are Python classes, which offer convenient ways for data access and manipulation. + +.. autosummary:: + + syncopy.AnalogData + syncopy.SpectralData + syncopy.CrossSpectralData + syncopy.SpikeData + syncopy.EventData diff --git a/doc/source/user/work-flow.mmd b/doc/source/user/work-flow.mmd index c77e1301f..f3cb1da33 100644 --- a/doc/source/user/work-flow.mmd +++ b/doc/source/user/work-flow.mmd @@ -1,20 +1,27 @@ graph LR classDef MEM fill:#ff908a,stroke:#333,stroke-width:2px; classDef DISC fill:#78e4ff,stroke:#333,stroke-width:1px; - classDef default fill:#9cfcff,stroke:#333,stroke-width:1px; - + classDef Other fill:#9cfcff,stroke:#333,stroke-width:1px; + spyRead[/.spy\] -->|load| Data FTread[/.mat\] -->|load_ft_raw| Data NWBread[/nwb\] -->|load_nwd| Data npInput[NumPy arrays] -.->|collect_trials| Data(AnalogData) - - Data -->|connectivityanalysis| CData(CrossSpectralData) + + Data -->|freqanalysis| SData(SpectralData) Data -.-> |singlepanelplot|PlotD1[Figures] Data -.-> |show| ShowD1[NumPy arrays] - - CData -.-> |singlepanelplot|PlotD[Figures] - CData -.-> |show| ShowD2[NumPy arrays] - CData --> |save| SaveD2[/.spy\] + + SData -.-> |singlepanelplot|PlotD[Figures] + SData -.-> |show| ShowD2[NumPy arrays] + SData --> |save| SaveD2[/.spy\] + class npInput,PlotD,ShowD1,PlotD1,ShowD2,PlotD2 MEM; - class Data,CData DISC; + class Data,SData DISC; + class spyRead,FTread,NWBread,SaveD2 Other; linkStyle 4 stroke:#78e4ff,stroke-width:3px; + + subgraph one [ Legend ] + disc[/on disc\]:::DISC + mem[in memory]:::MEM + end \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 320c15c68..cb9f4df83 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,6 +61,21 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "22.12.0" @@ -149,7 +164,7 @@ test-no-codebase = ["pytest", "matplotlib", "pillow"] [[package]] name = "coverage" -version = "6.5.0" +version = "7.0.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -245,11 +260,11 @@ zict = ">=0.1.3" [[package]] name = "docutils" -version = "0.19" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "exceptiongroup" @@ -765,6 +780,26 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pydata-sphinx-theme" +version = "0.8.1" +description = "Bootstrap-based Sphinx theme from the PyData community" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +beautifulsoup4 = "*" +docutils = "!=0.17.0" +packaging = "*" +sphinx = ">=3.5.4,<5" + +[package.extras] +doc = ["numpydoc", "myst-parser", "pandas", "pytest", "pytest-regressions", "sphinxext-rediraffe", "sphinx-sitemap", "jupyter-sphinx", "plotly", "numpy", "xarray"] +test = ["pytest", "pydata-sphinx-theme"] +coverage = ["pytest-cov", "codecov", "pydata-sphinx-theme"] +dev = ["pyyaml", "pre-commit", "nox", "pydata-sphinx-theme"] + [[package]] name = "pyflakes" version = "2.3.1" @@ -932,9 +967,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "sphinx" -version = "5.3.0" +version = "4.5.0" description = "Python documentation generator" category = "main" optional = false @@ -942,16 +985,16 @@ python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.20" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.12" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" requests = ">=2.5.0" -snowballstemmer = ">=2.0" +snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" @@ -961,8 +1004,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "flake8-comprehensions", "flake8-bugbear", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "typed-ast", "cython"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-automodapi" @@ -979,12 +1022,22 @@ sphinx = ">=2" test = ["pytest", "pytest-cov", "cython", "codecov", "coverage"] [[package]] -name = "sphinx-bootstrap-theme" -version = "0.8.1" -description = "Sphinx Bootstrap Theme." +name = "sphinx-book-theme" +version = "0.3.3" +description = "A clean book theme for scientific explanations and documentation with Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.dependencies] +pydata-sphinx-theme = ">=0.8.0,<0.9.0" +pyyaml = "*" +sphinx = ">=3,<5" + +[package.extras] +code_style = ["pre-commit (>=2.7.0,<2.8.0)"] +doc = ["ablog (>=0.10.13,<0.11.0)", "ipywidgets", "folium", "numpy", "matplotlib", "numpydoc", "myst-nb (>=0.13.2,<0.14.0)", "nbclient", "pandas", "plotly", "sphinx (>=4.0,<5.0)", "sphinx-design", "sphinx-examples", "sphinx-copybutton", "sphinx-tabs", "sphinx-togglebutton (>=0.2.1)", "sphinx-thebe (>=0.1.1)", "sphinxcontrib-bibtex (>=2.2,<3.0)", "sphinxcontrib-youtube", "sphinxext-opengraph"] +test = ["beautifulsoup4 (>=4.6.1,<5)", "coverage", "myst-nb (>=0.13.2,<0.14.0)", "pytest (>=6.0.1,<6.1.0)", "pytest-cov", "pytest-regressions (>=2.0.1,<2.1.0)", "sphinx-thebe"] [[package]] name = "sphinxcontrib-applehelp" @@ -1199,7 +1252,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-co [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "b594b21f28d7f3edf8d1b455542fe645595335144b200d78aeaad126bdb61286" +content-hash = "9266a8d7c627a008ffa691ad205e7dd767d697482c35d53d92e8e254504b3cca" [metadata.files] alabaster = [ @@ -1226,6 +1279,10 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] black = [ {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, @@ -1332,56 +1389,57 @@ contourpy = [ {file = "contourpy-1.0.6.tar.gz", hash = "sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142"}, ] coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + {file = "coverage-7.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2569682d6ea9628da8d6ba38579a48b1e53081226ec7a6c82b5024b3ce5009f"}, + {file = "coverage-7.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ec256a592b497f26054195f7d7148892aca8c4cdcc064a7cc66ef7a0455b811"}, + {file = "coverage-7.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5885a4ceb6dde34271bb0adafa4a248a7f589c89821e9da3110c39f92f41e21b"}, + {file = "coverage-7.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d43d406a4d73aa7f855fa44fa77ff47e739b565b2af3844600cdc016d01e46b9"}, + {file = "coverage-7.0.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18df11efa615b79b9ecc13035a712957ff6283f7b244e57684e1c092869f541"}, + {file = "coverage-7.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f6a4bf5bdee93f6817797beba7086292c2ebde6df0d5822e0c33f8b05415c339"}, + {file = "coverage-7.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:33efe89cd0efef016db19d8d05aa46631f76793de90a61b6717acb202b36fe60"}, + {file = "coverage-7.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96b5b1f1079e48f56bfccf103bcf44d48b9eb5163f1ea523fad580f15d3fe5e0"}, + {file = "coverage-7.0.0-cp310-cp310-win32.whl", hash = "sha256:fb85b7a7a4b204bd59d6d0b0c8d87d9ffa820da225e691dfaffc3137dc05b5f6"}, + {file = "coverage-7.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:793dcd9d42035746fc7637df4336f7581df19d33c5c5253cf988c99d8e93a8ba"}, + {file = "coverage-7.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d564142a03d3bc8913499a458e931b52ddfe952f69b6cd4b24d810fd2959044a"}, + {file = "coverage-7.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0a8b0e86bede874bf5da566b02194fbb12dd14ce3585cabd58452007f272ba81"}, + {file = "coverage-7.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e645c73cbfc4577d93747d3f793115acf6f907a7eb9208fa807fdcf2da1964a4"}, + {file = "coverage-7.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de06e7585abe88c6d38c1b73ce4c3cb4c1a79fbb0da0d0f8e8689ef5729ec60d"}, + {file = "coverage-7.0.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a30b646fbdd5bc52f506e149fa4fbdef82432baf6b81774e61ec4e3b43b9cbde"}, + {file = "coverage-7.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:db8141856dc9be0917413df7200f53accf1d84c8b156868e6af058a1ea8e903a"}, + {file = "coverage-7.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:59e71912c7fc78d08a567ee65656123878f49ca1b5672e660ea70bf8dfbebf8f"}, + {file = "coverage-7.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b8f7cd942dda3795fc9eadf303cc53a422ac057e3b70c2ad6d4276ec6a83a541"}, + {file = "coverage-7.0.0-cp311-cp311-win32.whl", hash = "sha256:bf437a04b9790d3c9cd5b48e9ce9aa84229040e3ae7d6c670a55118906113c5a"}, + {file = "coverage-7.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a7e1bb36b4e57a2d304322021b35d4e4a25fa0d501ba56e8e51efaebf4480556"}, + {file = "coverage-7.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:215f40ef86f1958a1151fa7fad2b4f2f99534c4e10a34a1e065eba3f19ef8868"}, + {file = "coverage-7.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae088eb1cbdad8206931b1bf3f11dee644e038a9300be84d3e705e29356e5b1d"}, + {file = "coverage-7.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9071e197faa24837b967bc9aa0b9ef961f805a75f1ee3ea1f3367f55cd46c3c"}, + {file = "coverage-7.0.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f1e6d9c70d45a960d3f3d781ea62b167fdf2e0e1f6bb282b96feea653adb923"}, + {file = "coverage-7.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9fadd15f9fcfd7b16d9cccce9f5e6ec6f9b8df860633ad9aa62c2b14c259560f"}, + {file = "coverage-7.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:10b6246cae61896ab4c7568e498e492cbb73a2dfa4c3af79141c43cf806f929a"}, + {file = "coverage-7.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a8785791c2120af114ea7a06137f7778632e568a5aa2bbfc3b46c573b702af74"}, + {file = "coverage-7.0.0-cp37-cp37m-win32.whl", hash = "sha256:30220518dd89c4878908d73f5f3d1269f86e9e045354436534587a18c7b9da85"}, + {file = "coverage-7.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bc904aa96105d73357de03de76336b1e3db28e2b12067d36625fd9646ab043fd"}, + {file = "coverage-7.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2331b7bd84a1be79bd17ca8e103ce38db8cbf7cb354dc56e651ba489cf849212"}, + {file = "coverage-7.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e907db8bdd0ad1253a33c20fdc5f0f6209d271114a9c6f1fcdf96617343f7ca0"}, + {file = "coverage-7.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0deee68e0dae1d6e3fe6943c76d7e66fbeb6519bd08e4e5366bcc28a8a9aca"}, + {file = "coverage-7.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fff0f08bc5ffd0d78db821971472b4adc2ee876b86f743e46d634fb8e3c22f"}, + {file = "coverage-7.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a290b7921c1c05787b953e5854d394e887df40696f21381cc33c4e2179bf50ac"}, + {file = "coverage-7.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:100546219af59d2ad82d4575de03a303eb27b75ea36ffbd1677371924d50bcbc"}, + {file = "coverage-7.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c1ba6e63b831112b9484ff5905370d89e43d4316bac76d403031f60d61597466"}, + {file = "coverage-7.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c685fc17d6f4f1a3833e9dac27d0b931f7ccb52be6c30d269374203c7d0204a2"}, + {file = "coverage-7.0.0-cp38-cp38-win32.whl", hash = "sha256:8938f3a10f45019b502020ba9567b97b6ecc8c76b664b421705c5406d4f92fe8"}, + {file = "coverage-7.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:c4b63888bef2928d0eca12cbce0760cfb696acb4fe226eb55178b6a2a039328a"}, + {file = "coverage-7.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cda63459eb20652b22e038729a8f5063862c189a3963cb042a764b753172f75e"}, + {file = "coverage-7.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e06abac1a4aec1ff989131e43ca917fc7bd296f34bf0cfe86cbf74343b21566d"}, + {file = "coverage-7.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b94ad926e933976627f040f96dd1d9b0ac91f8d27e868c30a28253b9b6ac2d"}, + {file = "coverage-7.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6b4af31fb49a2ae8de1cd505fa66c403bfcc5066e845ac19d8904dcfc9d40da"}, + {file = "coverage-7.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36b62f0220459e528ad5806cc7dede71aa716e067d2cb10cb4a09686b8791fba"}, + {file = "coverage-7.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:43ec1935c6d6caab4f3bc126d20bd709c0002a175d62208ebe745be37a826a41"}, + {file = "coverage-7.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8593c9baf1f0f273afa22f5b45508b76adc7b8e94e17e7d98fbe1e3cd5812af2"}, + {file = "coverage-7.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fee283cd36c3f14422d9c1b51da24ddbb5e1eed89ad2480f6a9f115df38b5df8"}, + {file = "coverage-7.0.0-cp39-cp39-win32.whl", hash = "sha256:97c0b001ff15b8e8882995fc07ac0a08c8baf8b13c1145f3f12e0587bbb0e335"}, + {file = "coverage-7.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8dbf83a4611c591b5de65069b6fd4dd3889200ed270cd2f7f5ac765d3842889f"}, + {file = "coverage-7.0.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:bcaf18e46668057051a312c714a4548b81f7e8fb3454116ad97be7562d2a99e4"}, + {file = "coverage-7.0.0.tar.gz", hash = "sha256:9a175da2a7320e18fc3ee1d147639a2b3a8f037e508c96aa2da160294eb50e17"}, ] cycler = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, @@ -1404,8 +1462,8 @@ distributed = [ {file = "distributed-2022.12.0.tar.gz", hash = "sha256:d93707757f7fa3b2b803e43f6c14c41c3e453e9714bca8bf3013d3bf083c18ce"}, ] docutils = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] exceptiongroup = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, @@ -1856,6 +1914,10 @@ pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] +pydata-sphinx-theme = [ + {file = "pydata_sphinx_theme-0.8.1-py3-none-any.whl", hash = "sha256:af2c99cb0b43d95247b1563860942ba75d7f1596360594fce510caaf8c4fcc16"}, + {file = "pydata_sphinx_theme-0.8.1.tar.gz", hash = "sha256:96165702253917ece13dd895e23b96ee6dce422dcc144d560806067852fe1fed"}, +] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, @@ -1962,17 +2024,21 @@ sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] sphinx = [ - {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, - {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-automodapi = [ {file = "sphinx-automodapi-0.14.1.tar.gz", hash = "sha256:a2f9c0f9e2901875e6db75df6c01412875eb15f25e7db1206e1b69fedf75bbc9"}, {file = "sphinx_automodapi-0.14.1-py3-none-any.whl", hash = "sha256:4238e131d7abc47226449661bb3cfa2bb1b5b190184ffa69d9b924b984a22753"}, ] -sphinx-bootstrap-theme = [ - {file = "sphinx-bootstrap-theme-0.8.1.tar.gz", hash = "sha256:683e3b735448dadd0149f76edecf95ff4bd9157787e9e77e0d048ca6f1d680df"}, - {file = "sphinx_bootstrap_theme-0.8.1-py2.py3-none-any.whl", hash = "sha256:6ef36206c211846ea6cbdb45bc85645578e7c62d0a883361181708f8b6ea743b"}, +sphinx-book-theme = [ + {file = "sphinx_book_theme-0.3.3-py3-none-any.whl", hash = "sha256:9685959dbbb492af005165ef1b9229fdd5d5431580ac181578beae3b4d012d91"}, + {file = "sphinx_book_theme-0.3.3.tar.gz", hash = "sha256:0ec36208ff14c6d6bf8aee1f1f8268e0c6e2bfa3cef6e41143312b25275a6217"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, diff --git a/pyproject.toml b/pyproject.toml index c9d628e8a..ffc1f977f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ black = "^22.6.0" pytest = "^7.0" ipython = "^8.0" pytest-cov = "^3.0.0" -sphinx-bootstrap-theme = ">=0.8" +sphinx-book-theme = "^0.3.3" sphinx-automodapi = "^0.14.1" flake8 = "^3.9" diff --git a/syncopy/specest/freqanalysis.py b/syncopy/specest/freqanalysis.py index 31099d2f3..cd0363d8f 100644 --- a/syncopy/specest/freqanalysis.py +++ b/syncopy/specest/freqanalysis.py @@ -315,9 +315,28 @@ def freqanalysis(data, method='mtmfft', output='pow', Examples -------- - Coming soon... + Generate 10 seconds of white noise, sampled at 1000 Hz: + >>> import syncopy.tests.synth_data as synth_data + >>> wn = synth_data.white_noise(nTrials=2, nChannels=3, nSamples=10000, samplerate=1000) + + Configure Welch's method to estimate the power spectral density with a window length of 0.25 seconds + and an overlap of 50 percent between the windows: + + >>> cfg = spy.get_defaults(spy.freqanalysis) + >>> cfg.method = "welch" + >>> cfg.t_ftimwin = 0.25 # Window length in seconds. + >>> cfg.toi = 0.5 # Overlap between periodograms (0.5 = 50 percent overlap). + + Run Welch: + + >>> psd = spy.freqanalysis(cfg, wn) + + Visualize the result for the first trial: + + >>> _, ax = res.singlepanelplot(trials=0, logscale=False) + >>> ax.set_title("Welch result") See also -------- diff --git a/syncopy/statistics/timelockanalysis.py b/syncopy/statistics/timelockanalysis.py index 97b3389ce..8fd8b34a5 100644 --- a/syncopy/statistics/timelockanalysis.py +++ b/syncopy/statistics/timelockanalysis.py @@ -38,8 +38,9 @@ def timelockanalysis(data, keeptrials=False, **kwargs): """ - Average, variance and covariance for :class:`~syncopy.AnalogData` objects across trials - If input ``data`` is not timelocked already, toilim and trial selections will be + Average, variance and covariance for :class:`~syncopy.AnalogData` objects across trials. + + If input ``data`` is not timelocked already, trial cutting and selections will be applied according to the ``latency`` setting. Parameters