-
Notifications
You must be signed in to change notification settings - Fork 13
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
move_to method #320
move_to method #320
Conversation
I just realized that I vectorized the part that takes the coordinate arrays but not the part that takes floats, so I will vectorize that "half" of the method too now:) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks!
Please write a docstring that explains exactly what this method is doing.
jaxley/modules/base.py
Outdated
# Compute the within cell branch offsets | ||
tup_indices = np.array([view.cell_index, view.branch_index]) | ||
branches_per_cell = np.unique(tup_indices, axis=1)[0] | ||
_, first_branches = np.unique(branches_per_cell, return_index=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to get the first branches, can you not do net[:,0,0].view.index
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really because what I need is the index into xyzr which has length equal to the number of branches, not the number of compartments like view
I think you can almost exactly copy the PR description into the docstring :) |
Thanks for the feedback @michaeldeistler! I added lots of comments, so you can let me know now if it's too much or too little. Unfortunately I still cannot use move_to() with array inputs and groups, because group.xyzr doesn't exist (like network.xyzr), but we can talk about whether that's worth implementing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing out that this is not yet possible :) But I think this can be done without much code, see me comment below. Let me know if you want to have a quick zoom.
@@ -1021,6 +1020,73 @@ def _move(self, x: float, y: float, z: float, view): | |||
self.xyzr[i][:, 1] += y | |||
self.xyzr[i][:, 2] += z | |||
|
|||
def move_to( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I just saw that this cannot move submodules, right? I.e.
net.cell(0).move_to(x, y, z)
does not work, right?
I think this should definitely be possible. The way to do this is to pass self.nodes
to the ._move_to()
method in Module
and to add a move_to
method to View
and pass self.view
to _move_to()
in the View
. You can take inspiration from other methods: For example the .show()
method passes self.nodes
here and self.view
in the View
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That actually does work, I wrote the function just like move() so it is also in view (these lines). Hope this is alright :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well sorry, this works to update the coordinates in the view, but then I guess it doesn't update the module's xyzr, so I will try to fix that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you probably need self.pointer.xyzr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I made it so that it can take cell view input now. It is not very pretty underneath, but when one does net.cell(0).move_to(x, y, z)
, one is really operating on net.xyzr
, and it makes sense that this is distinct from operating on a cell.xyzr
, which would only be the case if one does cell.move_to(x, y, z)
. I am totally open to prettier ways of doing this, but for now it works, and it is even possible to do net.cell([0, 1, ...]).move_to(x, y, z)
where x, y, and z are arrays the length of the number of cells!
I also added ._update_nodes_with_xyz()
to both the move and move_to methods at the end so that the results are shown in the view.
Another thing I added is that if someone tries to call move()
or move_to()
with float input and xyz is not calculated yet, then it will print something and run .compute_xyz()
. If this isn't done, then xyzr will just stay entirely nans, and the user might be confused as to why.
This adds @property
def xyzr(self):
idxs = self.view.global_comp_index
return np.array(self.pointer.xyzr)[idxs] Works until |
jaxley/modules/base.py
Outdated
|
||
@property | ||
def xyzr(self): | ||
idxs = self.view.global_comp_index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might wanna raise on .loc
or add `interpolation of coordinates like in
jaxley/jaxley/utils/cell_utils.py
Line 195 in 08d5538
def interpolate_xyz(loc: float, coords: np.ndarray): |
I was getting some weird behavior trying to use the xyzr property before, so I removed it, but I will add it back in when I make sure that it works alright. Then I'll see if it can be used to clean up the method a bit. Thanks for your patience :) |
A couple of updates on this pull request:
Looking forward to hearing your thoughts :) Edit: I guess I will work next on getting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot!
I think we definitely have to support SWC files though. I think an easy way (that will also save a ton of code) would be to have a function that modifies one coordinate and jit
that. Would that not work? We can also discuss on Thu.
jaxley/modules/base.py
Outdated
|
||
NOTE: This method works when module.xyzr is not of the form | ||
(n branches, [start_branch, end_branch], 4) but rather constructed by the code | ||
that reads swc files.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it does not work if the xzy is constructed with self.compute_xyz()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I only now (after having seen code below) understand this sentence. So, IIUC, it does work for coordinates generated from self.compute_xyz()
and it does not work for those generated from read_swc
, right?
Anyways, I think this is a pretty big limitation. Can we not jit-compile the for loop across branches and make it fast without having to rely on matrix operations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry that was not worded correctly.
I should definitely get something working for xyzrs from SWCs -- the only reason I didn't before is just because I didn't realize they were different in shape from xyzr generated by compute_xyz().
The one thing that I find a bit strange about xyzr from SWC files though is that some branches have a (4, ) shape array as the xyzr, so not even the start and end of the branch. I guess this then means that those branches have length zero or something. I will have to look into this more though.
As for the jit compiled loop, jitting would require static argument shapes, and different networks will have different numbers of branches. If we jit the moving of one branch, I'm not terribly convinced there would be a great speed-up because the function is then just one variable assignment, but I can test it. vmap might be really helpful though.
@@ -1024,6 +1028,102 @@ def _move(self, x: float, y: float, z: float, view): | |||
self.xyzr[i][:, 0] += x | |||
self.xyzr[i][:, 1] += y | |||
self.xyzr[i][:, 2] += z | |||
self._update_nodes_with_xyz() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you modifying ._move()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this line, the xyz coords don't update in the view, so calling network.show() after calling .move() would not update the xyz. This is run at the end of move_to() as well. We could require that the user call _update_notes_with_xyz() after calling move() and move_to(), but I thought that was a bit of a hassle
jaxley/modules/base.py
Outdated
If x, y, and z are arrays, then they must each have a length equal to the number | ||
of cells being moved. Then the first compartment of the first branch of each | ||
cell is moved to the specified location. This avoids the need to move cells in | ||
loops. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome!
jaxley/modules/base.py
Outdated
x: Union[float, np.ndarray] = 0.0, | ||
y: Union[float, np.ndarray] = 0.0, | ||
z: Union[float, np.ndarray] = 0.0, | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type hint for return type: -> None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not done for any of the other methods in base.py though as far as I see, should I add that everywhere? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or I could just do it here and then I guess that would be a separate PR
jaxley/modules/base.py
Outdated
# Find the indices in xyzr of the first branch of each cell | ||
_, first_branches = np.unique(cell_inds, return_index=True) | ||
# Select the xyz coordinates of the first branch of each cell | ||
xyz_first_branches = xyzr_arr[first_branches, :, :3] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you not do pointer[cell_inds, 0, 0].xyzr
?
jaxley/modules/base.py
Outdated
# Compute the distance between first branches and their connected branches | ||
branch_offsets = xyzr_arr[:, :, :3] - xyz_firsts_expanded | ||
|
||
# Compute new coord arr [# branches, [branch start, branch end], [x, y, z]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we just use a jit
ted for-loop we can get rid of almost all of this, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great now! Thanks a ton!
This PR adds a method to base.py that allows one to move cells or networks to specific x, y, z coordinates.
It takes a float, such that the first compartment of the first branch of the first cell is moved to that float coordinate, and everything else is shifted accordingly.
However, it can also take an array where the x, y, z dimensions all have to be the same (and the same as the number of cells), and this allows moving cells to specific x, y, z locations all at once. I wanted to write this so that one doesn't have to loop over all of the x, y, z coordinates of each cell to assign their locations, which would be very cumbersome for a large network.
I also wrote a file with tests for
move()
(which were missing before as far as I could tell) andmove_to()
.I look forward to hearing feedback and thoughts, because I'm sure there is some room for improvement.