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
A better way to create a response function for CSD #404
Conversation
dirs = dirs[single_peak_mask] | ||
|
||
for num_vox in range(0, data.shape[0]): | ||
rotmat = vec2vec_rotmat(dirs[num_vox, 0], np.array([0, 0, 1])) |
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.
It looks like maybe some of these computations (e.g. this line) could be moved outside of the num_it loop (above line 762)
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 might be misunderstanding, not sure.
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.
Hey @arokem, thanks a lot for your comments! I'm not sure if this is the case? I want to rotate the main peak direction of every voxel towards the z-axis here. So I actually need to compute the rotation matrix that does this for every voxel independently (this is rotmat). Then I rotate the gradients accordingly, and fit spherical harmonics. Maybe I overlook something, could you perhaps comment on that? Thank you!
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.
Yep - gotcha - sorry about that.
On Thu, Aug 21, 2014 at 10:53 AM, ChantalTax notifications@github.com
wrote:
In dipy/reconst/csdeconv.py:
csd_peaks = peaks_from_model(model=csd_model,
data=data,
sphere=sphere,
relative_peak_threshold=peak_thr,
min_separation_angle=25,
parallel=parallel)
dirs = csd_peaks.peak_dirs
vals = csd_peaks.peak_values
single_peak_mask = (vals[:, 1] / vals[:, 0]) < peak_thr
data = data[single_peak_mask]
dirs = dirs[single_peak_mask]
for num_vox in range(0, data.shape[0]):
rotmat = vec2vec_rotmat(dirs[num_vox, 0], np.array([0, 0, 1]))
Hey @arokem https://github.com/arokem, thanks a lot for your comments!
I'm not sure if this is the case? I want to rotate the main peak direction
of every voxel towards the z-axis here. So I actually need to compute the
rotation matrix that does this for every voxel independently (this is
rotmat). Then I rotate the gradients accordingly, and fit spherical
harmonics. Maybe I overlook something, could you perhaps comment on that?
Thank you!—
Reply to this email directly or view it on GitHub
https://github.com/nipy/dipy/pull/404/files#r16555307.
Hey @ChantalTax - looks overall great, but I haven't done much of a fine-grained review yet. Would you mind rebasing this on top of master before we move on? Let me know if you need help with that |
x, y, z = rot_gradients[where_dwi].T | ||
r, theta, phi = cart2sphere(x, y, z) | ||
# for the gradient sphere | ||
B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, 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.
it seems like B_dwi can be computed outside of the loop too,
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.
Hey @MrBago, many thanks for your comments! As I wrote in the above comment, I am not sure if this is possible, but maybe I overlook something? I want to rotate the main peak direction of every voxel towards the z-axis here. So I actually need to compute the rotation matrix that does this for every voxel independently (this is rotmat). Then I rotate the gradients accordingly, and fit spherical harmonics.
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.
Sorry @ChantalTax, I started shooting my mouth off before I gave this a proper look over. This looks really awesome overall. I think you're right, there is no way to avoid calling real_sh_harm
inside of this loop, which is too bad because the scipy implementation is quite slow (but I hear that might be improving soon).
Looking over this, the main thing I notice is that you're not enforcing axial symmetry on the response function. I think that might be a good idea because you're using a vector alignment to get your rotation matrix, this leaves the axial rotation angle ambiguous (which makes sense for this application), but then I think you should force the response function to be axially symmetric (m != 0
harmonics have zero coefficient). This might also allow you to improve performance because you don't need to build the full B_dwi matrix every time. That being said, you've spent a lot more time on this than I have, so just let me know if I'm rambling nonsense.
In terms of performance, can i suggest using a HemiSphere instead of a full sphere. The HemiSphere implementation in dipy takes advantages of the antipodal symmetry inherent in diffusion imaging. It works everywhere a sphere is needed (but doesn't look as good for displaying ODFs/FODs and such), but is twice as fast. You can import the HemiSphere version of 'symmetric724' by using from dipy.reconst.peaks import default_sphere
, or you can build a HemiSphere from any sphere by using hemisphere = HemiSphere.from_sphere(sphere)
. This will eliminate the colinear lines in your B_dwi matrix, which aren't helping you anyways.
Thanks for you great contribution, let me know if you need any help with my suggestions above.
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.
Please see my suggestion above for a significantly faster way to
evaluate sph_harm, which may help out 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.
@MrBago yes you are totally right about the axial symmetric response function. Up to now I didn't do this because, if I'm not mistaken, the function sh_to_rh does this before actual CSD. But it would be nicer to output an axially symmetric response function, I totally agree.
I'm not entirely sure what you mean by using a hemisphere, and to which lines of codes you refer to? Could you shed some light on that?
Thanks!
@stefanv I remember that you had identified in the past that real_sph_harm is too slow and you had some ideas on how to better implement this. For @ChantalTax the performance of real_sph_harm is crucial as she is calling it many many times. Would you mind suggesting a performance boost for this function. Make it or show to Chantal how she could do it? |
This will be fixed after this year's scipy GSoC. |
Oh that is great @stefanv. Until we start depending on the latest scipy version we will need to have something working nicely in dipy. Would it be possible to let us know when that update is ready so that we can copy it temporarily in Dipy? Would that be reasonable or this will be C code? |
It may require access to Fortran. @pv, can you confirm the requirements |
Eleftherios, Couldn’t help but chime in on this one. Numerical recipes has some really simple code for calculating these. See the chapter on special functions, sub-chapter “Spherical Harmonics” The code can be stripped down even more to speed things up. I’ve attached some code I use (in C) to calculate the real spherical harmonics. Feel free to use it if it helps. Sorry I don’t have time to do much coding. Ken From: Eleftherios Garyfallidis [mailto:notifications@github.com] @stefanvhttps://github.com/stefanv I remember that you had identified in the past that real_sph_harm is too slow and you had some ideas on how to better implement this. For @ChantalTaxhttps://github.com/ChantalTax the performance of real_sph_harm is crucial as she is calling it many many times. Would you mind suggesting a performance boost for this function. Make it or show to Chantal how she could do it? — Please consider the environment before printing this e-mail Cleveland Clinic is ranked as one of the top hospitals in America by U.S.News & World Report (2014). Confidentiality Note: This message is intended for use only by the individual or entity to which it is addressed and may contain information that is privileged, confidential, and exempt from disclosure under applicable law. If the reader of this message is not the intended recipient or the employee or agent responsible for delivering the message to the intended recipient, you are hereby notified that any dissemination, distribution or copying of this communication is strictly prohibited. If you have received this communication in error, please contact the sender immediately and destroy the material in its entirety, whether electronic or hard copy. Thank you. /******************************************************************************
*****************************************************************************/ #define M_4PI 12.56637061435917295405 void ylm_calc( int lmax, float z, float phi, float * ylm)
} |
Just note that numerical recipes code is not licensed under a free license On Thu, Aug 14, 2014 at 6:51 PM, ken-sakaie notifications@github.com
|
The revised sph_harm implementation is faster by a factor of 50 or more. Further performance in your code would gained by restructuring the |
Stefan The code isn't copy and paste, so I think there shouldn't be copyright issues. The algorithm really isn't much more than a recursion relation from Abramowitz and Stegun. A&S is now on the NIST website, which is US gov't funded (as was A&S). I expect it would be illegal to make the recursion relation proprietary. Thank you, On Aug 14, 2014, at 1:02 PM, "Stefan van der Walt" <notifications@github.commailto:notifications@github.com> wrote: Just note that numerical recipes code is not licensed under a free license On Thu, Aug 14, 2014 at 6:51 PM, ken-sakaie <notifications@github.commailto:notifications@github.com>
— Reply to this email directly or view it on GitHubhttps://github.com//pull/404#issuecomment-52211303.Please consider the environment before printing this e-mail Cleveland Clinic is ranked as one of the top hospitals in America by U.S.News & Confidentiality Note: This message is intended for use only by the individual Thank you. |
If you just want to calculate it for single
The performance should be pretty close to C for large enough theta, phi arrays. |
peak in order to call it a single fiber population [1] | ||
init_fa : float | ||
FA of the initial 'fat' response function (tensor) | ||
init_trace : fload |
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.
typo: fload -> float
S0 = 1 | ||
evals = fa_trace_to_lambdas(init_fa, init_trace) | ||
response = (evals, S0) | ||
sphere = get_sphere('symmetric724') |
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 use a HemiSphere you can just replace this line with sphere = default_sphere
and import default_sphere
from dipy.reconst.peaks
. default_sphere
is HemiSphere version of symmetric724
.
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.
Please use this sphere from dipy.data import default_sphere
.
@ChantalTax if we add the faster implementation of the spherical harmonics as a new function, the rest of dipy will not benefit from the faster implementation (unless someone goes back and changes all the function calls). Can I suggest that if you'd like to work on a faster implementation, you split out this work into a separate pull request and simply replace Also before you put a lot of time into this, I'm inclined to suggest that dipy continues to use the scipy implementation and that we make a note in the installation docs that recommends users upgrade to the upcoming scipy release for the best performance. I'm happy to discuss this point further but let's pick it up in issue #410. |
Hey @ChantalTax - could you rebase this on |
I'd like to see the commits dealing with the shm basis rebased out of this before we merge it, but that shouldn't be much extra work because this needs to be rebased on master anyways. |
Yeah - sounds OK to me. @ChantalTax, @Garyfallidis - please let us know if On Mon, Oct 6, 2014 at 7:42 PM, MrBago notifications@github.com wrote:
|
@ChantalTax is currently out of office for the coming weeks. This PR will have to wait. I am sure she will need our help for rebasing this as she is new in the git business. |
Should we just go ahead and rebase + merge this ourselves without her? On Tue, Oct 7, 2014 at 12:10 PM, Eleftherios Garyfallidis <
|
Better wait for Omar's PR to get in. This was rebased before his PR was reverted. It might resolve itself. |
Hey, @ChantalTax are you back on this? |
Let us know if you need any help with merging/rebasing etc. |
|
||
x, y, z = rot_gradients[where_dwi].T | ||
r, theta, phi = cart2sphere(x, y, z) | ||
# for the gradient sphere |
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 believe, but I might be wrong, that here you could do something like B_dwi = real_sph_harm(0, n[m == 0], theta[:, None], phi[:, None])
. The coefficients associated with the other rows of the matrix are going to be set to zero anyways and the functions should be orthogonal, so the result should not change.
This would require changes in a few other places in the code, for example the CSD model might need accept the response function as N / 2 + 1 coefficients (N being the SH order) of a radially symmetric function. But that might be the right thing to do anyways.
Please let me know if I'm just wrong about this or if I'm not explaining it well.
response[sh_mask] = 0 | ||
|
||
change = abs((response_p[~sh_mask] - response[~sh_mask])/response_p[~sh_mask]) | ||
if change.all() < convergence: |
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.
Hi, @ChantalTax
Is change.all() < convergence
what you intended or did you mean all(change < convergence)
? As it is now, the result of change.all()
(either True or False) is compared to convergence
(0.001 by default).
Cheers,
Kesshi
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 your comment, you are right, I think I mean the second option: to check if all changes were below the convergence threshold. I will replace this by all(change < convergence)
.
Best,
Chantal
Hey @ChantalTax - could you please rebase this on |
…ould be tested more because they don't give the same results as the currently used functions
…esponse function axially symmetric
2488ab2
to
0c20dfd
Compare
Hey @MrBago - this is now rebased on master. Could you please take another look? |
@@ -47,7 +55,7 @@ def __init__(self, gtab, response, reg_sphere=None, sh_order=8, lambda_=1, | |||
ndarray and the second is the signal value for the response | |||
function without diffusion weighting. This is to be able to | |||
generate a single fiber synthetic signal. The response function | |||
will be used as deconvolution kernel ([1]_) | |||
will be used as deconvolution git pull nipy-dipy masterkernel ([1]_) |
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.
Whoops. Looks like this somehow got inserted here, while we were rebasing this.
@ChantalTax, @arokem, @MrBago should we close this PR now? |
Isn't that replaced by #588? Sorry, if I missed something. |
Thanks all! That's a wrap (We merged this in #588). |
This implementation includes the recursive calibration of the response function for CSD, Using an FA threshold is not a very robust method.It is dependent on the dataset (non-informed used subjectivity), and still depends on the diffusion tensor (FA and first eigenvector), which has low accuracy at high b-value. This function recursively calibrates the response function, for more information see Tax et al. NeuroImage 2014.
This implementation is slow only on one line (788) which calls real_sph_harm. I would appreciate any help to optimize this function. Thx!