diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 7f6a0804493a7044267df8d5568166a8d1e8f1b3..0239d180112bfb369ca0b69409589a309b2ddce2 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -165,6 +165,6 @@ jobs: python python/example/single_cell_model.py python python/example/single_cell_recipe.py python python/example/single_cell_stdp.py - python python/example/single_cell_swc.py test/unit/swc/pyramidal.swc + python python/example/single_cell_swc.py python/example/morph.swc python python/example/single_cell_detailed.py python/example/morph.swc python python/example/single_cell_detailed_recipe.py python/example/morph.swc diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst index 40f7705e603858ed99310e97a4f95d9a7dc61339..fd18f61891d92d2eb261e498f9b7df1418258f9a 100644 --- a/doc/python/morphology.rst +++ b/doc/python/morphology.rst @@ -562,7 +562,6 @@ constitute part of the CV boundary point set. A :class:`neuroml_morphology_data` object contains a representation of a morphology defined in NeuroML. - .. py:attribute:: cell_id :type: optional<str> @@ -578,6 +577,11 @@ constitute part of the CV boundary point set. A map from each segment group id to its corresponding collection of segments. + .. py:attribute:: morphology + :type: morphology + + The morphology associated with the :class:`neuroml_morph_data` object. + .. py:method:: segments Returns a label dictionary with a region entry for each segment, keyed by the segment id (as a string). diff --git a/python/cells.cpp b/python/cells.cpp index 8331ed9a78c7f8b1e49080d6acd845ca25906401..493409acbe957d65e998446124210124dd4c60fa 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -225,6 +225,12 @@ void register_cells(pybind11::module& m) { [](const label_dict_proxy &ld) { return pybind11::make_key_iterator(ld.cache.begin(), ld.cache.end());}, pybind11::keep_alive<0, 1>()) + .def("append", [](label_dict_proxy& l, const label_dict_proxy& other, const char* prefix) { + l.import(other, prefix); + }, + "other"_a, "The label_dict to be imported" + "prefix"_a="", "optional prefix appended to the region and locset labels", + "Import the entries of a another label dictionary with an optional prefix.") .def_readonly("regions", &label_dict_proxy::regions, "The region definitions.") .def_readonly("locsets", &label_dict_proxy::locsets, diff --git a/python/example/morph.nml b/python/example/morph.nml new file mode 100644 index 0000000000000000000000000000000000000000..ea58feb735e52ea5601f224896a7e0f991528c78 --- /dev/null +++ b/python/example/morph.nml @@ -0,0 +1,72 @@ +<neuroml xmlns="http://www.neuroml.org/schema/neuroml2"> +<morphology id="m1"> + <segment id="0"> + <proximal x="0" y="0" z="0" diameter="4"/> + <distal x="40" y="0" z="0" diameter="4"/> + </segment> + <segment id="1"> + <parent segment="0"/> + <proximal x="40" y="0" z="0" diameter="1.6"/> + <distal x="80" y="0" z="0" diameter="1.6"/> + </segment> + <segment id="2"> + <parent segment="1"/> + <proximal x="80" y="0" z="0" diameter="1.6"/> + <distal x="120" y="-5" z="0" diameter="1.6"/> + </segment> + <segment id="3"> + <parent segment="2"/> + <proximal x="120" y="-5" z="0" diameter="1.6"/> + <distal x="200" y="40" z="0" diameter="0.8"/> + </segment> + <segment id="4"> + <parent segment="3"/> + <proximal x="200" y="40" z="0" diameter="0.8"/> + <distal x="260" y="60" z="0" diameter="0.4"/> + </segment> + <segment id="5"> + <parent segment="2"/> + <proximal x="120" y="-5" z="0" diameter="1"/> + <distal x="190" y="-30" z="0" diameter="1"/> + </segment> + <segment id="6"> + <parent segment="5"/> + <proximal x="190" y="-30" z="0" diameter="1"/> + <distal x="240" y="-70" z="0" diameter="0.4"/> + </segment> + <segment id="7"> + <parent segment="5"/> + <proximal x="190" y="-30" z="0" diameter="1"/> + <distal x="230" y="-10" z="0" diameter="0.4"/> + </segment> + <segment id="8"> + <parent segment="7"/> + <proximal x="230" y="-10" z="0" diameter="0.4"/> + <distal x="360" y="-20" z="0" diameter="0.4"/> + </segment> + <segment id="9"> + <parent segment="0" fractionAlong="0.0"/> + <proximal x="0" y="0" z="0" diameter="4"/> + <distal x="-70" y="0" z="0" diameter="0.8"/> + </segment> + <segment id="10"> + <parent segment="9"/> + <proximal x="-70" y="0" z="0" diameter="0.8"/> + <distal x="-100" y="0" z="0" diameter="0.8"/> + </segment> + <segmentGroup id="soma"> + <member segment="0"/> + </segmentGroup> + <segmentGroup id="axon"> + <member segment="9"/> + <member segment="10"/> + </segmentGroup> + <segmentGroup id="dend"> + <member segment="1"/> + <member segment="2"/> + <member segment="3"/> + <member segment="4"/> + <member segment="5"/> + </segmentGroup> +</morphology> +</neuroml> diff --git a/python/example/single_cell_nml.py b/python/example/single_cell_nml.py new file mode 100755 index 0000000000000000000000000000000000000000..cb5e668ff9177fd4a85c6b0c42c151a0cef9bfc1 --- /dev/null +++ b/python/example/single_cell_nml.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +import arbor +from arbor import mechanism as mech +from arbor import location as loc +import pandas, seaborn +import sys + +# Load a cell morphology from an nml file. +# Example present here: morph.nml +if len(sys.argv) < 2: + print("No NeuroML file passed to the program") + sys.exit(0) + +filename = sys.argv[1] + +# Read the NeuroML morphology from the file. +morpho_nml = arbor.neuroml(filename) + +# Read the morphology data associated with morphology "m1". +morpho_data = morpho_nml.morphology("m1") + +# Get the morphology. +morpho = morpho_data.morphology + +# Get the region label dictionaries associated with the morphology. +morpho_segments = morpho_data.segments() +morpho_named = morpho_data.named_segments() +morpho_groups = morpho_data.groups() + +# Create new label dict add to it all the NeuroML dictionaries. +labels = arbor.label_dict() +labels.append(morpho_segments) +labels.append(morpho_named) +labels.append(morpho_groups) + +# Add locsets to the label dictionary. +labels['stim_site'] = '(location 1 0.5)' # site for the stimulus, in the middle of branch 1. +labels['axon_end'] = '(restrict (terminal) (region "axon"))' # end of the axon. +labels['root'] = '(root)' # the start of the soma in this morphology is at the root of the cell. + +# Optional: print out the regions and locsets available in the label dictionary. +print("Label dictionary regions: ", labels.regions, "\n") +print("Label dictionary locsets: ", labels.locsets, "\n") + +decor = arbor.decor() + +# Set initial membrane potential to -55 mV +decor.set_property(Vm=-55) +# Use Nernst to calculate reversal potential for calcium. +decor.set_ion('ca', method=mech('nernst/x=ca')) +#decor.set_ion('ca', method='nernst/x=ca') +# hh mechanism on the soma and axon. +decor.paint('"soma"', 'hh') +decor.paint('"axon"', 'hh') +# pas mechanism the dendrites. +decor.paint('"dend"', 'pas') +# Increase resistivity on dendrites. +decor.paint('"dend"', rL=500) +# Attach stimuli that inject 4 nA current for 1 ms, starting at 3 and 8 ms. +decor.place('"root"', arbor.iclamp(10, 1, current=5)) +decor.place('"stim_site"', arbor.iclamp(3, 1, current=0.5)) +decor.place('"stim_site"', arbor.iclamp(10, 1, current=0.5)) +decor.place('"stim_site"', arbor.iclamp(8, 1, current=4)) +# Detect spikes at the soma with a voltage threshold of -10 mV. +decor.place('"axon_end"', arbor.spike_detector(-10)) + +# Create the policy used to discretise the cell into CVs. +# Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. +soma_policy = arbor.cv_policy_single('"soma"') +dflt_policy = arbor.cv_policy_max_extent(1.0) +policy = dflt_policy | soma_policy +decor.discretization(policy) + +# Combine morphology with region and locset definitions to make a cable cell. +cell = arbor.cable_cell(morpho, labels, decor) + +print(cell.locations('"axon_end"')) + +# Make single cell model. +m = arbor.single_cell_model(cell) + +# Attach voltage probes that sample at 50 kHz. +m.probe('voltage', where='"root"', frequency=50000) +m.probe('voltage', where='"stim_site"', frequency=50000) +m.probe('voltage', where='"axon_end"', frequency=50000) + +# Simulate the cell for 15 ms. +tfinal=15 +m.run(tfinal) +print("Simulation done.") + +# Print spike times. +if len(m.spikes)>0: + print('{} spikes:'.format(len(m.spikes))) + for s in m.spikes: + print(' {:7.4f}'.format(s)) +else: + print('no spikes') + +# Plot the recorded voltages over time. +print("Plotting results ...") +df_list = [] +for t in m.traces: + df_list.append(pandas.DataFrame({'t/ms': t.time, 'U/mV': t.value, 'Location': str(t.location), "Variable": t.variable})) + +df = pandas.concat(df_list) + +seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV",hue="Location",col="Variable",ci=None).savefig('single_cell_nml.svg') diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index 5584b9f5d41176679339ef2a9763cb05113b5cda..63113384db0b86af3ed419f3607d790aef09b5a8 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -17,7 +17,7 @@ import pandas, seaborn import sys # Load a cell morphology from an swc file. -# Example present here: ../../test/unit/swc/pyramidal.swc +# Example present here: morph.swc if len(sys.argv) < 2: print("No SWC file passed to the program") sys.exit(0) diff --git a/python/proxy.hpp b/python/proxy.hpp index d47ab761b745d7947bd80ccd65d1f8d00ff5582d..1d4d059989cfe2e20196367fa7ea41472f8c9966 100644 --- a/python/proxy.hpp +++ b/python/proxy.hpp @@ -23,12 +23,31 @@ struct label_dict_proxy { } } - label_dict_proxy(const arb::label_dict& label_dict): dict(label_dict) {} + label_dict_proxy(const arb::label_dict& label_dict): dict(label_dict) { + for (const auto& [l, reg]: dict.regions()) { + regions.push_back(l); + } + for (const auto& [l, ls]: dict.locsets()) { + locsets.push_back(l); + } + } std::size_t size() const { return locsets.size() + regions.size(); } + void import(const label_dict_proxy& other, std::string prefix) { + regions.clear(); + locsets.clear(); + dict.import(other.dict, prefix); + for (const auto& [l, reg]: dict.regions()) { + regions.push_back(l); + } + for (const auto& [l, ls]: dict.locsets()) { + locsets.push_back(l); + } + } + void set(const char* name, const char* desc) { using namespace std::string_literals; // The following code takes an input name and a region or locset