From 114894e005956e3d888ef07467ae9f54400d8426 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 20 Dec 2024 04:14:50 -0600 Subject: [PATCH 01/18] Provide JupyterLite and download links via include file --- doc/source/_static/pywavelets.css | 49 +++++++++++++++++++---------- doc/source/conf.py | 45 ++++++++++++++++++++++++-- doc/source/regression/dwt-idwt.md | 20 +----------- doc/source/regression/gotchas.md | 20 +----------- doc/source/regression/header.md | 13 ++++++++ doc/source/regression/modes.md | 20 +----------- doc/source/regression/multilevel.md | 20 +----------- doc/source/regression/wavelet.md | 20 +----------- doc/source/regression/wp.md | 20 +----------- doc/source/regression/wp2d.md | 20 +----------- 10 files changed, 95 insertions(+), 152 deletions(-) create mode 100644 doc/source/regression/header.md diff --git a/doc/source/_static/pywavelets.css b/doc/source/_static/pywavelets.css index 56f12e4df..fcff86097 100644 --- a/doc/source/_static/pywavelets.css +++ b/doc/source/_static/pywavelets.css @@ -1,27 +1,42 @@ /* Custom CSS rules for the interactive documentation button */ .try_examples_button { - color: white; - background-color: #0054a6; - border: none; - padding: 5px 10px; - border-radius: 10px; - margin-bottom: 5px; - box-shadow: 0 2px 5px rgba(108,108,108,0.2); - font-weight: bold; - font-size: small; + color: white; + background-color: #0054a6; + border: none; + padding: 5px 10px; + border-radius: 10px; + margin-bottom: 5px; + box-shadow: 0 2px 5px rgba(108,108,108,0.2); + font-weight: bold; + font-size: small; } .try_examples_button:hover { -background-color: #0066cc; -transform: scale(1.02); -box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); -cursor: pointer; + background-color: #0066cc; + transform: scale(1.02); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + cursor: pointer; } .try_examples_button_container { - display: flex; - justify-content: flex-start; - gap: 10px; - margin-bottom: 20px; + display: flex; + justify-content: flex-start; + gap: 10px; + margin-bottom: 20px; +} + +/* +Admonitions on this site are styled with some top margin. This makes sense when +the admonition appears within the flow of the article. But when it is the very +first child of an article, its top margin gets added to the article's top +padding, resulting in too much whitespace. + +We use "tip" admonitions at the top of each doc in the regression/ directory. +This rule removes the top margin for tip admonitions when they occur as the very +first child of some container. This will not affect non-first-child tip +admonitions. +*/ +.admonition.tip:first-child { + margin-top: 0; } diff --git a/doc/source/conf.py b/doc/source/conf.py index bd4c7a75b..f054537f0 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -11,11 +11,10 @@ # serve to show the default. import datetime -import importlib.metadata import os +import re from pathlib import Path -import jinja2.filters import numpy as np import pywt @@ -40,6 +39,8 @@ def preprocess_notebooks(app: Sphinx, *args, **kwargs): print("Converting Markdown files to IPyNB...") for path in (HERE / "regression").glob("*.md"): + if path.match("regression/header.md"): + continue nb = jupytext.read(str(path)) ipynb_path = path.with_suffix(".ipynb") with open(ipynb_path, "w") as f: @@ -47,8 +48,47 @@ def preprocess_notebooks(app: Sphinx, *args, **kwargs): print(f"Converted {path} to {ipynb_path}") +# Should match {{ parent_docname }} or {{parent_docname}} +parent_docname_substitution_re = re.compile(r"{{\s*parent_docname\s*}}") + +def sub_parent_docname_in_header( + app: Sphinx, relative_path: Path, parent_docname: str, content: list[str] +): + """Fill in the name of the document in the header. + + When regression/header.md is read via the include directive, replace + {{ parent_docname }} with the name of the parent document that included + header.md. + + Note: parent_docname does not include the file extension. + + Here is a simplified example of how this works. + + Contents of header.md: + + {download}`Download {{ parent_docname }}.md <{{ parent_docname }}.md>` + + Contents of foobar.md: + + ```{include} header.md + ``` + + After this function and others are run... + + Contents of foobar.md: + + {download}`Download foobar.md ` + """ + if not relative_path.match("regression/header.md"): + return + + for i, value in enumerate(content): + content[i] = re.sub(parent_docname_substitution_re, parent_docname, value) + + def setup(app): app.connect("config-inited", preprocess_notebooks) + app.connect("include-read", sub_parent_docname_in_header) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -309,6 +349,7 @@ def setup(app): # directories to ignore when looking for source files. exclude_patterns = [ 'substitutions.rst', + 'regression/header.md', 'regression/*.ipynb' # exclude IPyNB files from the build ] diff --git a/doc/source/regression/dwt-idwt.md b/doc/source/regression/dwt-idwt.md index 92289ff88..003cc1d0e 100644 --- a/doc/source/regression/dwt-idwt.md +++ b/doc/source/regression/dwt-idwt.md @@ -16,25 +16,7 @@ mystnb: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: dwt-idwt.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/gotchas.md b/doc/source/regression/gotchas.md index 6d3b4719f..b5615a25a 100644 --- a/doc/source/regression/gotchas.md +++ b/doc/source/regression/gotchas.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: gotchas.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/header.md b/doc/source/regression/header.md new file mode 100644 index 000000000..e2ba11988 --- /dev/null +++ b/doc/source/regression/header.md @@ -0,0 +1,13 @@ +````{tip} + +This page can also be run or downloaded as a notebook. + +```{jupyterlite} {{ parent_docname }}.ipynb +:new_tab: True +``` + +Downloads: + +1. {download}`Download {{ parent_docname }}.ipynb <{{ parent_docname }}.ipynb>` +2. {download}`Download {{ parent_docname }}.md <{{ parent_docname }}.md>` +```` diff --git a/doc/source/regression/modes.md b/doc/source/regression/modes.md index 38ea50b07..3f333ae87 100644 --- a/doc/source/regression/modes.md +++ b/doc/source/regression/modes.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: modes.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/multilevel.md b/doc/source/regression/multilevel.md index dde825d41..8bcd41152 100644 --- a/doc/source/regression/multilevel.md +++ b/doc/source/regression/multilevel.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: multilevel.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/wavelet.md b/doc/source/regression/wavelet.md index 03a52a9c2..f4509056a 100644 --- a/doc/source/regression/wavelet.md +++ b/doc/source/regression/wavelet.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: wavelet.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/wp.md b/doc/source/regression/wp.md index 5c230cdae..17a091207 100644 --- a/doc/source/regression/wp.md +++ b/doc/source/regression/wp.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: wp.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ diff --git a/doc/source/regression/wp2d.md b/doc/source/regression/wp2d.md index 98d78d48e..a4fd4227d 100644 --- a/doc/source/regression/wp2d.md +++ b/doc/source/regression/wp2d.md @@ -13,25 +13,7 @@ kernelspec: +++ {"tags": ["jupyterlite_sphinx_strip"]} -```{eval-rst} -.. currentmodule:: pywt - -.. dropdown:: 🧑‍🔬 This notebook can be executed online. Click this section to try it out! ✨ - :color: success - - .. notebooklite:: wp2d.ipynb - :width: 100% - :height: 600px - :prompt: Open notebook - -.. dropdown:: Download this notebook - :color: info - :open: - - Please use the following links to download this notebook in various formats: - - 1. :download:`Download IPyNB (IPython Notebook) ` - 2. :download:`Download Markdown Notebook (Jupytext) ` +```{include} header.md ``` +++ From 3f44dba134b448aab3a531fbd9cd95a28a224005 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 27 Dec 2024 19:13:25 -0600 Subject: [PATCH 02/18] restyle notebook cells --- doc/source/_static/myst-nb.css | 58 ++++++++++++++++++++++++++++++++++ doc/source/conf.py | 1 + 2 files changed, 59 insertions(+) create mode 100644 doc/source/_static/myst-nb.css diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css new file mode 100644 index 000000000..9800b1087 --- /dev/null +++ b/doc/source/_static/myst-nb.css @@ -0,0 +1,58 @@ +/* MyST-NB + +This stylesheet targets elements output by MyST-NB that represent notebook +cells. + +In some cases these rules override MyST-NB. In some cases they override PyData +Sphinx Theme or Sphinx. And in some cases they do not override existing styling +but add new styling. */ + +/* +Undo the border that MyST-NB puts on the input cell +*/ +div.cell div.cell_input { + border: none; +} + +/* +Undo border radius on code cells (this styling actually comes from Sphinx, not MyST-NB) +*/ + +div.cell pre { + border-radius: 0; +} +div.cell div.highlight { + border-radius: 0; +} + +/* +Add border to output container, remove background color. +*/ +div.cell div.cell_output div.output { + background-color: var(--pst-color-background); + border-radius: 0; + border-color: var(--pst-color-border-muted); + border-top-style: double; + border-top-width: medium; +} + +/* +By default, all code cells have a muted background. +This removes that background. + +By adding a border above and removing the background color here, +we create outputs that look outlined. +*/ +div.cell div.cell_output pre { + background-color: transparent; +} + +/* +Remove extra whitespace +*/ +div.cell div.cell_output { + margin-top: 0; +} +div.cell div.cell_output > :first-child { + margin-top: 0; +} diff --git a/doc/source/conf.py b/doc/source/conf.py index f054537f0..9ce751f21 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -269,6 +269,7 @@ def setup(app): # _static directory. html_css_files = [ "pywavelets.css", + "myst-nb.css" ] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, From 60f88439e10d01adb8b58beffc163fb303deaa0f Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 27 Dec 2024 21:09:48 -0600 Subject: [PATCH 03/18] mock and hide outputs, update styles --- doc/source/_static/myst-nb.css | 35 ++++++++++++++------------- doc/source/regression/dwt-idwt.md | 39 ++++++++++++++++++++++++++++--- doc/source/regression/modes.md | 9 ++++++- doc/source/regression/wp.md | 27 +++++++++++++++++++-- doc/source/regression/wp2d.md | 26 +++++++++++++++++++-- 5 files changed, 110 insertions(+), 26 deletions(-) diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css index 9800b1087..9bbce2f0f 100644 --- a/doc/source/_static/myst-nb.css +++ b/doc/source/_static/myst-nb.css @@ -17,42 +17,41 @@ div.cell div.cell_input { /* Undo border radius on code cells (this styling actually comes from Sphinx, not MyST-NB) */ - -div.cell pre { - border-radius: 0; -} -div.cell div.highlight { +div.cell pre, +div.cell div.highlight, +div.pywt-handcoded-cell-output, +div.pywt-handcoded-cell-output pre, +div.pywt-handcoded-cell-output div.highlight { border-radius: 0; } /* Add border to output container, remove background color. */ -div.cell div.cell_output div.output { +div.cell div.cell_output, +div.pywt-handcoded-cell-output { background-color: var(--pst-color-background); border-radius: 0; border-color: var(--pst-color-border-muted); + border-style: solid; + border-width: 1px; border-top-style: double; border-top-width: medium; } - -/* -By default, all code cells have a muted background. -This removes that background. - -By adding a border above and removing the background color here, -we create outputs that look outlined. -*/ -div.cell div.cell_output pre { +div.cell div.cell_output div.output, +div.cell div.cell_output pre, +div.pywt-handcoded-cell-output pre { background-color: transparent; + border: none; } /* Remove extra whitespace */ -div.cell div.cell_output { - margin-top: 0; -} +div.cell div.cell_output, div.cell div.cell_output > :first-child { margin-top: 0; } +div.pywt-handcoded-cell-output { + margin-top: -1em; +} \ No newline at end of file diff --git a/doc/source/regression/dwt-idwt.md b/doc/source/regression/dwt-idwt.md index 003cc1d0e..3c7096c00 100644 --- a/doc/source/regression/dwt-idwt.md +++ b/doc/source/regression/dwt-idwt.md @@ -154,11 +154,22 @@ Remember that only one argument at a time can be `None`: ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(pywt.idwt(None, None, 'db2', 'symmetric')) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: At least one coefficient parameter must be specified. +``` + ++++ + ### Coefficients data size in `pywt.idwt` When doing the `idwt` transform, usually the coefficient arrays @@ -166,11 +177,22 @@ must have the same size. ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(pywt.idwt([1, 2, 3, 4, 5], [1, 2, 3, 4], 'db2', 'symmetric')) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: Coefficients arrays must have the same size. +``` + ++++ + Not every coefficient array can be used in `idwt`. In the following example the `idwt` will fail because the input arrays are invalid - they couldn't be created as a result of `dwt`, because @@ -179,11 +201,22 @@ mode is `4`, not `3`: ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- pywt.idwt([1,2,4], [4,1,3], 'db4', 'symmetric') ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: Invalid coefficient arrays length for specified wavelet. Wavelet and mode must be the same as used for decomposition. +``` + ++++ + ```{code-cell} int(pywt.dwt_coeff_len(1, pywt.Wavelet('db4').dec_len, 'symmetric')) ``` diff --git a/doc/source/regression/modes.md b/doc/source/regression/modes.md index 3f333ae87..2c9539a7d 100644 --- a/doc/source/regression/modes.md +++ b/doc/source/regression/modes.md @@ -49,11 +49,18 @@ Therefore, an invalid mode name should raise a `ValueError`: ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- pywt.dwt([1,2,3,4], 'db2', 'invalid') ``` +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: Unknown mode name 'invalid'. +``` + You can also refer to modes via the attributes of the `Modes` class: ```{code-cell} diff --git a/doc/source/regression/wp.md b/doc/source/regression/wp.md index 17a091207..febaf8230 100644 --- a/doc/source/regression/wp.md +++ b/doc/source/regression/wp.md @@ -126,20 +126,43 @@ Oops, we have reached the maximum level of decomposition and received an `IndexE ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(wp['aaaa'].data) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +IndexError: Path length is out of range. +``` + ++++ + + Now, try an invalid path: ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(wp['ac']) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: Subnode name must be in ['a', 'd'], not 'c'. +``` + ++++ + which just yielded a `ValueError`. ### Accessing `Node`'s attributes diff --git a/doc/source/regression/wp2d.md b/doc/source/regression/wp2d.md index a4fd4227d..4fb9a91f6 100644 --- a/doc/source/regression/wp2d.md +++ b/doc/source/regression/wp2d.md @@ -147,11 +147,22 @@ print(wp['aaa'].data) ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(wp['aaaa'].data) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +IndexError: Path length is out of range. +``` + ++++ + Oops, we have reached the maximum level of decomposition for the `'aaaa'` path, which, by the way, was: @@ -163,11 +174,22 @@ Now, try an invalid path: ```{code-cell} --- -tags: [raises-exception] +tags: [raises-exception, remove-output] --- print(wp['f']) ``` ++++ {"tags": ["jupyterlite_sphinx_strip"]} + +```{code-block} python +:class: pywt-handcoded-cell-output +Traceback (most recent call last): +... +ValueError: Subnode name must be in ['a', 'h', 'v', 'd'], not 'f'. +``` + ++++ + ### Accessing Node2D's attributes `WaveletPacket2D` is a tree data structure, which evaluates to a set From 07f381f574404cc43b94ebfc457957b98e98ef9b Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 27 Dec 2024 21:10:14 -0600 Subject: [PATCH 04/18] exclude cells tagged jupyterlite_sphinx_strip from md to ipynb conversion --- doc/source/conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 9ce751f21..56ec13bfd 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -42,6 +42,13 @@ def preprocess_notebooks(app: Sphinx, *args, **kwargs): if path.match("regression/header.md"): continue nb = jupytext.read(str(path)) + + # In .md to .ipynd conversion, do not include any cells that have the + # jupyterlite_sphinx_strip tag + nb.cells = [ + cell for cell in nb.cells if "jupyterlite_sphinx_strip" not in cell.metadata.get("tags", []) + ] + ipynb_path = path.with_suffix(".ipynb") with open(ipynb_path, "w") as f: nbformat.write(nb, f) From abcc8e5dd4076197a32f736903f81dfe513f64be Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 30 Dec 2024 12:57:11 -0600 Subject: [PATCH 05/18] Use better CSS selectors --- doc/source/_static/myst-nb.css | 11 +++++++---- doc/source/conf.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css index 9bbce2f0f..699e86606 100644 --- a/doc/source/_static/myst-nb.css +++ b/doc/source/_static/myst-nb.css @@ -46,12 +46,15 @@ div.pywt-handcoded-cell-output pre { } /* -Remove extra whitespace +Bring output up against input (remove extra whitespace) */ div.cell div.cell_output, div.cell div.cell_output > :first-child { margin-top: 0; } -div.pywt-handcoded-cell-output { - margin-top: -1em; -} \ No newline at end of file +div.cell:has(+ div.pywt-handcoded-cell-output) { + margin-bottom: 0; +} +div.cell + div.pywt-handcoded-cell-output { + margin-top: 0; +} diff --git a/doc/source/conf.py b/doc/source/conf.py index 56ec13bfd..61552cb6c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -43,7 +43,7 @@ def preprocess_notebooks(app: Sphinx, *args, **kwargs): continue nb = jupytext.read(str(path)) - # In .md to .ipynd conversion, do not include any cells that have the + # In .md to .ipynb conversion, do not include any cells that have the # jupyterlite_sphinx_strip tag nb.cells = [ cell for cell in nb.cells if "jupyterlite_sphinx_strip" not in cell.metadata.get("tags", []) From 69f082af8376f4d37216cf6fcce95a0b5a3dc874 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 30 Dec 2024 13:00:06 -0600 Subject: [PATCH 06/18] Add README file to regression folder --- doc/source/conf.py | 3 +- doc/source/regression/README.md | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 doc/source/regression/README.md diff --git a/doc/source/conf.py b/doc/source/conf.py index 61552cb6c..344afe933 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -39,7 +39,7 @@ def preprocess_notebooks(app: Sphinx, *args, **kwargs): print("Converting Markdown files to IPyNB...") for path in (HERE / "regression").glob("*.md"): - if path.match("regression/header.md"): + if any(path.match(pattern) for pattern in exclude_patterns): continue nb = jupytext.read(str(path)) @@ -358,6 +358,7 @@ def setup(app): exclude_patterns = [ 'substitutions.rst', 'regression/header.md', + 'regression/README.md', 'regression/*.ipynb' # exclude IPyNB files from the build ] diff --git a/doc/source/regression/README.md b/doc/source/regression/README.md new file mode 100644 index 000000000..7a08717d7 --- /dev/null +++ b/doc/source/regression/README.md @@ -0,0 +1,56 @@ +# Regression folder + +This folder contains various useful examples illustrating how to use and how not +to use PyWavelets. + +The examples are written in the [MyST markdown notebook +format](https://myst-nb.readthedocs.io/en/v0.13.2/use/markdown.html). This +allows each .md file to function simultaneously as documentation that can be fed +into Sphinx and as a source file that can be converted to the Jupyter notebook +format (.ipynb), which can then be opened in notebook applications such as +JupyterLab. For this reason, each example page in this folder includes a header template +that adds a blurb to the top of each page about how the page can be +run or downloaded as a Jupyter notebook. + +There a few shortcomings to this approach of generating the code cell outputs in +the documentation pages at build time rather than hand editing them into the +document source file. One is that we can no longer compare the generated outputs +with the expected outputs as we used to do with doctest. Another is that we +lose some control over how we want the outputs to appear, unless we use a workaround. + +Here is the workaround we created. First we tell MyST-NB to remove the generated +cell output from the documentation page by adding the `remove-output` tag to the +`code-cell` directive in the markdown file. Then we hand code the output in a +`code-block` directive, not to be confused with `code-cell`! The `code-cell` +directive says "I am notebook code cell input, run me!" The `code-block` +directive says, "I am just a block of code for documentation purposes, don't run +me!" To the code block, we add the `.pywt-handcoded-cell-output` class so that +we can style it to look the same as other cell outputs on the same HTML page. +Finally, we tag the handcoded output with `jupyterlite_sphinx_strip` so that we +can exclude it when converting from .md to .ipynb. That way only generated +output appears in the .ipynb notebook. + +To recap: + +- We use the `remove-output` tag to remove the **generated** code cell output +during .md to .html conversion (this conversion is done by MyST-NB). +- We use the `jupyterlite_sphinx_strip` tag to remove the **handcoded** output +during .md to .ipynb conversion (this conversion is done by Jupytext). + +Example markdown: + + ```{code-cell} + :tags: [raises-exception, remove-output] + 1 / 0 + ``` + + +++ {"tags" ["jupyterlite_sphinx_strip"]} + + ```{code-block} python + :class: pywt-handcoded-cell-output + Traceback (most recent call last): + ... + ZeroDivisionError: division by zero + ``` + + +++ From 7a79a0c93cefc0a3830b071e720eb1417699773f Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 30 Dec 2024 13:01:01 -0600 Subject: [PATCH 07/18] Update regression/index.rst --- doc/source/regression/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/regression/index.rst b/doc/source/regression/index.rst index 21065886a..609a02843 100644 --- a/doc/source/regression/index.rst +++ b/doc/source/regression/index.rst @@ -5,9 +5,8 @@ Usage examples ============== -The following examples are used as doctest regression tests written using reST -markup. They are included in the documentation since they contain various useful -examples illustrating how to use and how not to use PyWavelets. +The following pages contain various useful examples illustrating how to use and +how not to use PyWavelets. For more usage examples see the `demo`_ directory in the source package. From e1d6b50ed68f285dda19fc4bd4b285576f268af1 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 30 Dec 2024 14:18:56 -0600 Subject: [PATCH 08/18] Fail docs build if cell throws but does not have raises-exception tag --- doc/source/conf.py | 5 ++--- doc/source/regression/dwt-idwt.md | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 344afe933..1472a694e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -400,13 +400,12 @@ def setup(app): os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1" +# https://myst-nb.readthedocs.io/en/latest/configuration.html nb_execution_mode = 'auto' nb_execution_timeout = 60 nb_execution_allow_errors = False - +nb_execution_raise_on_error = True nb_render_markdown_format = "myst" -render_markdown_format = "myst" - nb_remove_code_source = False nb_remove_code_outputs = False diff --git a/doc/source/regression/dwt-idwt.md b/doc/source/regression/dwt-idwt.md index 3c7096c00..c96bdb36d 100644 --- a/doc/source/regression/dwt-idwt.md +++ b/doc/source/regression/dwt-idwt.md @@ -9,9 +9,6 @@ kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 -mystnb: - execution_allow_errors: true - execution_show_tb: true --- +++ {"tags": ["jupyterlite_sphinx_strip"]} From 38e24d645235ee5e75cb01e07773e993cef5e588 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 30 Dec 2024 14:19:30 -0600 Subject: [PATCH 09/18] improve notebook doc page header --- doc/source/_static/pywavelets.css | 7 +------ doc/source/regression/header.md | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/source/_static/pywavelets.css b/doc/source/_static/pywavelets.css index fcff86097..7aa4e8326 100644 --- a/doc/source/_static/pywavelets.css +++ b/doc/source/_static/pywavelets.css @@ -31,12 +31,7 @@ Admonitions on this site are styled with some top margin. This makes sense when the admonition appears within the flow of the article. But when it is the very first child of an article, its top margin gets added to the article's top padding, resulting in too much whitespace. - -We use "tip" admonitions at the top of each doc in the regression/ directory. -This rule removes the top margin for tip admonitions when they occur as the very -first child of some container. This will not affect non-first-child tip -admonitions. */ -.admonition.tip:first-child { +.admonition.pywt-margin-top-0 { margin-top: 0; } diff --git a/doc/source/regression/header.md b/doc/source/regression/header.md index e2ba11988..d3729bc2b 100644 --- a/doc/source/regression/header.md +++ b/doc/source/regression/header.md @@ -1,13 +1,15 @@ ````{tip} +:class: pywt-margin-top-0 -This page can also be run or downloaded as a notebook. +This page can also be run or downloaded as a Jupyter notebook. -```{jupyterlite} {{ parent_docname }}.ipynb -:new_tab: True -``` +- Run in a new tab using JupyterLite[^what-is-lite]: + ```{jupyterlite} {{ parent_docname }}.ipynb + :new_tab: True + ``` +- Download: + - Jupyter notebook: {download}`{{ parent_docname }}.ipynb <{{ parent_docname }}.ipynb>` + - MyST markdown: {download}`{{ parent_docname }}.md <{{ parent_docname }}.md>` -Downloads: - -1. {download}`Download {{ parent_docname }}.ipynb <{{ parent_docname }}.ipynb>` -2. {download}`Download {{ parent_docname }}.md <{{ parent_docname }}.md>` +[^what-is-lite]: [JupyterLite](https://jupyterlite.readthedocs.io) is a distribution of JupyterLab that runs entirely within a web browser. All computations run in the browser; none are sent to a server. ```` From 1630ebab39d68c58f646e1fefb684d784e8aab24 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Tue, 31 Dec 2024 10:42:26 -0600 Subject: [PATCH 10/18] formatting --- doc/source/regression/README.md | 2 -- doc/source/regression/dwt-idwt.md | 18 ++++--------- doc/source/regression/gotchas.md | 2 -- doc/source/regression/modes.md | 15 ++++------- doc/source/regression/multilevel.md | 2 -- doc/source/regression/wavelet.md | 32 ++++++++++------------- doc/source/regression/wp.md | 39 +++++++++++------------------ doc/source/regression/wp2d.md | 24 +++++++----------- 8 files changed, 47 insertions(+), 87 deletions(-) diff --git a/doc/source/regression/README.md b/doc/source/regression/README.md index 7a08717d7..380fafc2c 100644 --- a/doc/source/regression/README.md +++ b/doc/source/regression/README.md @@ -52,5 +52,3 @@ Example markdown: ... ZeroDivisionError: division by zero ``` - - +++ diff --git a/doc/source/regression/dwt-idwt.md b/doc/source/regression/dwt-idwt.md index c96bdb36d..3d8e75ac4 100644 --- a/doc/source/regression/dwt-idwt.md +++ b/doc/source/regression/dwt-idwt.md @@ -16,13 +16,11 @@ kernelspec: ```{include} header.md ``` -+++ - # DWT and IDWT ## Discrete Wavelet Transform -Let's do a Discrete Wavelet Transform of some sample data `x` +Let's do a [Discrete Wavelet Transform](ref-dwt) of some sample data `x` using the `db2` wavelet. It's simple: ```{code-cell} @@ -44,7 +42,7 @@ cD ## Inverse Discrete Wavelet Transform -Now, let's do the opposite operation: an Inverse Discrete Wavelet Transform: +Now let's do the opposite operation, an [Inverse Discrete Wavelet Transform](ref-idwt): ```{code-cell} pywt.idwt(cA, cD, 'db2') @@ -54,7 +52,7 @@ Voilà! That's it! ## More examples -Now, let's experiment with `dwt` some more. For example, let's pass a +Now let's experiment with `dwt()` some more. For example, let's pass a `Wavelet` object instead of the wavelet name and specify the signal extension mode (the default is `Modes.symmetric`) for the border effect handling: @@ -76,7 +74,7 @@ Note that the output coefficients arrays' length depends not only on the input data length but also on the `Wavelet` type (particularly on its filters length `Wavelet.dec_len` that are used in the transformation). -To find out what the size of the output data will be, use the `dwt_coeff_len` +To find out what the size of the output data will be, use the `dwt_coeff_len()` function: ```{code-cell} @@ -97,7 +95,7 @@ Looks fine. (And if you expected that the output length would be a half of the input data length, well, that's the trade-off that allows for the perfect reconstruction...). -The third argument of the `dwt_coeff_len` function is the already mentioned signal +The third argument of the `dwt_coeff_len()` function is the already mentioned signal extension mode (please refer to the PyWavelets' documentation for the `modes` description). Currently, there are six extension modes available under `Modes`: @@ -165,8 +163,6 @@ Traceback (most recent call last): ValueError: At least one coefficient parameter must be specified. ``` -+++ - ### Coefficients data size in `pywt.idwt` When doing the `idwt` transform, usually the coefficient arrays @@ -188,8 +184,6 @@ Traceback (most recent call last): ValueError: Coefficients arrays must have the same size. ``` -+++ - Not every coefficient array can be used in `idwt`. In the following example the `idwt` will fail because the input arrays are invalid - they couldn't be created as a result of `dwt`, because @@ -212,8 +206,6 @@ Traceback (most recent call last): ValueError: Invalid coefficient arrays length for specified wavelet. Wavelet and mode must be the same as used for decomposition. ``` -+++ - ```{code-cell} int(pywt.dwt_coeff_len(1, pywt.Wavelet('db4').dec_len, 'symmetric')) ``` diff --git a/doc/source/regression/gotchas.md b/doc/source/regression/gotchas.md index b5615a25a..7dcfe20ce 100644 --- a/doc/source/regression/gotchas.md +++ b/doc/source/regression/gotchas.md @@ -16,8 +16,6 @@ kernelspec: ```{include} header.md ``` -+++ - # Gotchas PyWavelets utilizes `NumPy` under the hood. That's why handling the data diff --git a/doc/source/regression/modes.md b/doc/source/regression/modes.md index 2c9539a7d..771f45d3a 100644 --- a/doc/source/regression/modes.md +++ b/doc/source/regression/modes.md @@ -16,11 +16,9 @@ kernelspec: ```{include} header.md ``` -+++ - # Signal Extension Modes -Let's import `pywt`, first: +Import `pywt` first: ```{code-cell} import pywt @@ -39,13 +37,13 @@ def format_array(a): return numpy.array2string(a, precision=5, separator=' ', suppress_small=True) ``` -A list of available signal extension modes (`Modes`) is provided as follows: +A list of available signal extension [modes](Modes): ```{code-cell} pywt.Modes.modes ``` -Therefore, an invalid mode name should raise a `ValueError`: +An invalid mode name should raise a `ValueError`: ```{code-cell} --- @@ -71,7 +69,7 @@ for mode_name in ['zero', 'constant', 'symmetric', 'reflect', 'periodic', 'smoot print("Mode: %d (%s)" % (mode, mode_name)) ``` -The default mode is symmetric, i.e., `Modes.symmetric`: +The default mode is [symmetric](Modes.symmetric): ```{code-cell} cA, cD = pywt.dwt(x, 'db2') @@ -86,13 +84,10 @@ cD pywt.idwt(cA, cD, 'db2') ``` -And using a keyword argument: +Specify the mode using a keyword argument: ```{code-cell} cA, cD = pywt.dwt(x, 'db2', mode='symmetric') -``` - -```{code-cell} cA ``` diff --git a/doc/source/regression/multilevel.md b/doc/source/regression/multilevel.md index 8bcd41152..4ce6c9509 100644 --- a/doc/source/regression/multilevel.md +++ b/doc/source/regression/multilevel.md @@ -16,8 +16,6 @@ kernelspec: ```{include} header.md ``` -+++ - # Multilevel DWT, IDWT and SWT ## Multilevel DWT decomposition diff --git a/doc/source/regression/wavelet.md b/doc/source/regression/wavelet.md index f4509056a..60cb91a6f 100644 --- a/doc/source/regression/wavelet.md +++ b/doc/source/regression/wavelet.md @@ -16,8 +16,6 @@ kernelspec: ```{include} header.md ``` -+++ - # The Wavelet object ## Wavelet families and builtin Wavelets names @@ -73,8 +71,8 @@ print(w) But the most important bits of information are the wavelet filters coefficients, which are used in Discrete Wavelet Transform. These coefficients -can be obtained via the `Wavelet.dec_lo`, `Wavelet.dec_hi`, `Wavelet.rec_lo`, -and the `~Wavelet.rec_hi` attributes, which +can be obtained via the `Wavelet.dec_lo`, `dec_hi`, `rec_lo`, +and the `rec_hi` attributes, which correspond to lowpass & highpass decomposition filters, and lowpass & highpass reconstruction filters respectively: @@ -83,7 +81,7 @@ def print_array(arr): print("[%s]" % ", ".join(["%.14f" % x for x in arr])) ``` -Another way to get the filters data is to use the `Wavelet.filter_bank` +Another way to get the filters data is to use the `filter_bank` attribute, which returns all four filters in a tuple: ```{code-cell} @@ -92,7 +90,7 @@ w.filter_bank == (w.dec_lo, w.dec_hi, w.rec_lo, w.rec_hi) Other properties of a `Wavelet` object are: -1. `Wavelet.name`, `Wavelet.short_family_name`, and `Wavelet.family_name`: +`name`, `short_family_name`, and `family_name`: ```{code-cell} print(w.name) @@ -100,7 +98,7 @@ print(w.short_family_name) print(w.family_name) ``` -2. Decomposition (`Wavelet.dec_len`) and reconstruction (`Wavelet.rec_len`) filter lengths: +Decomposition (`dec_len`) and reconstruction (`rec_len`) filter lengths: ```{code-cell} w.dec_len @@ -110,7 +108,7 @@ w.dec_len w.rec_len ``` -3. Orthogonality (`Wavelet.orthogonal`) and biorthogonality (`Wavelet.biorthogonal`): +Orthogonality (`orthogonal`) and biorthogonality (`biorthogonal`): ```{code-cell} w.orthogonal @@ -120,14 +118,14 @@ w.orthogonal w.biorthogonal ``` -3. Symmetry (`Wavelet.symmetry`): +Symmetry (`symmetry`): ```{code-cell} print(w.symmetry) ``` -4. Number of vanishing moments for the scaling function `phi` (`Wavelet.vanishing_moments_phi`) - and the wavelet function `psi` (`Wavelet.vanishing_moments_psi`), associated with the filters: +Number of vanishing moments for the scaling function `phi` (`vanishing_moments_phi`) +and the wavelet function `psi` (`vanishing_moments_psi`), associated with the filters: ```{code-cell} w.vanishing_moments_phi @@ -138,7 +136,7 @@ w.vanishing_moments_psi ``` Now when we know a bit about the builtin Wavelets, let's see how to create -custom `Wavelets` objects. These can be done in two ways: +[custom wavelets](custom-wavelets). These can be created in two ways: 1. Passing the filter bank object that implements the `filter_bank` attribute. The attribute must return four filters coefficients. @@ -152,11 +150,7 @@ class MyHaarFilterBank(object): [sqrt(2)/2, sqrt(2)/2], [-sqrt(2)/2, sqrt(2)/2], [sqrt(2)/2, sqrt(2)/2], [sqrt(2)/2, -sqrt(2)/2] ) -``` -and let's put this in action: - -```{code-cell} my_wavelet = pywt.Wavelet('My Haar Wavelet', filter_bank=MyHaarFilterBank()) ``` @@ -171,7 +165,7 @@ my_filter_bank = ( my_wavelet = pywt.Wavelet('My Haar Wavelet', filter_bank=my_filter_bank) ``` -Note that such custom `Wavelets` objects **will not** have all the properties set +Note that such custom wavelets **will not** have all the properties set to correct values and some of them could be missing: ```{code-cell} @@ -197,7 +191,7 @@ We all know that the fun with wavelets is in wavelet functions. Now, what would this package be without a tool to compute wavelet and scaling functions approximations? -This is the purpose of the `Wavelet.wavefun` method, which is used to +This is the purpose of the `wavefun()` method, which is used to approximate scaling function (`phi`) and wavelet function (`psi`) at the given level of refinement, based on the filters coefficients. @@ -230,7 +224,7 @@ w.orthogonal ``` :::{seealso} -You can find live examples of the usage of `Wavelet.wavefun` and +You can find live examples of the usage of `wavefun()` and images of all the built-in wavelets on the [Wavelet Properties Browser](http://wavelets.pybytes.com) page. diff --git a/doc/source/regression/wp.md b/doc/source/regression/wp.md index febaf8230..4a889c1fb 100644 --- a/doc/source/regression/wp.md +++ b/doc/source/regression/wp.md @@ -16,8 +16,6 @@ kernelspec: ```{include} header.md ``` -+++ - # Wavelet Packets ## `import pywt` and construct a helper function @@ -26,7 +24,7 @@ kernelspec: import pywt ``` -This helper function that can format arrays in a consistent manner across +This helper function can format arrays in a consistent manner across different systems. Please note that this function is just for the purpose of this example and is not part of the PyWavelets library, and it is not necessary or required to use it in your own code: @@ -53,14 +51,12 @@ The input data and decomposition coefficients are stored in the ```{code-cell} print(wp.data) -[1, 2, 3, 4, 5, 6, 7, 8] ``` -Nodes `Node` are identified by their paths, i.e., `Node.path`. For the root +Nodes are identified by their paths, i.e., `Node.path`. For the root node, the path is `''` and the decomposition level is `0`. ```{code-cell} -# Should return blank print(repr(wp.path)) ``` @@ -90,9 +86,9 @@ First check what is the maximum level of decomposition: print(wp.maxlevel) ``` -and try accessing subnodes of the WP tree: +and try accessing subnodes of the WP tree. -- 1st level: +1st level: ```{code-cell} print(wp['a'].data) @@ -102,7 +98,7 @@ print(wp['a'].data) print(wp['a'].path) ``` -- 2nd level: +2nd level: ```{code-cell} print(wp['aa'].data) @@ -112,7 +108,7 @@ print(wp['aa'].data) print(wp['aa'].path) ``` -- 3rd level: +3rd level: ```{code-cell} print(wp['aaa'].data) @@ -140,9 +136,6 @@ Traceback (most recent call last): IndexError: Path length is out of range. ``` -+++ - - Now, try an invalid path: ```{code-cell} @@ -161,8 +154,6 @@ Traceback (most recent call last): ValueError: Subnode name must be in ['a', 'd'], not 'c'. ``` -+++ - which just yielded a `ValueError`. ### Accessing `Node`'s attributes @@ -174,9 +165,9 @@ of the `Node` class (which in turn inherits from the `BaseNode` class). Tree nodes can be accessed using the `obj[x]` (`Node.__getitem__`) operator. -Each tree node has a set of attributes: `Node.data`, `Node.path`, -`Node.node_name`, `Node.parent`, `Node.level`, -`Node.maxlevel` and `Node.mode`. +Each tree node has a set of attributes: `data`, `path`, +`node_name`, `parent`, `level`, +`maxlevel` and `mode`. ```{code-cell} x = [1, 2, 3, 4, 5, 6, 7, 8] @@ -230,7 +221,7 @@ or sorted based on the band frequency (`freq`): print([node.path for node in wp.get_level(3, 'freq')]) ``` -Note that `WaveletPacket.get_level` also performs automatic decomposition +Note that `WaveletPacket.get_level()` also performs automatic decomposition until it reaches the specified `level`. ## Reconstructing data from Wavelet Packets @@ -240,7 +231,7 @@ x = [1, 2, 3, 4, 5, 6, 7, 8] wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric') ``` -Now, let's create a new instance of the `WaveletPacket` class and set its nodes +Now create a new instance of the `WaveletPacket` class and set its nodes with some data. ```{code-cell} @@ -362,7 +353,7 @@ x = [1, 2, 3, 4, 5, 6, 7, 8] wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric') ``` -1. At first the wp's attribute `a` is `None`: +At first the wp's attribute `a` is `None`: ```{code-cell} print(wp.a) @@ -370,14 +361,14 @@ print(wp.a) **Remember that you should not rely on the attribute access.** -2. At the first attempt to access the node, it is computed via the decomposition - of its parent node (which is the `wp` object itself). +At the first attempt to access the node, it is computed via the decomposition +of its parent node (which is the `wp` object itself). ```{code-cell} print(wp['a']) ``` -3. Now, `wp.a` is set to the newly created node: +Now `wp.a` is set to the newly created node: ```{code-cell} print(wp.a) diff --git a/doc/source/regression/wp2d.md b/doc/source/regression/wp2d.md index 4fb9a91f6..ee4ffdaa8 100644 --- a/doc/source/regression/wp2d.md +++ b/doc/source/regression/wp2d.md @@ -16,8 +16,6 @@ kernelspec: ```{include} header.md ``` -+++ - # 2D Wavelet Packets ## Import pywt @@ -36,7 +34,7 @@ x = numpy.array([[1, 2, 3, 4, 5, 6, 7, 8]] * 8, 'd') print(x) ``` -Now create a 2D Wavelet Packet `pywt.WaveletPacket2D` object: +Now create a 2D [Wavelet Packet](ref-wp) object: ```{code-cell} wp = pywt.WaveletPacket2D(data=x, wavelet='db1', mode='symmetric') @@ -49,7 +47,7 @@ The input `data` and decomposition coefficients are stored in the print(wp.data) ``` -Nodes (the `Node2D>` class) are identified by paths. For the root node, the path is +Nodes (the `Node2D` class) are identified by paths. For the root node, the path is `''` and the decomposition level is `0`. ```{code-cell} @@ -161,8 +159,6 @@ Traceback (most recent call last): IndexError: Path length is out of range. ``` -+++ - Oops, we have reached the maximum level of decomposition for the `'aaaa'` path, which, by the way, was: @@ -188,8 +184,6 @@ Traceback (most recent call last): ValueError: Subnode name must be in ['a', 'h', 'v', 'd'], not 'f'. ``` -+++ - ### Accessing Node2D's attributes `WaveletPacket2D` is a tree data structure, which evaluates to a set @@ -233,7 +227,7 @@ print(wp['av'].mode) We can get all nodes on the particular level using the `WaveletPacket2D.get_level` method: -- 0 level - the root `wp` node: +0 level - the root `wp` node: ```{code-cell} len(wp.get_level(0)) @@ -253,7 +247,7 @@ len(wp.get_level(1)) print([node.path for node in wp.get_level(1)]) ``` -- 2nd level of decomposition: +2nd level of decomposition: ```{code-cell} len(wp.get_level(2)) @@ -268,7 +262,7 @@ for i, path in enumerate(paths): print(path, end=' ') ``` -- 3rd level of decomposition: +3rd level of decomposition: ```{code-cell} print(len(wp.get_level(3))) @@ -407,7 +401,7 @@ x = numpy.array([[1, 2, 3, 4, 5, 6, 7, 8]] * 8) wp = pywt.WaveletPacket2D(data=x, wavelet='db1', mode='symmetric') ``` -1. At first, the `wp`'s attribute `a` is `None` +At first, the `wp`'s attribute `a` is `None` ```{code-cell} print(wp.a) @@ -415,14 +409,14 @@ print(wp.a) **Remember that you should not rely on the attribute access.** -2. During the first attempt to access the node it is computed - via decomposition of its parent node (the wp object itself). +During the first attempt to access the node it is computed +via decomposition of its parent node (the wp object itself). ```{code-cell} print(wp['a']) ``` -3. Now the `a` is set to the newly created node: +Now the `a` is set to the newly created node: ```{code-cell} print(wp.a) From 4c663d1a2f8b266971c42a7044115258d7e71b91 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Tue, 31 Dec 2024 14:11:34 -0600 Subject: [PATCH 11/18] follow Melissa's style --- doc/source/_static/myst-nb.css | 83 ++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css index 699e86606..75ff69ee3 100644 --- a/doc/source/_static/myst-nb.css +++ b/doc/source/_static/myst-nb.css @@ -7,54 +7,61 @@ In some cases these rules override MyST-NB. In some cases they override PyData Sphinx Theme or Sphinx. And in some cases they do not override existing styling but add new styling. */ -/* -Undo the border that MyST-NB puts on the input cell -*/ +div.cell div.cell_input::before { + content: "\00A0In:"; + font-weight: 600; + + background-color: var(--pst-color-background); + + border-top-left-radius: var(--mystnb-source-border-radius); + border-top-right-radius: var(--mystnb-source-border-radius); +} + div.cell div.cell_input { - border: none; + border-left-color: var(--pst-color-border); } -/* -Undo border radius on code cells (this styling actually comes from Sphinx, not MyST-NB) -*/ -div.cell pre, -div.cell div.highlight, -div.pywt-handcoded-cell-output, -div.pywt-handcoded-cell-output pre, -div.pywt-handcoded-cell-output div.highlight { - border-radius: 0; +div.cell div.cell_output::before, +div.pywt-handcoded-cell-output::before { + content: "\00A0Out:"; + font-weight: 600; + + display: block; + + border: thin solid var(--pst-color-surface); + border-bottom: none; + border-top-left-radius: var(--mystnb-source-border-radius); + border-top-right-radius: var(--mystnb-source-border-radius); } -/* -Add border to output container, remove background color. -*/ -div.cell div.cell_output, -div.pywt-handcoded-cell-output { - background-color: var(--pst-color-background); +div.cell div.cell_output { border-radius: 0; - border-color: var(--pst-color-border-muted); - border-style: solid; - border-width: 1px; - border-top-style: double; - border-top-width: medium; } + div.cell div.cell_output div.output, -div.cell div.cell_output pre, -div.pywt-handcoded-cell-output pre { - background-color: transparent; - border: none; -} +div.pywt-handcoded-cell-output { + border-color: var(--pst-color-surface); + + border-radius: var(--mystnb-source-border-radius); + border-top-left-radius: 0; + border-top-right-radius: 0; -/* -Bring output up against input (remove extra whitespace) -*/ -div.cell div.cell_output, -div.cell div.cell_output > :first-child { margin-top: 0; } -div.cell:has(+ div.pywt-handcoded-cell-output) { - margin-bottom: 0; + +div.cell div[class*=highlight-], +div.cell div.highlight, +div.pywt-handcoded-cell-output { + border-radius: var(--mystnb-source-border-radius); } -div.cell + div.pywt-handcoded-cell-output { - margin-top: 0; + +div.cell div.cell_input pre, +div.cell div.cell_output pre { + border-radius: var(--mystnb-source-border-radius); + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +div.pywt-handcoded-cell-output pre { + border: none; } From 76d5544f26a7f00042bd6fe5836dc4d683a8dd68 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Tue, 31 Dec 2024 17:48:19 -0600 Subject: [PATCH 12/18] fix dark mode --- doc/source/_static/myst-nb.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css index 75ff69ee3..263ea3b2f 100644 --- a/doc/source/_static/myst-nb.css +++ b/doc/source/_static/myst-nb.css @@ -18,7 +18,8 @@ div.cell div.cell_input::before { } div.cell div.cell_input { - border-left-color: var(--pst-color-border); + background-color: inherit; + border-color: var(--pst-color-border); } div.cell div.cell_output::before, @@ -40,6 +41,8 @@ div.cell div.cell_output { div.cell div.cell_output div.output, div.pywt-handcoded-cell-output { + background-color: inherit; + border-color: var(--pst-color-surface); border-radius: var(--mystnb-source-border-radius); From fb0d107b1a68ab10f80bf75c271e9c1f36bec456 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Thu, 2 Jan 2025 07:34:33 -0600 Subject: [PATCH 13/18] Bump jupyterlite-sphinx to 0.17.1 to use notebooklite --- doc/source/regression/header.md | 2 +- util/readthedocs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/regression/header.md b/doc/source/regression/header.md index d3729bc2b..05b713223 100644 --- a/doc/source/regression/header.md +++ b/doc/source/regression/header.md @@ -4,7 +4,7 @@ This page can also be run or downloaded as a Jupyter notebook. - Run in a new tab using JupyterLite[^what-is-lite]: - ```{jupyterlite} {{ parent_docname }}.ipynb + ```{notebooklite} {{ parent_docname }}.ipynb :new_tab: True ``` - Download: diff --git a/util/readthedocs/requirements.txt b/util/readthedocs/requirements.txt index 0857c6b6d..878fb3a75 100644 --- a/util/readthedocs/requirements.txt +++ b/util/readthedocs/requirements.txt @@ -1,6 +1,6 @@ cython docutils -jupyterlite-sphinx>=0.16.2 +jupyterlite-sphinx>=0.17.1 jupyterlite-pyodide-kernel jupytext pydata-sphinx-theme From 45f802380a31d002c73388351a7dda683d28f2f2 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Thu, 2 Jan 2025 08:15:47 -0600 Subject: [PATCH 14/18] suggestions from code review --- doc/source/_static/myst-nb.css | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css index 263ea3b2f..674dfa601 100644 --- a/doc/source/_static/myst-nb.css +++ b/doc/source/_static/myst-nb.css @@ -8,13 +8,15 @@ Sphinx Theme or Sphinx. And in some cases they do not override existing styling but add new styling. */ div.cell div.cell_input::before { - content: "\00A0In:"; - font-weight: 600; + content: "In"; background-color: var(--pst-color-background); border-top-left-radius: var(--mystnb-source-border-radius); border-top-right-radius: var(--mystnb-source-border-radius); + + /* align with code below, this is the same padding applied
 by PyData Sphinx Theme */
+  padding-left: 1rem;
 }
 
 div.cell div.cell_input {
@@ -24,8 +26,7 @@ div.cell div.cell_input {
 
 div.cell div.cell_output::before,
 div.pywt-handcoded-cell-output::before {
-  content: "\00A0Out:";
-  font-weight: 600;
+  content: "Out";
 
   display: block;
 
@@ -33,6 +34,9 @@ div.pywt-handcoded-cell-output::before {
   border-bottom: none;
   border-top-left-radius: var(--mystnb-source-border-radius);
   border-top-right-radius: var(--mystnb-source-border-radius);
+
+  /* align with code below, this is the same padding applied 
 by PyData Sphinx Theme */
+  padding-left: 1rem;
 }
 
 div.cell div.cell_output {

From 49df46d24cd06c24583eb2174719e4fbc42ab4a0 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 2 Jan 2025 08:56:27 -0600
Subject: [PATCH 15/18] Make the output left aligned with the input

---
 doc/source/_static/myst-nb.css | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css
index 674dfa601..6fcd63139 100644
--- a/doc/source/_static/myst-nb.css
+++ b/doc/source/_static/myst-nb.css
@@ -15,13 +15,15 @@ div.cell div.cell_input::before {
   border-top-left-radius: var(--mystnb-source-border-radius);
   border-top-right-radius: var(--mystnb-source-border-radius);
 
-  /* align with code below, this is the same padding applied 
 by PyData Sphinx Theme */
+  /* Left-align with the code below.
+     This is the same padding applied 
 by PyData Sphinx Theme */
   padding-left: 1rem;
 }
 
 div.cell div.cell_input {
   background-color: inherit;
   border-color: var(--pst-color-border);
+  border-left-width: 0.2rem;
 }
 
 div.cell div.cell_output::before,
@@ -35,12 +37,17 @@ div.pywt-handcoded-cell-output::before {
   border-top-left-radius: var(--mystnb-source-border-radius);
   border-top-right-radius: var(--mystnb-source-border-radius);
 
-  /* align with code below, this is the same padding applied 
 by PyData Sphinx Theme */
+  /* Left-align with the code below.
+     This is the same padding applied 
 by PyData Sphinx Theme */
   padding-left: 1rem;
 }
 
 div.cell div.cell_output {
   border-radius: 0;
+
+  /* Left-align the text in the output with the text in the input.
+     This value equals the border-left-width of the input cell */
+  margin-left: 0.2rem;
 }
 
 div.cell div.cell_output div.output,

From 1b0ddfd40a25603cc31ebdecf4e64ef56a5038ba Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 2 Jan 2025 13:15:53 -0600
Subject: [PATCH 16/18] Improve CSS

---
 doc/source/_static/myst-nb.css | 92 +++++++++++++++++-----------------
 1 file changed, 45 insertions(+), 47 deletions(-)

diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css
index 6fcd63139..bae5216b4 100644
--- a/doc/source/_static/myst-nb.css
+++ b/doc/source/_static/myst-nb.css
@@ -7,75 +7,73 @@ In some cases these rules override MyST-NB. In some cases they override PyData
 Sphinx Theme or Sphinx. And in some cases they do not override existing styling
 but add new styling. */
 
-div.cell div.cell_input::before {
-  content: "In";
+/* Set up a few variables for this stylesheet */
+.cell,
+.pywt-handcoded-cell-output {
+  --pywt-cell-input-border-left-width: .2rem;
+
+  /* This matches the padding applied to 
 elements by PyData Sphinx Theme */
+  --pywt-code-block-padding: 1rem;
 
-  background-color: var(--pst-color-background);
+  /* override mystnb */
+  --mystnb-source-border-radius: .25rem; /* match PyData Sphinx Theme */
+}
 
-  border-top-left-radius: var(--mystnb-source-border-radius);
-  border-top-right-radius: var(--mystnb-source-border-radius);
+.cell .cell_input::before {
+  content: "In";
 
-  /* Left-align with the code below.
-     This is the same padding applied 
 by PyData Sphinx Theme */
-  padding-left: 1rem;
+  /* Left-aligns the text in this box and the one that follows it */
+  padding-left: var(--pywt-code-block-padding);
 }
 
+/* Cannot use `.cell .cell_input` selector because the stylesheet from MyST-NB
+   uses `div.cell div.cell_input` and we want to override those rules */
 div.cell div.cell_input {
   background-color: inherit;
   border-color: var(--pst-color-border);
-  border-left-width: 0.2rem;
+  border-left-width: var(--pywt-cell-input-border-left-width);
+  background-clip: padding-box;
+  overflow: hidden;
 }
 
-div.cell div.cell_output::before,
-div.pywt-handcoded-cell-output::before {
-  content: "Out";
-
-  display: block;
-
-  border: thin solid var(--pst-color-surface);
-  border-bottom: none;
-  border-top-left-radius: var(--mystnb-source-border-radius);
-  border-top-right-radius: var(--mystnb-source-border-radius);
-
-  /* Left-align with the code below.
-     This is the same padding applied 
 by PyData Sphinx Theme */
-  padding-left: 1rem;
+.cell .cell_output,
+.pywt-handcoded-cell-output {
+  border: var(--mystnb-source-border-width) solid var(--pst-color-surface);
+  border-radius: var(--mystnb-source-border-radius);
+  background-clip: padding-box;
+  overflow: hidden;
 }
 
-div.cell div.cell_output {
-  border-radius: 0;
+.cell .cell_output::before,
+.pywt-handcoded-cell-output::before {
+  content: "Out";
+  display: block;
 
-  /* Left-align the text in the output with the text in the input.
-     This value equals the border-left-width of the input cell */
-  margin-left: 0.2rem;
+  /* Left-aligns the text in this box and the one that follows it */
+  padding-left: var(--pywt-code-block-padding);
 }
 
-div.cell div.cell_output div.output,
-div.pywt-handcoded-cell-output {
+.cell .cell_output .output {
   background-color: inherit;
-
-  border-color: var(--pst-color-surface);
-
-  border-radius: var(--mystnb-source-border-radius);
-  border-top-left-radius: 0;
-  border-top-right-radius: 0;
-
+  border: none;
   margin-top: 0;
 }
 
-div.cell div[class*=highlight-],
-div.cell div.highlight,
+.cell .cell_output,
+/* must prefix the following selector with `div.` to override Sphinx margin rule on div[class*=highlight-] */
 div.pywt-handcoded-cell-output {
-  border-radius: var(--mystnb-source-border-radius);
+  /* Left-align the text in the output with the text in the input */
+  margin-left: calc(var(--pywt-cell-input-border-left-width) - var(--mystnb-source-border-width));
 }
 
-div.cell div.cell_input pre,
-div.cell div.cell_output pre {
-  border-radius: var(--mystnb-source-border-radius);
-  border-top-left-radius: 0;
-  border-top-right-radius: 0;
+.cell .cell_output .output,
+.cell .cell_input pre,
+.cell .cell_output pre,
+.pywt-handcoded-cell-output .highlight,
+.pywt-handcoded-cell-output pre {
+  border-radius: 0;
 }
 
-div.pywt-handcoded-cell-output pre {
-  border: none;
+.pywt-handcoded-cell-output pre {
+  border: none; /* MyST-NB sets border to none for 
 tags inside div.cell */
 }

From 3402f8801291a71c50380d436d080addcd5c3366 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 2 Jan 2025 16:22:17 -0600
Subject: [PATCH 17/18] Match font weight to captioned code block (plus bottom
 border)

---
 doc/source/_static/myst-nb.css | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css
index bae5216b4..0eede7104 100644
--- a/doc/source/_static/myst-nb.css
+++ b/doc/source/_static/myst-nb.css
@@ -21,6 +21,7 @@ but add new styling. */
 
 .cell .cell_input::before {
   content: "In";
+  font-weight: var(--pst-font-weight-caption);
 
   /* Left-aligns the text in this box and the one that follows it */
   padding-left: var(--pywt-code-block-padding);
@@ -48,6 +49,8 @@ div.cell div.cell_input {
 .pywt-handcoded-cell-output::before {
   content: "Out";
   display: block;
+  font-weight: var(--pst-font-weight-caption);
+  border-bottom: var(--mystnb-source-border-width) solid var(--pst-color-border);
 
   /* Left-aligns the text in this box and the one that follows it */
   padding-left: var(--pywt-code-block-padding);

From e6970f3ab3cc417a2e84780daef5645db5a7a2a5 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 2 Jan 2025 16:32:17 -0600
Subject: [PATCH 18/18] oops, put the border in the right place

---
 doc/source/_static/myst-nb.css | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/source/_static/myst-nb.css b/doc/source/_static/myst-nb.css
index 0eede7104..bd30038a1 100644
--- a/doc/source/_static/myst-nb.css
+++ b/doc/source/_static/myst-nb.css
@@ -21,6 +21,7 @@ but add new styling. */
 
 .cell .cell_input::before {
   content: "In";
+  border-bottom: var(--mystnb-source-border-width) solid var(--pst-color-border);
   font-weight: var(--pst-font-weight-caption);
 
   /* Left-aligns the text in this box and the one that follows it */
@@ -50,7 +51,6 @@ div.cell div.cell_input {
   content: "Out";
   display: block;
   font-weight: var(--pst-font-weight-caption);
-  border-bottom: var(--mystnb-source-border-width) solid var(--pst-color-border);
 
   /* Left-aligns the text in this box and the one that follows it */
   padding-left: var(--pywt-code-block-padding);