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

Feat/export swc:- This aims to add a file which exports SWC file into neuroml. #407

Merged
merged 34 commits into from
Sep 4, 2024

Conversation

AdityaBITMESRA
Copy link
Contributor

This draft PR aims to add a file which exports SWC into neuroml for visualisation

  1. It is not complete mainly around the soma representation part.
  2. Will be making it more efficient and reliable so that it can cover all swc cases.

@AdityaBITMESRA AdityaBITMESRA marked this pull request as draft July 19, 2024 08:09
@AdityaBITMESRA AdityaBITMESRA marked this pull request as ready for review July 29, 2024 08:04
@AdityaBITMESRA
Copy link
Contributor Author

Hi @sanjayankur31 I have refactored the code. Can you give it a look if time permits?

@sanjayankur31 sanjayankur31 self-requested a review July 29, 2024 09:45
@sanjayankur31 sanjayankur31 added the S: ready for review Status: ready for review label Jul 29, 2024
@sanjayankur31
Copy link
Member

Cool, will have a look at it today or tomorrow hopefully.

@sanjayankur31
Copy link
Member

Did we add any unit tests for the writer @AdityaBITMESRA ? (Not seeing them in this PR)

@AdityaBITMESRA
Copy link
Contributor Author

No, I will add them soon

@AdityaBITMESRA
Copy link
Contributor Author

I wanted to ask how should I structure my test file like I should build a mock swc data for all the cases?

@sanjayankur31
Copy link
Member

I wanted to ask how should I structure my test file like I should build a mock swc data for all the cases?

Yeh, you can build mock data, or you can even copy the case files from the CVApp if you want, or you can only paste their contents into a Python string in the test? Whatever you think works best.

Copy link
Member

@sanjayankur31 sanjayankur31 left a comment

Choose a reason for hiding this comment

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

Looks very good. Getting there. A few notes/queries/suggestions there.

pyneuroml/swc/ExportNML.py Outdated Show resolved Hide resolved


class NeuroMLWriter:
def __init__(self, swc_graph: "SWCGraph"):
Copy link
Member

Choose a reason for hiding this comment

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

does SWCGraph need to be in quotes here?

start_point = self.find_start_point()

print(f"Cell name: {cell_name}")
print(f"Start point: {start_point}")
Copy link
Member

Choose a reason for hiding this comment

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

if you're using these for debugging, use logging so that you can use logger.debug and so on. Only stuff that is definitely needed to be printed to the user should use print.

pyneuroml/swc/ExportNML.py Show resolved Hide resolved
:param this_point: The current SWCNode.
:param parent_point: The parent SWCNode.
:param cable_id: The ID of the current cable.
:param version: The NeuroML version being used.
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, we should've mentioned this---NeuroMLv1 is deprecated and not to be used at all. So, no need to have special cases supporting it. We only export to NeuroMLv2.

self.next_segment_id += 1
self.point_indices_vs_seg_ids[this_point.id] = seg_id

segment = [f' <segment id="{seg_id}" name="Seg_{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.

Now that this is all neuroml specific, is there a reason we're not creating objects using the libneuroml API? (Segment and SegmentGroup)? It's usually better than creating xml by hand.

Copy link
Contributor Author

@AdityaBITMESRA AdityaBITMESRA Jul 31, 2024

Choose a reason for hiding this comment

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

Can I get some more ideas on how to proceed with this? I am bit confused about using the libneuroml API.
@sanjayankur31

Copy link
Member

Choose a reason for hiding this comment

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

Sure, see this example for a start:

https://docs.neuroml.org/Userdocs/MultiCompartmentOLMexample.html#declaring-the-cell

When users use NeuroML, they don't write XML---that's tedious and error prone. Instead, they use the libNeuroML API:

cell = neuroml.Cell(...)
cell.add_segment(...)

..

it's basically a Python object model of the NeuroML schema, so a Cell contains Morphology, which contains Segments and SegmentGroups.

That's a better way of generating the NeuroML than writing XML by hand.

Let me know if this isn't clear and we can have a chat where I can explain it to you more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thankyou I understand it now...will try to implement this

Copy link
Member

Choose a reason for hiding this comment

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

Do take a look at the various "utility functions" in the Cell class, they should help with lots of things (hopefully):

https://libneuroml.readthedocs.io/en/latest/userdocs/coreclasses.html#cell

Copy link
Member

Choose a reason for hiding this comment

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

I've just remembered: if you just add all the segments with their correct parents to the cell, you should be able to use the create_unbranched_segment_group_branches to automatically create all the unbranched segment groups. It creates the adjacency matrix and then traverses the whole cell detecting branches:

https://libneuroml.readthedocs.io/en/latest/userdocs/coreclasses.html#neuroml.nml.nml.Cell.create_unbranched_segment_group_branches

],
}
groups.extend(
type_groups.get(p.type, [f"SWC_group_{p.type}", "dendrite_group"])
Copy link
Member

Choose a reason for hiding this comment

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

what are we doing here? if a group for the type is not found, we're using SWC_group_{type} and dendrite_group? Not sure if this is correct. If it doesn't belong to the standard types, it shouldn't be valid SWC 🤔

self.group_content.append(f" <meta:group>{group}</meta:group>")
self.group_content.append(" </cable>")
else:
for group in self.get_groups_for_type(this_point):
Copy link
Member

Choose a reason for hiding this comment

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

if this is the first point of a new branch, it should also create a new segment group.

ALL "cables" also have to have their own segment groups, in addition to the standard soma/dendrite etc groups. Is this being done?

@AdityaBITMESRA
Copy link
Contributor Author

Thankyou for all the comments @sanjayankur31 I will update after addressing the comments

@AdityaBITMESRA
Copy link
Contributor Author

Hi @sanjayankur31 I have tried to address all the comments so can you have a look if time permits...it is still not complete but I wanted to know if I am going right or not

@sanjayankur31 sanjayankur31 self-requested a review August 5, 2024 12:24
Copy link
Member

@sanjayankur31 sanjayankur31 left a comment

Choose a reason for hiding this comment

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

It's looking good and you're on the right track. Well done. 👍

Dropped a few comments about things I wasn't completely clear on.

Initialize the NeuroMLWriter.

:param swc_graph: The graph representation of the SWC file.
:type swc_graph: Any # Replace with the actual type of swc_graph if known
Copy link
Member

Choose a reason for hiding this comment

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

Should probably be your SWCGraph class?



class NeuroMLWriter:
def __init__(self, swc_graph):
Copy link
Member

Choose a reason for hiding this comment

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

Please also remember to add the type hint here.

logger.info("Initializing NeuroMLWriter")
self.swc_graph = swc_graph
self.points: List[SWCNode] = swc_graph.nodes
self.section_types: List[str] = [
Copy link
Member

Choose a reason for hiding this comment

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

are these not already defined in the other module? Can we re-use that so that the same bits aren't defined in two different locations?

self.next_segment_id += 1

segment = Segment(id=seg_id, name=f"Seg_{seg_id}")
segment.type = this_point.type
Copy link
Member

Choose a reason for hiding this comment

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

neuroml.Segment does not have a type attribute

diameter=2 * this_point.radius,
)

self.cell.morphology.segments.append(segment)
Copy link
Member

Choose a reason for hiding this comment

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

why not also place the segment in the default group here when it has been created? All segments should go into the all group, and then here since you already check what the segment type is, you can also at least place it in the soma/dendrite/axon group?

Otherwise you're iterating over all segments again in the create_segment_group

"""
logger.info("Creating segment groups")

def get_groups_for_type(segment_type: int) -> List[str]:
Copy link
Member

Choose a reason for hiding this comment

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

see above comment, can this be done while creating the segment so it's done in the same pass, instead of doing a second pass over all the segments? (if you need information about all segments to do this, then a second pass is required, that's OK too).


if this_point.type == SWCNode.SOMA:
self.handle_soma(this_point, parent_point, cable_id, version, new_cell)
elif this_point.type != SWCNode.SOMA and parent_point.type == SWCNode.SOMA:
Copy link
Member

Choose a reason for hiding this comment

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

Hrm, I'm unclear on his---the first point of a dendrite will have a soma point as parent, no?

This is tricky to understand. Maybe add a comment before the conditional explaining the three cases so that others reading the code are clear on what's happening here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thankyou for pointing this out...I will add comments to make it more clear

logger.info("Creating Cell object")
cell_name = self.get_cell_name()

self.cell = Cell(id=cell_name)
Copy link
Member

Choose a reason for hiding this comment

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

I'd maybe use the component_factory here:

from neuroml.utils import component_factory
cell = component_factory("Cell", id="cell_name", notes="something")

(the advantage of the component factory is that it validates things as we go)


logger.info("Segment groups created successfully")

def print_soma_segments(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

is this for debugging?

you can use cell.get_segment_group("soma_group") to get all segments in the segment group.

See help(neuroml.Cell) in a python interpreter to see all the methods of the Cell class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes I added this for debugging...will go through the recomendation

Copy link
Member

Choose a reason for hiding this comment

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

Cool, I'd also suggest writing unit tests for testing that each part of your code is doing what you expect it to do :)

@AdityaBITMESRA
Copy link
Contributor Author

I have added a unittest file and will make it more perfect with time

import tempfile
from typing import Dict, List, Optional, Set

import neuroml.writers as writers
Copy link
Member

Choose a reason for hiding this comment

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

This looks good, well done. I think the file should be test_ExportNML.py, just for consistency, though.

@sanjayankur31 sanjayankur31 self-requested a review August 8, 2024 10:23
@AdityaBITMESRA
Copy link
Contributor Author

Hi @sanjayankur31 I have addressed the issues...Can you give it a look?

@AdityaBITMESRA
Copy link
Contributor Author

AdityaBITMESRA commented Aug 27, 2024

I was running with this swc file@sanjayankur31

@AdityaBITMESRA
Copy link
Contributor Author

AdityaBITMESRA commented Aug 27, 2024

Screenshot from 2024-08-13 13-54-19.png

This is the plotted morphology of neuroml generated from the python based converter

Screenshot from 2024-08-13 13-54-57.png

This is from the cvapp

@AdityaBITMESRA
Copy link
Contributor Author

I went through the neuroml file in the soma segment 70 the proximal is set to the previous segment distal which is creating the problem.

When I am manually writing the proximal to that of point id 1 which is its parent according to the swc data I am getting the exact morphology like the cvapp

@sanjayankur31
Copy link
Member

Cool, I think I know what's going on. I'll look into it soon.

@sanjayankur31
Copy link
Member

Working on this here: #421

(This will be automatically merged when that one gets merged, so leaving this open)

@sanjayankur31 sanjayankur31 merged commit 612815d into NeuroML:development Sep 4, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S: ready for review Status: ready for review
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

2 participants