-
Notifications
You must be signed in to change notification settings - Fork 35
Add cell builder utility functions #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 35 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
fee5846
feat: add cell builder utilities
sanjayankur31 7880bfa
feat: propagate validation result to writer
sanjayankur31 a885a4d
feat: add cell builder utility
sanjayankur31 47b5cab
feat: add unit tests for cell builder utility
sanjayankur31 819e76d
feat: add pytest to CI
sanjayankur31 4e9d4db
fix: correct type hinting for list of floats
sanjayankur31 a5df48b
fix: use Python 2 compatible type hinting syntax
sanjayankur31 c4cca51
fix: drop f-strings for Python 2 compatibility
sanjayankur31 4b2feb8
refactor(ci): move pytest to test scripts
sanjayankur31 3fb3111
feat: add fraction_along to add_segment
sanjayankur31 107352e
feat: set default fraction_along in segment parent
sanjayankur31 731ddcd
feat: add summary doc string to add_segment
sanjayankur31 95994db
chore: remove stray doc string line
sanjayankur31 ec46850
feat: allow omission of proximal point in add_segment
sanjayankur31 3daaa24
fix: make sure p is defined even when prox is None
sanjayankur31 a2cca2f
feat: do not repeat includes
sanjayankur31 29de814
feat(cellbuilder): define a dict to store neurolex ids
sanjayankur31 bf5a553
feat(cellbuilder): add default segment groups
sanjayankur31 098df66
feat(cellbuilder): add utilities to easily find segment groups
sanjayankur31 04ce495
feat(cellbuilder): add newly created segments to default segments
sanjayankur31 91345a4
fix: correct type annotations
sanjayankur31 8fadf0d
chore(cellbuilder): add note about order of segment groups in NML files
sanjayankur31 9735a51
refactor(cellbuilder): migrate segment getters to nml.py in libNeuroML
sanjayankur31 ff1b0de
refactor(cellbuilder): use update nml.py methods and make conventiona…
sanjayankur31 b6123c4
feat(cell-builder): use py3 type annotations
sanjayankur31 49d687e
chore: format with black
sanjayankur31 ffadf98
chore(ci): remove duplicate pytest command
sanjayankur31 2f9eac0
feat(deps): set version constraint: libNeuroML>= 0.3.1
sanjayankur31 429d0a8
docs: add CellBuilder to sphinx docs
sanjayankur31 313c262
test(CellBuilder): re-enable skipped tests
sanjayankur31 8b604f5
chore: correct section heading in docs
sanjayankur31 8066c59
feat(cell-builder): add missing check
sanjayankur31 f6ba895
feat: handle exception when a segment group does not exist
sanjayankur31 911ed4b
test(cell-builder): add new tests
sanjayankur31 c3b0b89
chore: use is for comparison with None instead of ==
sanjayankur31 0ab7859
fix(cell-builder): append to resistivities instead of overwriting
sanjayankur31 311c561
fix(cell-builder): correct append function call
sanjayankur31 18fedd3
fix(cell-builder): append to list of InitMembPotentials
sanjayankur31 1ff8211
improvement(cell-builder): re-order function args
sanjayankur31 32d2e05
feat(cell-builder): add generic channel density helper
sanjayankur31 f42acf3
test(cell-builder): add test for generic conductance density adder
sanjayankur31 3e9b2e6
feat(cell-builder): add generic method to add membrane properties
sanjayankur31 fdd9e99
feat(utils): add general component factory function
sanjayankur31 f418f5a
improvement(utils): make component factory case sensitive
sanjayankur31 8cbc383
chore(utils): remove wrong doc noting names are case insensitive
sanjayankur31 f42dda8
test(cell-builder): correct tests
sanjayankur31 4b84a33
chore: add missing blank line
sanjayankur31 0eb1220
feat(cell-builder): add generic function to add intracellular properties
sanjayankur31 381e9a5
test(cell-builder): test intracellular and membrane property helpers
sanjayankur31 6e7ae42
test(utils): add tests for factory helper function
sanjayankur31 627a7ed
feat(utils): add function to check for compulsory parameters
sanjayankur31 a74a26c
Merge branch 'development' into feat/cell-builder-methods-105
sanjayankur31 be491cc
fix(component-factory): return created component
sanjayankur31 813217e
test(plot): disable interactive
sanjayankur31 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ Subpackages | |
| pyneuroml.swc | ||
| pyneuroml.tune | ||
| pyneuroml.neuron | ||
| pyneuroml.utils | ||
|
|
||
|
|
||
| pyneuroml.pynml module | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| pyneuroml.utils package | ||
| ========================== | ||
|
|
||
| .. automodule:: pyneuroml.utils | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: | ||
|
|
||
| pyneuroml.utils.CellBuilder module | ||
| --------------------------------------------- | ||
|
|
||
| .. automodule:: pyneuroml.utils.CellBuilder | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,307 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Utility functions to help build cells in NeuroML | ||
| """ | ||
|
|
||
|
|
||
| from typing import List | ||
| from neuroml import ( | ||
| Cell, | ||
| Morphology, | ||
| MembraneProperties, # type: ignore # noqa | ||
| IntracellularProperties, | ||
| BiophysicalProperties, | ||
| Segment, | ||
| SegmentGroup, | ||
| Point3DWithDiam, | ||
| SegmentParent, | ||
| Member, | ||
| InitMembPotential, | ||
| Resistivity, | ||
| SpecificCapacitance, | ||
| NeuroMLDocument, | ||
| IncludeType, | ||
| ChannelDensity, | ||
| ) | ||
|
|
||
|
|
||
| neuro_lex_ids = { | ||
| "axon": "GO:0030424", | ||
| "dend": "GO:0030425", | ||
| "soma": "GO:0043025", | ||
| } | ||
|
|
||
|
|
||
| def create_cell(cell_id: str, use_convention: bool = True) -> Cell: | ||
| """Create a NeuroML Cell. | ||
|
|
||
| Initialises the cell with these properties assigning IDs where applicable: | ||
| - Morphology: "morphology" | ||
| - BiophysicalProperties: "biophys" | ||
| - MembraneProperties | ||
| - IntracellularProperties | ||
|
|
||
| if `use_convention` is True, it also creates some default SegmentGroups for | ||
| convenience: | ||
|
|
||
| - "all", "soma_group", "dendrite_group", "axon_group" which | ||
| are used by other helper functions to include all, soma, dendrite, and | ||
| axon segments respectively. | ||
|
|
||
| Note that since this cell does not currently include a segment in its | ||
| morphology, it is *not* a valid NeuroML construct. Use the `add_segment` | ||
| function to add segments. `add_segment` will also populate the default | ||
| segment groups this creates. | ||
|
|
||
| :param cell_id: id of the cell | ||
| :type cell_id: str | ||
| :param use_convention: whether helper segment groups should be created using the default convention | ||
| :type use_convention: bool | ||
| :returns: created cell object of type neuroml.Cell | ||
|
|
||
| """ | ||
| cell = Cell(id=cell_id) | ||
| cell.morphology = Morphology(id="morphology") | ||
| membrane_properties = MembraneProperties() | ||
| intracellular_properties = IntracellularProperties() | ||
|
|
||
| cell.biophysical_properties = BiophysicalProperties( | ||
| id="biophys", | ||
| intracellular_properties=intracellular_properties, | ||
| membrane_properties=membrane_properties, | ||
| ) | ||
|
|
||
| if use_convention: | ||
| seg_group_all = SegmentGroup(id="all") | ||
| seg_group_soma = SegmentGroup( | ||
| id="soma_group", | ||
| neuro_lex_id=neuro_lex_ids["soma"], | ||
| notes="Default soma segment group for the cell", | ||
| ) | ||
| seg_group_axon = SegmentGroup( | ||
| id="axon_group", | ||
| neuro_lex_id=neuro_lex_ids["axon"], | ||
| notes="Default axon segment group for the cell", | ||
| ) | ||
| seg_group_dend = SegmentGroup( | ||
| id="dendrite_group", | ||
| neuro_lex_id=neuro_lex_ids["dend"], | ||
| notes="Default dendrite segment group for the cell", | ||
| ) | ||
| cell.morphology.segment_groups.append(seg_group_all) | ||
| cell.morphology.segment_groups.append(seg_group_soma) | ||
| cell.morphology.segment_groups.append(seg_group_axon) | ||
| cell.morphology.segment_groups.append(seg_group_dend) | ||
|
|
||
| return cell | ||
|
|
||
|
|
||
| def add_segment( | ||
| cell: Cell, | ||
| prox: List[float], | ||
| dist: List[float], | ||
| name: str = None, | ||
| parent: SegmentParent = None, | ||
| fraction_along: float = 1.0, | ||
| group: SegmentGroup = None, | ||
| use_convention: bool = True, | ||
| ) -> Segment: | ||
| """Add a segment to the cell. | ||
|
|
||
| Suggested convention: use `axon_`, `soma_`, `dend_` prefixes for axon, | ||
| soma, and dendrite segments respectivey. This will allow this function to | ||
| add the correct neurolex IDs to the group. | ||
|
|
||
| If `use_convention` is true, the created segment is also added to the | ||
| default segment groups that were created by the `create_cell` function: | ||
| `all`, `dendrite_group`, `soma_group`, `axon_group`. Note that while it is | ||
| not necessary to use the convention, it is necessary to be consistent. | ||
|
|
||
| :param cell: cell to add segment to | ||
| :type cell: Cell | ||
| :param prox: proximal segment information | ||
| :type prox: list with 4 float entries: [x, y, z, diameter] | ||
| :param dist: dist segment information | ||
| :type dist: list with 4 float entries: [x, y, z, diameter] | ||
| :param name: name of segment | ||
| :type name: str | ||
| :param parent: parent segment | ||
| :type parent: SegmentParent | ||
| :param fraction_along: where the new segment is connected to the parent (0: distal point, 1: proximal point) | ||
| :type fraction_along: float | ||
| :param group: segment group to add the segment to | ||
| :type group: SegmentGroup | ||
| :param use_convention: whether helper segment groups should be created using the default convention | ||
| :type use_convention: bool | ||
| :returns: the created segment | ||
|
|
||
| """ | ||
| try: | ||
| if prox: | ||
| p = Point3DWithDiam(x=prox[0], y=prox[1], z=prox[2], diameter=prox[3]) | ||
| else: | ||
| p = None | ||
| except IndexError as e: | ||
| print("{}: prox must be a list of 4 elements".format(e)) | ||
| try: | ||
| d = Point3DWithDiam(x=dist[0], y=dist[1], z=dist[2], diameter=dist[3]) | ||
| except IndexError as e: | ||
| print("{}: dist must be a list of 4 elements".format(e)) | ||
|
|
||
| segid = len(cell.morphology.segments) | ||
|
|
||
| if segid > 0 and parent is None: | ||
| raise Exception( | ||
| "There are currently more than one segments in the cell, but one is being added without specifying a parent segment" | ||
| ) | ||
|
|
||
| sp = ( | ||
| SegmentParent(segments=parent.id, fraction_along=fraction_along) | ||
| if parent | ||
| else None | ||
| ) | ||
| segment = Segment(id=segid, proximal=p, distal=d, parent=sp) | ||
|
|
||
| if name: | ||
| segment.name = name | ||
|
|
||
| if group: | ||
| seg_group = None | ||
| seg_group_default = None | ||
| neuro_lex_id = None | ||
|
|
||
| # cell.get_segment_group throws an exception of the segment group | ||
| # does not exist | ||
| try: | ||
| seg_group = cell.get_segment_group(group) | ||
| except Exception as e: | ||
| print("Warning: {}".format(e)) | ||
|
|
||
| if "axon_" in group: | ||
| neuro_lex_id = neuro_lex_ids[ | ||
| "axon" | ||
| ] # See http://amigo.geneontology.org/amigo/term/GO:0030424 | ||
| if use_convention: | ||
| seg_group_default = cell.get_segment_group("axon_group") | ||
| if "soma_" in group: | ||
| neuro_lex_id = neuro_lex_ids["soma"] | ||
| if use_convention: | ||
| seg_group_default = cell.get_segment_group("soma_group") | ||
| if "dend_" in group: | ||
| neuro_lex_id = neuro_lex_ids["dend"] | ||
| if use_convention: | ||
| seg_group_default = cell.get_segment_group("dendrite_group") | ||
|
|
||
| if seg_group is None: | ||
| seg_group = SegmentGroup(id=group, neuro_lex_id=neuro_lex_id) | ||
| cell.morphology.segment_groups.append(seg_group) | ||
|
|
||
| seg_group.members.append(Member(segments=segment.id)) | ||
| # Ideally, these higher level segment groups should just include other | ||
| # segment groups using Include, which would result in smaller NML | ||
| # files. However, because these default segment groups are defined | ||
| # first, they are printed in the NML file before the new segments and their | ||
| # groups. The jnml validator does not like this. | ||
| # TODO: clarify if the order of definition is important, or if the jnml | ||
| # validator needs to be updated to manage this use case. | ||
| if use_convention and seg_group_default: | ||
| seg_group_default.members.append(Member(segments=segment.id)) | ||
|
|
||
| if use_convention: | ||
| seg_group_all = cell.get_segment_group("all") | ||
| seg_group_all.members.append(Member(segments=segment.id)) | ||
|
|
||
| cell.morphology.segments.append(segment) | ||
| return segment | ||
|
|
||
|
|
||
| def set_init_memb_potential(cell: Cell, v: str, group: str = "all") -> None: | ||
| """Set the initial membrane potential of the cell. | ||
|
|
||
| :param cell: cell to modify | ||
| :type cell: Cell | ||
| :param v: value to set for membrane potential with units | ||
| :type v: str | ||
| :param group: id of segment group to modify | ||
| :type group: str | ||
| """ | ||
| cell.biophysical_properties.membrane_properties.init_memb_potentials = [ | ||
| InitMembPotential(value=v, segment_groups=group) | ||
| ] | ||
|
|
||
|
|
||
| def set_resistivity(cell: Cell, resistivity: str, group: str = "all") -> None: | ||
| """Set the resistivity of the cell | ||
|
|
||
| :param cell: cell to modfify | ||
| :param resistivity: value resistivity to set with units | ||
| :type resistivity: str | ||
| :param group: segment group to modify | ||
| :type group: str | ||
| """ | ||
| cell.biophysical_properties.intracellular_properties.resistivities = [ | ||
| Resistivity(value=resistivity, segment_groups=group) | ||
| ] | ||
|
|
||
|
|
||
| def set_specific_capacitance(cell: Cell, spec_cap: str, group: str = "all") -> None: | ||
| """Set the specific capacitance for the cell. | ||
|
|
||
| :param cell: cell to set specific capacitance for | ||
| :type cell: Cell | ||
| :param spec_cap: value of specific capacitance with units | ||
| :type spec_cap: str | ||
| :param group: segment group to modify | ||
| :type group: str | ||
| """ | ||
| cell.biophysical_properties.membrane_properties.specific_capacitances.append( | ||
| SpecificCapacitance(value=spec_cap, segment_groups=group) | ||
| ) | ||
|
|
||
|
|
||
| def add_channel_density( | ||
sanjayankur31 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cell: Cell, | ||
| nml_cell_doc: NeuroMLDocument, | ||
| cd_id: str, | ||
| cond_density: str, | ||
| ion_channel: str, | ||
| ion_chan_def_file: str = "", | ||
| erev: str = "0.0 mV", | ||
| ion: str = "non_specific", | ||
| group: str = "all", | ||
| ) -> None: | ||
| """Add channel density. | ||
|
|
||
| :param cell: cell to be modified | ||
| :type cell: Cell | ||
| :param nml_cell_doc: cell NeuroML document to which channel density is to be added | ||
| :type nml_cell_doc: NeuroMLDocument | ||
| :param cd_id: id for channel density | ||
| :type cd_id: str | ||
| :param cond_density: value of conductance density with units | ||
| :type cond_density: str | ||
| :param ion_channel: name of ion channel | ||
| :type ion_channel: str | ||
| :param ion_chan_def_file: path to NeuroML2 file defining the ion channel, if empty, it assumes the channel is defined in the same file | ||
| :type ion_chan_def_file: str | ||
| :param erev: value of reversal potential with units | ||
| :type erev: str | ||
| :param ion: name of ion | ||
| :type ion: str | ||
| :param group: segment groups to add to | ||
| :type group: str | ||
| """ | ||
| cd = ChannelDensity( | ||
| id=cd_id, | ||
| segment_groups=group, | ||
| ion=ion, | ||
| ion_channel=ion_channel, | ||
| erev=erev, | ||
| cond_density=cond_density, | ||
| ) | ||
|
|
||
| cell.biophysical_properties.membrane_properties.channel_densities.append(cd) | ||
|
|
||
| if len(ion_chan_def_file) > 0: | ||
| if IncludeType(ion_chan_def_file) not in nml_cell_doc.includes: | ||
| nml_cell_doc.includes.append(IncludeType(ion_chan_def_file)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| The utils package contains various utility functions to aid users working with | ||
| PyNeuroML | ||
|
|
||
| Copyright 2021 NeuroML Contributors | ||
| Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com> | ||
| """ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.