diff --git a/content/05_erps/02_hnn_core.md b/content/05_erps/02_hnn_core.md index b373796..f60d5ca 100644 --- a/content/05_erps/02_hnn_core.md +++ b/content/05_erps/02_hnn_core.md @@ -10,4 +10,4 @@ # Christopher Bailey --> -[[plot_simulate_evoked.ipynb]] \ No newline at end of file +[[plot_simulate_evoked.ipynb]] diff --git a/content/05_erps/output_nb_plot_simulate_evoked/fig_03.png b/content/05_erps/output_nb_plot_simulate_evoked/fig_03.png index 2dfe7f3..36af440 100644 Binary files a/content/05_erps/output_nb_plot_simulate_evoked/fig_03.png and b/content/05_erps/output_nb_plot_simulate_evoked/fig_03.png differ diff --git a/content/05_erps/output_nb_plot_simulate_evoked/fig_04.png b/content/05_erps/output_nb_plot_simulate_evoked/fig_04.png index 3e80b48..8700816 100644 Binary files a/content/05_erps/output_nb_plot_simulate_evoked/fig_04.png and b/content/05_erps/output_nb_plot_simulate_evoked/fig_04.png differ diff --git a/content/05_erps/plot_simulate_evoked.html b/content/05_erps/plot_simulate_evoked.html index ee8c653..c567c69 100644 --- a/content/05_erps/plot_simulate_evoked.html +++ b/content/05_erps/plot_simulate_evoked.html @@ -152,26 +152,25 @@

01. Simulate Event Related Potentials (ERPs)

[Done] Trial 1: 0.03 ms... +Trial 1: 10.0 ms... - Trial 1: 10.0 ms... -Trial 1: 20.0 ms... + Trial 1: 20.0 ms... Trial 1: 30.0 ms... Trial 1: 40.0 ms... Trial 1: 50.0 ms... Trial 1: 60.0 ms... Trial 1: 70.0 ms... Trial 1: 80.0 ms... +Trial 1: 90.0 ms... - Trial 1: 90.0 ms... -Trial 1: 100.0 ms... + Trial 1: 100.0 ms... Trial 1: 110.0 ms... Trial 1: 120.0 ms... Trial 1: 130.0 ms... Trial 1: 140.0 ms... Trial 1: 150.0 ms... Trial 1: 160.0 ms... - - Building the NEURON model +Building the NEURON model [Done] Trial 2: 0.03 ms... @@ -184,9 +183,9 @@

01. Simulate Event Related Potentials (ERPs)

Trial 2: 60.0 ms... Trial 2: 70.0 ms... Trial 2: 80.0 ms... -Trial 2: 90.0 ms... - Trial 2: 100.0 ms... + Trial 2: 90.0 ms... +Trial 2: 100.0 ms... Trial 2: 110.0 ms... Trial 2: 120.0 ms... Trial 2: 130.0 ms... @@ -328,10 +327,10 @@

01. Simulate Event Related Potentials (ERPs)

Trial 1: 60.0 ms... Trial 1: 70.0 ms... Trial 1: 80.0 ms... - - Trial 1: 90.0 ms... +Trial 1: 90.0 ms... Trial 1: 100.0 ms... -Trial 1: 110.0 ms... + + Trial 1: 110.0 ms... Trial 1: 120.0 ms... Trial 1: 130.0 ms... Trial 1: 140.0 ms... diff --git a/content/05_erps/plot_simulate_evoked.json b/content/05_erps/plot_simulate_evoked.json index cddd790..e67c4ec 100644 --- a/content/05_erps/plot_simulate_evoked.json +++ b/content/05_erps/plot_simulate_evoked.json @@ -1 +1,16 @@ -{"plot_simulate_evoked.ipynb": {"01. Simulate Event Related Potentials (ERPs)": {"contents": "

01. Simulate Event Related Potentials (ERPs)

", "sections": {"References": {"contents": "

References

.. [1] Jones, Stephanie R., et al. \"Neural correlates of tactile detection:\n a combined magnetoencephalography and biophysically based computational\n modeling study.\" Journal of Neuroscience 27.40 (2007): 10751-10764.

"}}}}} \ No newline at end of file +{ + "plot_simulate_evoked.ipynb": { + "01. Simulate Event Related Potentials (ERPs)": { + "title": "01. Simulate Event Related Potentials (ERPs)", + "level": 1, + "html": "
\n

01. Simulate Event Related Potentials (ERPs)

\n
\n
\n

This example demonstrates how to simulate a threshold level tactile\nevoked response, as detailed in the HNN GUI ERP tutorial,\nusing HNN-core. We recommend you first review the GUI tutorial.

\n

The workflow below recreates an example of the threshold level tactile\nevoked response, as observed in Jones et al. J. Neuroscience 2007 [1]_\n(e.g. Figure 7 in the GUI tutorial), albeit without a direct comparison\nto the recorded data.

\n
\n
\n\n# Authors: Mainak Jas \n# Sam Neymotin \n# Blake Caldwell \n# Christopher Bailey \n# sphinx_gallery_thumbnail_number = 3\nimport os.path as op\nimport tempfile\nimport matplotlib.pyplot as plt\n\n
\n
\n

Let us import hnn_core

\n
\n
\n\nimport hnn_core\nfrom hnn_core import simulate_dipole, jones_2009_model\nfrom hnn_core.viz import plot_dipole\n\n
\n
\n

Let us first create our default network and visualize the cells\ninside it.

\n
\n
\n\nnet = jones_2009_model()\nnet.plot_cells()\nnet.cell_types['L5_pyramidal'].plot_morphology()\n\n
\n
\nOut:\n
\n
\n<Figure size 640x480 with 1 Axes>\n
\n
\n
\n\n
\n
\nOut:\n
\n
\n<Figure size 640x480 with 1 Axes>\n
\n
\n
\n\n
\n
\nOut:\n
\n
\n<Axes3D: >\n
\n
\n
\n

The network of cells is now defined, to which we add external drives as\nrequired. Weights are prescribed separately for AMPA and NMDA receptors\n(receptors that are not used can be omitted or set to zero). The possible\ndrive types include the following (click on the links for documentation):

\n\n
\n
\n

First, we add a distal evoked drive

\n
\n
\n\nweights_ampa_d1 = {'L2_basket': 0.006562, 'L2_pyramidal': .000007,\n'L5_pyramidal': 0.142300}\nweights_nmda_d1 = {'L2_basket': 0.019482, 'L2_pyramidal': 0.004317,\n'L5_pyramidal': 0.080074}\nsynaptic_delays_d1 = {'L2_basket': 0.1, 'L2_pyramidal': 0.1,\n'L5_pyramidal': 0.1}\nnet.add_evoked_drive(\n'evdist1', mu=63.53, sigma=3.85, numspikes=1, weights_ampa=weights_ampa_d1,\nweights_nmda=weights_nmda_d1, location='distal',\nsynaptic_delays=synaptic_delays_d1, event_seed=274)\n\n
\n
\n

Then, we add two proximal drives

\n
\n
\n\nweights_ampa_p1 = {'L2_basket': 0.08831, 'L2_pyramidal': 0.01525,\n'L5_basket': 0.19934, 'L5_pyramidal': 0.00865}\nsynaptic_delays_prox = {'L2_basket': 0.1, 'L2_pyramidal': 0.1,\n'L5_basket': 1., 'L5_pyramidal': 1.}\n# all NMDA weights are zero; pass None explicitly\nnet.add_evoked_drive(\n'evprox1', mu=26.61, sigma=2.47, numspikes=1, weights_ampa=weights_ampa_p1,\nweights_nmda=None, location='proximal',\nsynaptic_delays=synaptic_delays_prox, event_seed=544)\n# Second proximal evoked drive. NB: only AMPA weights differ from first\nweights_ampa_p2 = {'L2_basket': 0.000003, 'L2_pyramidal': 1.438840,\n'L5_basket': 0.008958, 'L5_pyramidal': 0.684013}\n# all NMDA weights are zero; omit weights_nmda (defaults to None)\nnet.add_evoked_drive(\n'evprox2', mu=137.12, sigma=8.33, numspikes=1,\nweights_ampa=weights_ampa_p2, location='proximal',\nsynaptic_delays=synaptic_delays_prox, event_seed=814)\n\n
\n
\n

Now let's simulate the dipole, running 2 trials with the\nhnn_core.parallel_backends.Joblib backend.\nTo run them in parallel we could set n_jobs to equal the number of\ntrials. The Joblib backend allows running the simulations in parallel\nacross trials.

\n
\n
\n\nfrom hnn_core import JoblibBackend\nwith JoblibBackend(n_jobs=2):\ndpls = simulate_dipole(net, tstop=170., n_trials=2)\n\n
\n
\nOut:\n
\n
\nJoblib will run 2 trial(s) in parallel by distributing trials over 2 jobs.\nLoading custom mechanism files from /opt/homebrew/Caskroom/miniconda/base/envs/py311/lib/python3.11/site-packages/hnn_core/mod/arm64/.libs/libnrnmech.so\nBuilding the NEURON model\n[Done]\nTrial 1: 0.03 ms...\nTrial 1: 10.0 ms...\nTrial 1: 20.0 ms...\nTrial 1: 30.0 ms...\nTrial 1: 40.0 ms...\nTrial 1: 50.0 ms...\nTrial 1: 60.0 ms...\nTrial 1: 70.0 ms...\nTrial 1: 80.0 ms...\nTrial 1: 90.0 ms...\nTrial 1: 100.0 ms...\nTrial 1: 110.0 ms...\nTrial 1: 120.0 ms...\nTrial 1: 130.0 ms...\nTrial 1: 140.0 ms...\nTrial 1: 150.0 ms...\nTrial 1: 160.0 ms...\nBuilding the NEURON model\n[Done]\nTrial 2: 0.03 ms...\nTrial 2: 10.0 ms...\nTrial 2: 20.0 ms...\nTrial 2: 30.0 ms...\nTrial 2: 40.0 ms...\nTrial 2: 50.0 ms...\nTrial 2: 60.0 ms...\nTrial 2: 70.0 ms...\nTrial 2: 80.0 ms...\nTrial 2: 90.0 ms...\nTrial 2: 100.0 ms...\nTrial 2: 110.0 ms...\nTrial 2: 120.0 ms...\nTrial 2: 130.0 ms...\nTrial 2: 140.0 ms...\nTrial 2: 150.0 ms...\nTrial 2: 160.0 ms...\n
\n
\n
\n

Rather than reading smoothing and scaling parameters from file, we recommend\nexplicit use of the ~hnn_core.dipole.Dipole.smooth and\n~hnn_core.dipole.Dipole.scale methods instead. Note that both methods\noperate in-place, i.e., the objects are modified.

\n
\n
\n\nwindow_len, scaling_factor = 30, 3000\nfor dpl in dpls:\ndpl.smooth(window_len).scale(scaling_factor)\n\n
\n
\n

Plot the amplitudes of the simulated aggregate dipole moments over time

\n
\n
\n\nimport matplotlib.pyplot as plt\nfig, axes = plt.subplots(2, 1, sharex=True, figsize=(6, 6),\nconstrained_layout=True)\nplot_dipole(dpls, ax=axes[0], layer='agg', show=False)\nnet.cell_response.plot_spikes_hist(ax=axes[1],\nspike_types=['evprox', 'evdist']);\n\n
\n
\nOut:\n
\n
\n<Figure size 600x600 with 2 Axes>\n
\n
\n
\n\n
\n
\n

If you want to analyze how the different cortical layers contribute to\ndifferent net waveform features, then instead of passing &#x27;agg&#x27; to\nlayer, you can provide a list of layers to be visualized and optionally\na list of axes to ax to visualize the dipole moments separately.

\n
\n
\n\nplot_dipole(dpls, average=False, layer=['L2', 'L5', 'agg'], show=False);\n\n
\n
\nOut:\n
\n
\n<Figure size 640x480 with 3 Axes>\n
\n
\n
\n\n
\n
\n

Now, let us try to make the exogenous driving inputs to the cells\nsynchronous and see what happens. This is achieved by setting\nn_drive_cells=1 and cell_specific=False when adding each drive.

\n
\n
\n\nnet_sync = jones_2009_model()\nn_drive_cells=1\ncell_specific=False\nnet_sync.add_evoked_drive(\n'evdist1', mu=63.53, sigma=3.85, numspikes=1, weights_ampa=weights_ampa_d1,\nweights_nmda=weights_nmda_d1, location='distal', n_drive_cells=n_drive_cells,\ncell_specific=cell_specific, synaptic_delays=synaptic_delays_d1, event_seed=274)\nnet_sync.add_evoked_drive(\n'evprox1', mu=26.61, sigma=2.47, numspikes=1, weights_ampa=weights_ampa_p1,\nweights_nmda=None, location='proximal', n_drive_cells=n_drive_cells,\ncell_specific=cell_specific, synaptic_delays=synaptic_delays_prox, event_seed=544)\nnet_sync.add_evoked_drive(\n'evprox2', mu=137.12, sigma=8.33, numspikes=1,\nweights_ampa=weights_ampa_p2, location='proximal', n_drive_cells=n_drive_cells,\ncell_specific=cell_specific, synaptic_delays=synaptic_delays_prox, event_seed=814)\n\n
\n
\n

You may interrogate current values defining the spike event time dynamics by

\n
\n
\n\nprint(net_sync.external_drives['evdist1']['dynamics'])\n\n
\n
\nOut:\n
\n
\n{'mu': 63.53, 'sigma': 3.85, 'numspikes': 1}\n
\n
\n
\n

Finally, let's simulate this network. Rather than modifying the dipole\nobject, this time we make a copy of it before smoothing and scaling.

\n
\n
\n\ndpls_sync = simulate_dipole(net_sync, tstop=170., n_trials=1)\ntrial_idx = 0\ndpls_sync[trial_idx].copy().smooth(window_len).scale(scaling_factor).plot()\nnet_sync.cell_response.plot_spikes_hist();\n\n
\n
\nOut:\n
\n
\nJoblib will run 1 trial(s) in parallel by distributing trials over 1 jobs.\nBuilding the NEURON model\n[Done]\nTrial 1: 0.03 ms...\nTrial 1: 10.0 ms...\nTrial 1: 20.0 ms...\nTrial 1: 30.0 ms...\nTrial 1: 40.0 ms...\nTrial 1: 50.0 ms...\nTrial 1: 60.0 ms...\nTrial 1: 70.0 ms...\nTrial 1: 80.0 ms...\nTrial 1: 90.0 ms...\nTrial 1: 100.0 ms...\nTrial 1: 110.0 ms...\nTrial 1: 120.0 ms...\nTrial 1: 130.0 ms...\nTrial 1: 140.0 ms...\nTrial 1: 150.0 ms...\nTrial 1: 160.0 ms...\n<Figure size 640x480 with 1 Axes>\n
\n
\n
\n\n
\n
\nOut:\n
\n
\n<Figure size 640x480 with 1 Axes>\n
\n
\n
\n\n
\n
\n

<h4>Warning</h4>

\n\n
", + "sub-sections": [ + { + "title": "References", + "level": 2, + "html": "
\n

References

\n

.. [1] Jones, Stephanie R., et al. "Neural correlates of tactile detection:\na combined magnetoencephalography and biophysically based computational\nmodeling study." Journal of Neuroscience 27.40 (2007): 10751-10764.

\n
" + } + ] + } + } +} \ No newline at end of file diff --git a/scripts/convert_notebooks.py b/scripts/convert_notebooks.py index 497acf8..935918d 100644 --- a/scripts/convert_notebooks.py +++ b/scripts/convert_notebooks.py @@ -30,7 +30,7 @@ def save_plot_as_image(img_data, img_filename, output_dir): return -def html_to_hierarchical_json(html: str, filename: str): +def html_to_json(html: str, filename: str): """ Convert html into hierarchical json """ @@ -59,6 +59,7 @@ def html_to_hierarchical_json(html: str, filename: str): # get the title, level of the new section current_level = line_match.group(1).strip() + current_level = int(current_level.lstrip('')) current_title = line_match.group(2).strip() # start a new section with the previous line @@ -82,6 +83,90 @@ def html_to_hierarchical_json(html: str, filename: str): return contents +def structure_json(contents): + """ + Determine the hierarchy of sections based on levels without adding content. + Returns a list of sections in order of their hierarchy. + """ + hierarchy = {} + + for filename, sections in contents.items(): + hierarchy[filename] = {} + + # list to track parent sections for potential nesting + parent_stack = [] + + for section_title, section_data in sections.items(): + level = section_data['level'] + html_contents = section_data['html'] + + # Create a section dict with 'title', 'level', and 'sub-sections' + section_info = { + 'title': section_title, + 'level': level, + 'html': html_contents, + 'sub-sections': [] + } + + # Ensure only sections with a level greater than the current + # section remain in the stack as potential parents + while parent_stack and parent_stack[-1]['level'] >= level: + parent_stack.pop() + + if parent_stack: + # Add the section as a child of the last parent + parent_stack[-1]['sub-sections'].append(section_info) + else: + # Add the section as a top-level section + hierarchy[filename][section_title] = section_info + + # Add the current section to the parent stack for future nesting + parent_stack.append(section_info) + + def remove_blank_subsections(sections): + seek = 'sub-sections' + + for k, v in list(sections.items()): + print('key:', k) + print('value:', v) + print('-' * 30) + + if isinstance(v, dict): + # check for 'sub-sections' key in dict + if seek in v: + print('Subsections present:', (seek in v)) + print('Subsection value:', v[seek]) + + # delete empty sub-sections + if v[seek] == []: + print('Blank section found') + del v[seek] + + # Recursively check all sub-sections + else: + print('Checking sub-sections recursively...') + for sub_section in v[seek]: + remove_blank_subsections(sub_section) + + print('#' * 30) + + elif isinstance(v, list): + # if v is an empty list, delete it + if v == []: + print('Blank section found in list') + del sections[k] + # if v is a list of dicts, iterate through the dicts + else: + for dictionary in v: + remove_blank_subsections(dictionary) + + return sections + + hierarchy[filename] = remove_blank_subsections(hierarchy[filename]) + + return hierarchy + + def extract_html_from_notebook( notebook, input_dir, # changed @@ -258,17 +343,23 @@ def convert_notebooks_to_html( f.write(html_content) f.write("\n") - nb_html_json = html_to_hierarchical_json( + # flat json + nb_html_json = html_to_json( html_content, filename, ) + # nested json + nb_html_json = structure_json( + nb_html_json + ) + output_json = os.path.join( input_folder, f"{os.path.splitext(filename)[0]}.json" ) with open(output_json, "w") as f: - json.dump(nb_html_json, f) + json.dump(nb_html_json, f, indent=4) print(f"Successfully converted '{filename}'") @@ -277,6 +368,7 @@ def convert_notebooks_to_html( def test_nb_conversion(): input_folder = "../tests" + input_folder = "../content/05_erps" convert_notebooks_to_html( input_folder, @@ -286,5 +378,3 @@ def test_nb_conversion(): test_nb_conversion() - -# %% diff --git a/tests/test.html b/tests/test.html index 86d5a52..862644f 100644 --- a/tests/test.html +++ b/tests/test.html @@ -1,8 +1,6 @@
-

- Section A -

+

Section A

@@ -14,12 +12,14 @@

hello world +
-

- Subsection i -

+

Subsection i

+
+
+

Subsection ii

@@ -35,18 +35,12 @@

Out:

- [<matplotlib.lines.Line2D at 0x17bac1e50>] -
- -
- Out: -
-
+ [<matplotlib.lines.Line2D at 0x11d7ead10>] <Figure size 640x480 with 1 Axes>
- +
@@ -65,19 +59,23 @@

Test 2 +
-

- header -

+

header

- ### test - -markdown cell spanning multiple lines +

test

+

markdown cell spanning multiple lines

+
+
+

standalone markdown text

+
+
+

Top Level Header

- standalone markdown text +
\ No newline at end of file diff --git a/tests/test.ipynb b/tests/test.ipynb index ec4617a..5816e4f 100644 --- a/tests/test.ipynb +++ b/tests/test.ipynb @@ -38,6 +38,13 @@ "## Subsection i" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Subsection ii" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -119,6 +126,18 @@ "source": [ "standalone markdown text" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Top Level Header" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { diff --git a/tests/test.json b/tests/test.json index bdbff91..9a69c6d 100644 --- a/tests/test.json +++ b/tests/test.json @@ -1 +1,38 @@ -{"test.ipynb": {"Section A": {"contents": "

\n\t\tSection A\n\t

", "sections": {"Subsection i": {"contents": "

\n\t\tSubsection i\n\t

", "sections": {"header": {"contents": "

\n\t\theader\n\t

"}}}}}}} \ No newline at end of file +{ + "test.ipynb": { + "Section A": { + "title": "Section A", + "level": 1, + "html": "
\n

Section A

\n
\n
\n\nprint(\"hello world\")\n\n
\n
\nOut:\n
\n
\nhello world\n
\n
", + "sub-sections": [ + { + "title": "Subsection i", + "level": 2, + "html": "
\n

Subsection i

\n
" + }, + { + "title": "Subsection ii", + "level": 2, + "html": "
\n

Subsection ii

\n
\n
\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nx = np.linspace(0, 2 * np.pi, 100)\ny = np.sin(x)\nplt.plot(x, y)\n\n
\n
\nOut:\n
\n
\n[<matplotlib.lines.Line2D at 0x11d7ead10>]\n<Figure size 640x480 with 1 Axes>\n
\n
\n
\n\n
\n
\n\nx = '## Test'\nheader_level = x.count(\"#\")\nx = x.split(\"# \")[-1]\nprint(\nx,\nheader_level\n)\n\n
\n
\nOut:\n
\n
\nTest 2\n
\n
", + "sub-sections": [ + { + "title": "header", + "level": 4, + "html": "
\n

header

\n
" + }, + { + "title": "test", + "level": 3, + "html": "
\n

test

\n

markdown cell spanning multiple lines

\n
\n
\n

standalone markdown text

\n
" + } + ] + } + ] + }, + "Top Level Header": { + "title": "Top Level Header", + "level": 1, + "html": "
\n

Top Level Header

\n
\n
\n
" + } + } +} \ No newline at end of file