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

[MRG] compute yscale from cell points #327

Merged
merged 10 commits into from May 9, 2021

Conversation

jasmainak
Copy link
Collaborator

closes #326

I tried not to touch too much so others can continue with their PRs

@jasmainak jasmainak changed the title [WIP] compute yscale from cell points [MRG] compute yscale from cell points May 7, 2021
@jasmainak jasmainak requested review from ntolley and rythorpe May 7, 2021 13:45
hnn_core/cell.py Outdated
@@ -293,18 +306,28 @@ def move_to_pos(self):
# 2. a list needs to be created with a Dipole (Point Process) in each
# section at position 1
# In Cell() and not Pyr() for future possibilities
def insert_dipole(self, yscale):
def insert_dipole(self, p_secs, dipole_sec):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's becoming clear that p_secs should be an attribute of Cell but I'm holding off on that in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see why p_secs needs to be an attribute of Cell since it isn't used anywhere outside Cell.build() and here. Since Cell.insert_dipole() is called after build(), why not just access the points via the NEURON Section objects?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm imagining that plot_cell_morphology will move from Network to Cell and then self.p_secs will be used for the plot. I'm a little wary of relying on NEURON objects because of : 1) numerical issues we saw in the past 2) unpicklability. Since p_secs is picklable we can make it accessible to the user to inspect/modify etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that to enable Cell.plot we should keep HOC-objects out of ours for as long as possible.

@codecov-commenter
Copy link

codecov-commenter commented May 7, 2021

Codecov Report

Merging #327 (b8b75b3) into master (580fee6) will increase coverage by 0.02%.
The diff coverage is 93.93%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #327      +/-   ##
==========================================
+ Coverage   90.31%   90.33%   +0.01%     
==========================================
  Files          13       13              
  Lines        2416     2421       +5     
==========================================
+ Hits         2182     2187       +5     
  Misses        234      234              
Impacted Files Coverage Δ
hnn_core/viz.py 87.50% <33.33%> (ø)
hnn_core/__init__.py 100.00% <100.00%> (ø)
hnn_core/cell.py 97.15% <100.00%> (+0.13%) ⬆️
hnn_core/cells_default.py 97.82% <100.00%> (ø)
hnn_core/params_default.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 580fee6...b8b75b3. Read the comment docs.

hnn_core/cell.py Outdated Show resolved Hide resolved
doc/whats_new.rst Show resolved Hide resolved
hnn_core/cell.py Outdated Show resolved Hide resolved
hnn_core/cell.py Outdated Show resolved Hide resolved
hnn_core/cell.py Outdated
from neuron import h, nrn

# Units for e: mV
# Units for gbar: S/cm^2


def _get_yscale(p_secs, dipole_sec):
"""Get cos theta to compute dipole in z direction."""
a = (np.array(p_secs[dipole_sec]['sec_pts'][1]) -
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here we have the hard assumption that our cells live in the xy-plane. Not much to do about it, just a note.

Copy link
Collaborator Author

@jasmainak jasmainak May 8, 2021

Choose a reason for hiding this comment

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

Not really. This just projects along the apical dendrite which may or may not be oriented along the y direction (Neuron coordinates ... z direction in physical coordinates).

There are the following two alternatives which I considered and that does assume the xy-plane:

y_long = (h.y3d(1, sec=sect) - h.y3d(0, sec=sect)) * pos_all

and

y_long = (p_secs[sec_name]['sec_pts'][1][1] - p_secs['apical_trunk']['sec_pts'][0][1]) * pos_all

and get rid of yscale altogether. That didn't work though because of numerical issues I suspect. The code uses a mix of Neuron and Python. At some point we should figure out what kind of rounding/precision Neuron actually uses. We could deliberately typecast in Python then to make things match and then remove it when we're confident it's correct

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not really. This just projects along the apical dendrite which may or may not be oriented along the y direction

But isn't index 1 in p_secs[dipole_sec]['sec_pts'][1] explicitly the y-coordinate? And 0 on the next line x. That's what I mean by "hard assumptions"---we're doing 2D projection, implicitly ignoring the z-orientation, which is assumed to be degenerate (flat).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But isn't index 1 in p_secs[dipole_sec]['sec_pts'][1] explicitly the y-coordinate?

actually no, the default Neuron has only two points and this is the second point. To get the y-coordinate of the second point you would have to do:

p_secs[dipole_sec]['sec_pts'][1][1]

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, my bad. All clear now!

hnn_core/cell.py Outdated
@@ -293,18 +306,28 @@ def move_to_pos(self):
# 2. a list needs to be created with a Dipole (Point Process) in each
# section at position 1
# In Cell() and not Pyr() for future possibilities
def insert_dipole(self, yscale):
def insert_dipole(self, p_secs, dipole_sec):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that to enable Cell.plot we should keep HOC-objects out of ours for as long as possible.

hnn_core/cell.py Outdated Show resolved Hide resolved
hnn_core/cell.py Outdated
The properties of syn are specified in p_syn.
mech is a dict with keys as the mechanism names. The
values are dictionaries with properties of the mechanism.
dipole_sec : str
Copy link
Collaborator

Choose a reason for hiding this comment

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

Along the lines of my other comments, could this be 'apical_section'?

The name of a section defining the apical orientation, onto which dipole moments are projected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

isn't "apical" specific to the default neuron? I like the use of term "projection" though. I'll try to incorporate it

Copy link
Collaborator

Choose a reason for hiding this comment

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

In my mind, "apical" refers to the single large dendrite sprouting from the top of the pyramid-formed soma (from the "apex" of the pyramid). Some L2/3 pyramidal cells have a bifurcation very close to the soma, but even then the two apical dendrites run in parallel, towards L1. The "textbook answer" is that the apical dendrite(s) is a defining characteristic of all neocortical (indeed, also hippocampal) pyramidal cells. The terms 'proximal' and 'distal' are much more ambiguous in comparison.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

humm ... so are you saying that you'd always want to compute the dipole along an apical dendrite? I'll change the name in that case

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. Even if we one day included a more morphologically detailed model, the dipole moment would still be calculated along the primary symmetry axis defined by the apical dendrite. You know how in MNE we have the "loose orientation constraint"? The "looseness" is fudge factor to cover for coreg and surface recon errors. The underlying strong assumption is that there's one dominant dipole orientation per source space point. And this is the normal to the cortical (WM) surface because of the apical dendrites.

hnn_core/cell.py Outdated
sect_name = sect.name().split('_', 1)[1]
y_scale = (yscale[sect_name] * sect.L) * pos_all
# y_long = (h.y3d(1, sec=sect) - h.y3d(0, sec=sect)) * pos
pos_proj = sect.L * cos_thetas[sect_name] * pos_all
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this scaling is super mysterious to me. We're doing:

diff(section length * position vector of segment)
= section length * segment lengths

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I was trying to make sense of that too... I think it's very ugly shorthand. The L and theta are constant, so the diff is effectively on the position, which is in units of section length ([0, 1]). This may have something to do with calculating the dipole moment of current, but it all depends on what Dipole.mod and Dipole_pp.mod are actually doing... I'm digging into the details of HOC (trying to figure out my LFP-woes). I have plans to visit the "Dipole"-calculations, too, hoping this part of our code will make more sense!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh I get it now. sect.L * np.diff(pos_all) is the fractional length (in units of um) of that segment. Multiply that by the current in the segment and you get the dipole moment of that segment

Copy link
Collaborator

Choose a reason for hiding this comment

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

Indeed, but that's what I mean by having to understand the MOD-files better. Because then they must not be calculating the dipole moment, right? But rather the primary current density...

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you're right. I'm guessing Qsum is the the sum of all radial current entering the dendrite at a given point in a nrn section?

Copy link
Collaborator

@cjayb cjayb left a comment

Choose a reason for hiding this comment

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

Final nitpicks, good to merge!

hnn_core/cell.py Outdated
"""Get cos(theta) to compute dipole along the apical dendrite."""
a = (np.array(p_secs[sec_name_apical]['sec_pts'][1]) -
np.array(p_secs[sec_name_apical]['sec_pts'][0]))
yscales = dict()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
yscales = dict()
cos_theta = dict()

hnn_core/cell.py Outdated
yscales = dict()
for sec_name, p_sec in p_secs.items():
b = np.array(p_sec['sec_pts'][1]) - np.array(p_sec['sec_pts'][0])
yscales[sec_name] = np.dot(a, b) / (norm(a) * norm(b))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
yscales[sec_name] = np.dot(a, b) / (norm(a) * norm(b))
cos_theta[sec_name] = np.dot(a, b) / (norm(a) * norm(b))

hnn_core/cell.py Outdated
for sec_name, p_sec in p_secs.items():
b = np.array(p_sec['sec_pts'][1]) - np.array(p_sec['sec_pts'][0])
yscales[sec_name] = np.dot(a, b) / (norm(a) * norm(b))
return yscales
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return yscales
return cos_theta

hnn_core/cell.py Outdated
mech is a dict with keys as the mechanism names. The
values are dictionaries with properties of the mechanism.
sec_name_apical : str
The name of the section along which dipole is to
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The name of the section along which dipole is to
The name of the section along which dipole moment is calculated.

WDYT?

@jasmainak
Copy link
Collaborator Author

Addressed! Thanks for the careful reviews :)

@cjayb cjayb merged commit eaa7373 into jonescompneurolab:master May 9, 2021
@cjayb
Copy link
Collaborator

cjayb commented May 9, 2021

Thanks for the much more readable code @jasmainak, merged after green light from @rythorpe

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.

compute cos theta from actual section points
4 participants