Skip to content
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

[WIP] nmlimport package: importing morphologies from nml file #17

Merged
merged 43 commits into from
Jul 13, 2018

Conversation

kapilkd13
Copy link
Contributor

This PR features current progress in implementing nmlimport package that allows importing morphologies from nml file.

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I am writing test for load_morphology function. Here I am comparing the final morphology object attributes to pre determined values to assert our function is correct. I am checking attributes like length,distance, coordinates and area. Although other three are providing correct result, area is failing assertion. see line https://github.com/brian-team/brian2tools/pull/17/files#diff-1a36c24e3b7044d2e6d50df875dc2968R63
output that we get:

=================================== FAILURES ===================================
_____________________________ test_load_morphology _____________________________

    def test_load_morphology():
        morphology = load_morphology("samples/sample1.cell.nml")
        assert_equal(morphology.distance,[ 8.5]*um)
        assert_equal(morphology.length,[ 17.]*um)
        assert_equal(morphology.coordinates,[[0.,0.,0.],[0.,17.,0.]]*um)
    
>       assert_equal(morphology.area,[1228.36272755]*um2)
E       AssertionError: 
E       Arrays are not equal
E       
E       (mismatch 100.0%)
E        x: Quantity([  1.228363e-09])
E        y: Quantity([  1.228363e-09])

Can you think on any reason? Can float-point precision be behind it.

@mstimberg
Copy link
Member

mstimberg commented May 28, 2018

Can you think on any reason? Can float-point precision be behind it.

Yes, it's very likely almost the same value. For these kind of comparisons, rather use assert_allclose instead of assert_equal.

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I have added tests and docstring. Please have a look. I haven't updated sphinx doc to auto import docstring as due to some reason sphinx is not working on my pc for this particular project.
make html gives me

sphinx-build: error: unrecognized arguments: /home/kapil/github/brian2tools/brian2tools /home/kapil/github/brian2tools/brian2tools/tests

I suspect it has to do something with the fact that I have pip develop this module instead of building it.

Also, passing file object works with libNeuroML (it is using lxml), although in docs it mention for file location only. I have updated load_morphology accordingly.

@mstimberg
Copy link
Member

due to some reason sphinx is not working on my pc for this particular project.

Mmm, that's odd, it should work with pip install -e . (that's what I'm using) or python setup.py develop. What happens if you directly run sphinx-build -b html -d ../docs/doctrees . ../docs/html in docs_sphinx? That should be what the makefile does.

I haven't updated sphinx doc to auto import docstring

You don't need to do anything to import the docstrings. The conf.py file automatically runs sphinx-apidoc to create the reference documentation for all files in the package.

Could you convert your docstrings to the numpydoc format? I find it much more readable in the source code and it is what we are using in the rest of brian2 and brian2tools.

@kapilkd13
Copy link
Contributor Author

kapilkd13 commented Jun 1, 2018

Hi @mstimberg I have updated the docstring. I tried the sphinx build command but it also throws same error.
Travis build is failing right now because we need libneuroml for testing but its latest package is not available for conda. Since tests are a part of conda recipe, pip installing libneuroml in travis file after the conda environment is built doesn't work either. We can create and upload libneuroml package for conda and use that. Or we can move tests out of conda recipe and into travis file, would that work? Is there some other way?

@kapilkd13
Copy link
Contributor Author

@mstimberg I have made conda recipe for libNeuroML, can you have a look and see if it is good to go.
https://github.com/kapilkd13/staged-recipes/blob/libneuroml/recipes/libneuroml/meta.yaml

@mstimberg
Copy link
Member

Hi @kapilkd13 , this is looking very good! Just some minor things to make it perfect:

  • Since libneuroml can be used with Python 2 and 3 without changes and is "pure Python", it can be a noarch package (but we should check back with Padraig, setup.py only mentions Python 2 in its classifiers)
  • The installation should use pip which therefore needs to be a build dependency
  • In addition to the dev_url, you could also add http://libneuroml.readthedocs.io as the doc_url

@kapilkd13
Copy link
Contributor Author

@mstimberg I forgot to mention I have developed the test script we were talking about. Its in the last commit here

@mstimberg
Copy link
Member

No worries, I saw the commit :)

@kapilkd13
Copy link
Contributor Author

@mstimberg I added function to generate proximal distance from parent and its working fine. But their is another issue, while validating we are checking if the proximal distance of a segment is matching to the distal of its parent but in some files their is fraction along is used and in these cases proximal point do not overlap with distal. This throws validation error. In this file https://github.com/NeuroML/NeuroML2/blob/master/examples/NML2_SimpleMorphology.nml
we are getting

brian2tools.nmlimport.nml.ValidationException: <Segment|3|Spine> and its parent segment <Segment|2|MainDendrite2> are not connected!!

@mstimberg
Copy link
Member

Hi, this is fine, we don't support these morphologies yet, anyway. I think the best thing would be to raise a NotImplementedError if a segment defines fractionAlong with a value different from 1.

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I added runtime dependencies to conda recipe. Should we push it now https://github.com/kapilkd13/staged-recipes/blob/libneuroml/recipes/libneuroml/meta.yaml

@mstimberg
Copy link
Member

Hi @mstimberg I added runtime dependencies to conda recipe. Should we push it now https://github.com/kapilkd13/staged-recipes/blob/libneuroml/recipes/libneuroml/meta.yaml

It is looking good, but some of the tests fail for me when building/installing/testing the package locally. It seems to be an issue with the jsonpickle library, if I install the 0.7.2. version from pip (unfortunately it's not packaged for conda-forge), the test runs fine. Could you maybe have a quick look at the bug, maybe it's something that is easily fixable in libneuroml (possibly via a try...except that tries something that works for the latest versions and falls back to something that works for earlier versions of jsonpickle)?

Oh, and I think you can remove vellamike from the maintainers, according to Padraig he left academia so I guess he's not particularly interested in maintaining the package.

@@ -31,7 +31,7 @@ def validate_morphology(segments):
for segment in segments:

if segment.parent is not None:
if segment.parent.fraction_along != 1:
if segment.parent.fraction_along not in [0,1]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we correctly support fraction_along=0, it would mean to connect to the proximal end of a segment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I was checking if it is connected proximally here I guess we should remove it then :)

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg how are you testing conda recipe locally, currently I am using conda-build . to build recipe after adding

  commands:
    -  py.test --pyargs neuroml
  requires:
    - pytest

to meta.yml file. On running I am getting errors like

  def read_neuroml2_file(nml2_file_name, include_includes=False, verbose=False,
                           already_included=[], print_method=print_, optimized=False):
    
        print_method("Loading NeuroML2 file: %s" % nml2_file_name, verbose)
    
        if not os.path.isfile(nml2_file_name):
            print_method("Unable to find file: %s!" % nml2_file_name, True)
>           sys.exit()
E           SystemExit

../_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehol/lib/python3.6/site-packages/neuroml/loaders.py:235: SystemExit

Is this the right approach? Also how did you pip installed specific version of jsonpickle with conda recipe. is pip allowed in meta file?

@mstimberg
Copy link
Member

Sorry, I should have been clearer about this. I don't do any change to the meta.yaml file (I don't think that you can run all the tests during the package build, since the necessary test files are not part of the package), instead I build it as it is. I have created a new conda environment for testing the package, and then I install it with:

conda install -c conda-forge --use-local libneuroml

Because of the --use-local, it will use the package that I've previously built. I then run the tests in neuroml/test directory of the source repository (with nosetests, but pytest it should work as well). As you said, you cannot install from pip in meta.yaml -- I simply removed the conda package for jsonpickle in my test environment and installed the older version with pip install jsonpickle==0.7.2.

@pgleeson
Copy link

@kapilkd13 I agree that the case fraction_along != 0 or 1 could be ignored, as it would complicate the conversion of the model to Brian & not produce correct electrical connectivity, but the case fraction_along = 0 should be fine: it explicitly states that the new segment is connected to the proximal point of the parent, and that should be fine to represent in Brian format.

@mstimberg
Copy link
Member

@pgleeson I agree that Brian does support it in principle. The reason why I told @kapilkd13 to ignore it for now is that the current approach for converting the NeuroML morphology to Brian is to first convert it into a point-based format like the one used by SWC, so that we can then use Brian's existing import facility for this representation. With that SWC-like format, we only have a simple "parent" link, so we cannot express the difference between a proximal and distal connection.

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I tried few things to resolve jsonpickle issue but wasn't very successful. libneuroml is using it for a very trivial task of loading json and it shouldn't throw this error. I do found an active issue on jsonpickle repo jsonpickle/jsonpickle#192 related to this. I guess its some issue with jsonpickle itself..

@mstimberg
Copy link
Member

Hi @kapilkd13 , I looked into this in quite a bit of detail and added my comments to the github issue you linked. The specific issue seems to be a problem in jsonpickle itself, but there's also an option that we can enable in newer jsonpickle versions that does the trick: NeuralEnsemble/libNeuroML/pull/72

Note that during testing, I had to clear the database, otherwise it would not overwrite the incorrect JSON with the fixed version:

>>> import pymongo
>>> client = pymongo.MongoClient('localhost', 27017)
>>> client.drop_database('mike_test_db3')
>>> client.drop_database('test_db_4')

@kapilkd13
Copy link
Contributor Author

@mstimberg I saw you comment on that thread, you dived in the code base and got to the issue. That was awesome :) I will try your suggestion. I also want to ask if I should push segment group related commits on this branch only or should we get this merge and start our work on new branch.

@mstimberg
Copy link
Member

I also want to ask if I should push segment group related commits on this branch only or should we get this merge and start our work on new branch.

I'd say: until we have a libneuroml package on conda-forge (and therefore can run the automatic test suite in its current form) you can add your changes here. As soon as this is the case, we can finish this branch and merge it, and then start to work on new features in separate pull requests.

@mstimberg
Copy link
Member

Can you have another go at the conda-forge recipe with the updated version of libneuroml that Padraig just uploaded to pypi (https://pypi.org/project/libNeuroML/)? It should now hopefully work with a recent version of jsonpickle. If it does, I think it is ready to for a pull request in conda-forge (you could maybe squash all commits into one before proposing it, though).

@kapilkd13
Copy link
Contributor Author

@mstimberg I tried with new libneuroml version but I am still getting the error. I modified meta.yaml file, updated libneuroml repo for any new changes, dropped mongo table. Then I created a new conda env for testing but I am still getting these errors. Can you test it on your system and verify.

@kapilkd13
Copy link
Contributor Author

@mstimberg I have created a new branch for conda recipe. Here is a link to final recipe https://github.com/kapilkd13/staged-recipes/blob/add_libneuroml/recipes/libneuroml/meta.yaml
Everything is working fine now. Actually I forgot to switch to development branch after pulling changes.

@mstimberg
Copy link
Member

Great, I can confirm that it works fine on my machine as well. Please go ahead and open the pull request for inclusion in conda-forge.

@kapilkd13
Copy link
Contributor Author

kapilkd13 commented Jun 16, 2018

Hi @mstimberg I was working on morphology function. I have a doubt, while reading a nml file how should we determine if the given set of segments belong to a section. What I am thinking is to create a tree of morphology object and perform DFS and consider link of segments that have only one children (ex. A--B--C--D) as one section. Should we do this for intermediate nodes also. I mean
A-----B-----C---D---E----G
         |              |
        C             F
In this tree with soma at A, we can consider E---G as one section, but should we also consider C--D as one section as C has only one child, giving us
A-----B-------sec1---sec2
         |              |
        C             F
Also should A---B be considered as one section

@mstimberg
Copy link
Member

Yes, I'd consider the second to be the right choice, with A and B separate if A is named soma and B something else. When we import from SWC, our policy is: merge every segment that only has one child with its child segment if both segments have the same type. In NeuroML we don't have a direct equivalent to the type information, but we can use a heuristic based on the name of the segment. I'd propose two options (e.g. with a name_heuristic option that defaults to True):

  • name_heuristic == False: Ignore the name of the segments, merge all segments if they only have one child. The only exception: if a segment is called soma, don't merge it with anything.
  • name_heuristic == True: Merge segments if they only have one child and their name matches, except for a numerical suffix. E.g., dend0 and dend1 can be merged but dend0 and apic0 cannot.

Hope that makes sense?

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I have pushed current development, moving towards class implementation and changing morphology generation method. Its pretty rough right now but it will be the basic structure for us. please have a look.
Command like this should work

from brian2tools.nmlimport.nml import *
s=MorphologyTree('nml-file')
plot_morphology(s.morphology_obj)

@kapilkd13
Copy link
Contributor Author

ah, I forgot to rebase my branch against master. Docs are building now, Thanks :) Please go ahead and review this PR. In the meantime I will look at the biophysical properties extracted from the file.

Copy link
Member

@mstimberg mstimberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looks very good in principle, I left quite a number of comments but these are mostly style issues that should be easy to fix.

from collections import defaultdict

# Return segment type depending on proximal and distal diameter
def get_segment_type(segment):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function is not used anymore.



# Checks if distal of first segment connects with proximal of second segment
def are_segments_joined(segment1, segment2):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, I don't think we need this check anymore ("unconnected" segments are fine).



# Shift coordinates for further processing
def shift_coords(x):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another function that is not used.

@@ -0,0 +1,349 @@
import neuroml.loaders as loaders
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purely cosmetic, but I prefer imports to be ordered/grouped (first standard library imports, then 3-rd party, then brian2/brian2tools), see http://brian2.readthedocs.io/en/stable/developer/guidelines/style.html


seg_parent = get_parent_segment(segment, segments)

if not are_segments_joined(segment, seg_parent):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the helper file, it is now fine if segments are not joined.

morphology=nml_object.morphology_obj
|

- To obtain topology information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this section could be shortened a bit. How about saying: "With this Morphology object, you can use all of Brian's functions to get information about the cell:" and then having a long code block (with these interactive >>> prompts). I don't think you need to say that morphology.area gives you the area, etc.

Pyramidal cell's Morphology plot.


- To get the resolved group ids of an nml `SegmentGroup`. This returns a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, I think it would be good to first show here a shortened version of the NeuroML file. I'd also add a little explanation of what a SegmentGroup is. BTW: You should use two backticks, i.e. ``SegmentGroup`` because SegmentGroup is not an object defined anywhere in the documentation.

[8, 6, 7], 'basal_gaba_input': [6], 'background_input': [7]}
|

- To get the information of all the child segments of a parent segment. This
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we need this, you can also use the children information stored in Brian's morphology

</morphology>
</cell>

Handling FractionAlong value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather explain this in the section title (e.g. "Handling sections not connected at the distal end")

</cell>

Handling FractionAlong value
--------------------------------
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Underline needs to have the same length as the title 😄

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg , I have made the suggested changes. But due to some reason travis is failing for python2. It looks like there is some issue with conda build in python2. Please have a look at https://travis-ci.org/brian-team/brian2tools/jobs/402402431

@mstimberg
Copy link
Member

But due to some reason travis is failing for python2. It looks like there is some issue with conda build in python2.

There was a problem in the conda package itself, but it got updated a few hours ago. I restarted the tests and they now pass.

Copy link
Member

@mstimberg mstimberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is looking very good, there are just a few more typos and missing NmlMorphology->NMLMorphology renames to correct (see my comments).

return doc


def _is_heurestically_sep(self, section, seg_id):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"heurestically" --> "heuristically"

Returns
-------
bool
Returns true if the given segment is not heurestically connected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here


def __init__(self, file_obj, name_heuristic=True):
"""
Initializes NmlMorphology Class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in a few other places: "NmlMorphology" --> "NMLMorphology"

@@ -0,0 +1,62 @@
import argparse
import os, sys
from brian2tools.nmlimport.nml import NmlMorphology
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now broken I guess (NmlMorphology --> NMLMorphology)


.. code:: python

from brian2tools.nmlimport.nml import NmlMorphology
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NmlMorphology --> NMLMorphology (here and below in a few places)

@kapilkd13
Copy link
Contributor Author

Hi @mstimberg I was developing function to convert str values like cond_density to quantity object and then I found https://github.com/brian-team/brian2tools/blob/master/brian2tools/nmlexport/supporting.py#L12 this method 😃 . This converts our string value to quantity object. I tested with few values, result looks good. We can make a copy of that function and extend it for our usecase. What do you think?
Something like this does the work:

from brian2tools.nmlexport.supporting import from_string
m=NMLMorphology('.nml file')
q=from_string(m.doc.cells[0].biophysical_properties.membrane_properties.channel_densities[0].cond_density)

@mstimberg
Copy link
Member

Hi @mstimberg I was developing function to convert str values like cond_density to quantity object and then I found https://github.com/brian-team/brian2tools/blob/master/brian2tools/nmlexport/supporting.py#L12 this method smiley . This converts our string value to quantity object. I tested with few values, result looks good. We can make a copy of that function and extend it for our usecase. What do you think?

Ha, I completely forgot about this function, I thought we only had the conversion in the other direction! We need to test this function more thoroughly, but in principle it should do what we want. So indeed no need to write this function again. Maybe the best approach would be to have a new package in parallel to nmlexport and nmlimport that includes functions that are useful for both, something like nmlutils.

@mstimberg mstimberg merged commit 339376e into brian-team:master Jul 13, 2018
@mstimberg
Copy link
Member

...and merged it is! This is already very useful as it is, looking forward to seeing even more features implemented 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants