[docs]deftensor_ellipsoid(centers,axes,lengths,colors,scales,opacity):"""Visualize one or many Tensor Ellipsoids with different features. Parameters ---------- centers : ndarray(N, 3) Tensor ellipsoid positions. axes : ndarray (3, 3) or (N, 3, 3) Axes of the tensor ellipsoid. lengths : ndarray (3, ) or (N, 3) Axes lengths. colors : ndarray (N,3) or tuple (3,) R, G and B should be in the range [0, 1]. scales : float or ndarray (N, ) Tensor ellipsoid size. opacity : float Takes values from 0 (fully transparent) to 1 (opaque). Returns ------- box_actor: Actor """box_actor=actor.box(centers,colors=colors,scales=scales)box_actor.GetMapper().SetVBOShiftScaleMethod(False)box_actor.GetProperty().SetOpacity(opacity)# Number of vertices that make up the boxn_verts=8big_centers=np.repeat(centers,n_verts,axis=0)attribute_to_actor(box_actor,big_centers,"center")big_scales=np.repeat(scales,n_verts,axis=0)attribute_to_actor(box_actor,big_scales,"scale")big_values=np.repeat(np.array(lengths,dtype=float),n_verts,axis=0)attribute_to_actor(box_actor,big_values,"evals")evec1=np.array([item[0]foriteminaxes])evec2=np.array([item[1]foriteminaxes])evec3=np.array([item[2]foriteminaxes])big_vectors_1=np.repeat(evec1,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_1,"evec1")big_vectors_2=np.repeat(evec2,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_2,"evec2")big_vectors_3=np.repeat(evec3,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_3,"evec3")# Start of shader implementationvs_dec=""" in vec3 center; in float scale; in vec3 evals; in vec3 evec1; in vec3 evec2; in vec3 evec3; out vec4 vertexMCVSOutput; out vec3 centerMCVSOutput; out float scaleVSOutput; out vec3 evalsVSOutput; out mat3 tensorMatrix; """# Variables assignmentv_assign=""" vertexMCVSOutput = vertexMC; centerMCVSOutput = center; scaleVSOutput = scale; """# Normalizationn_evals="evalsVSOutput = evals/(max(evals.x, max(evals.y, evals.z)));"# Values constraint to avoid incorrect visualizationsevals="evalsVSOutput = clamp(evalsVSOutput,0.05,1);"# Scaling matrixsc_matrix=""" mat3 S = mat3(1/evalsVSOutput.x, 0.0, 0.0, 0.0, 1/evalsVSOutput.y, 0.0, 0.0, 0.0, 1/evalsVSOutput.z); """# Rotation matrixrot_matrix="mat3 R = mat3(evec1, evec2, evec3);"# Tensor matrixt_matrix="tensorMatrix = inverse(R) * S * R;"vs_impl=compose_shader([v_assign,n_evals,evals,sc_matrix,rot_matrix,t_matrix])# Adding shader implementation to actorshader_to_actor(box_actor,"vertex",decl_code=vs_dec,impl_code=vs_impl)fs_vars_dec=""" in vec4 vertexMCVSOutput; in vec3 centerMCVSOutput; in float scaleVSOutput; in vec3 evalsVSOutput; in mat3 tensorMatrix; uniform mat4 MCVCMatrix; """# Importing the sphere SDFsd_sphere=import_fury_shader(os.path.join("sdf","sd_sphere.frag"))# SDF definitionsdf_map=""" float map(in vec3 position) { /* As the scaling is not a rigid body transformation, we multiply by a factor to compensate for distortion and not overestimate the distance. */ float scFactor = min(evalsVSOutput.x, min(evalsVSOutput.y, evalsVSOutput.z)); /* The approximation of distance is calculated by stretching the space such that the ellipsoid becomes a sphere (multiplying by the transformation matrix) and then computing the distance to a sphere in that space (using the sphere SDF). */ return sdSphere(tensorMatrix * (position - centerMCVSOutput), scaleVSOutput*0.48) * scFactor; } """# Importing central differences function for computing surface normalscentral_diffs_normal=import_fury_shader(os.path.join("sdf","central_diffs.frag"))# Importing raymarching functioncast_ray=import_fury_shader(os.path.join("ray_marching","cast_ray.frag"))# Importing the function that generates the ray componentsray_generation=import_fury_shader(os.path.join("ray_marching","gen_ray.frag"))# Importing Blinn-Phong model for lightingblinn_phong_model=import_fury_shader(os.path.join("lighting","blinn_phong_model.frag"))# Full fragment shader declarationfs_dec=compose_shader([fs_vars_dec,sd_sphere,sdf_map,central_diffs_normal,cast_ray,ray_generation,blinn_phong_model,])shader_to_actor(box_actor,"fragment",decl_code=fs_dec)ray_components=""" vec3 ro; vec3 rd; float t; gen_ray(ro, rd, t); """# Fragment shader output definition# If surface is detected, color is assigned, otherwise, nothing is paintedfrag_output_def=""" if(t < 20) { vec3 pos = ro + t * rd; vec3 normal = centralDiffsNormals(pos, .0001); // Light Direction vec3 ld = normalize(ro - pos); // Light Attenuation float la = dot(ld, normal); vec3 color = blinnPhongIllumModel(la, lightColor0, diffuseColor, specularPower, specularColor, ambientColor); fragOutput0 = vec4(color, opacity); } else { discard; } """# Full fragment shader implementationsdf_frag_impl=compose_shader([ray_components,frag_output_def])shader_to_actor(box_actor,"fragment",impl_code=sdf_frag_impl,block="light")returnbox_actor
[docs]defdouble_cone(centers,axes,angles,colors,scales,opacity):"""Visualize one or many Double Cones with different features. Parameters ---------- centers : ndarray(N, 3) Cone positions. axes : ndarray (3, 3) or (N, 3, 3) Axes of the cone. angles : float or ndarray (N, ) Angles of the cone. colors : ndarray (N, 3) or tuple (3,) R, G and B should be in the range [0, 1]. scales : float or ndarray (N, ) Cone size. opacity : float Takes values from 0 (fully transparent) to 1 (opaque). Returns ------- box_actor: Actor """box_actor=actor.box(centers,colors=colors,scales=scales)box_actor.GetMapper().SetVBOShiftScaleMethod(False)box_actor.GetProperty().SetOpacity(opacity)# Number of vertices that make up the boxn_verts=8big_centers=np.repeat(centers,n_verts,axis=0)attribute_to_actor(box_actor,big_centers,"center")big_scales=np.repeat(scales,n_verts,axis=0)attribute_to_actor(box_actor,big_scales,"scale")evec1=np.array([item[0]foriteminaxes])evec2=np.array([item[1]foriteminaxes])evec3=np.array([item[2]foriteminaxes])big_vectors_1=np.repeat(evec1,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_1,"evec1")big_vectors_2=np.repeat(evec2,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_2,"evec2")big_vectors_3=np.repeat(evec3,n_verts,axis=0)attribute_to_actor(box_actor,big_vectors_3,"evec3")big_angles=np.repeat(np.array(angles,dtype=float),n_verts,axis=0)attribute_to_actor(box_actor,big_angles,"angle")# Start of shader implementationvs_dec=""" in vec3 center; in float scale; in vec3 evec1; in vec3 evec2; in vec3 evec3; in float angle; out vec4 vertexMCVSOutput; out vec3 centerMCVSOutput; out float scaleVSOutput; out mat3 rotationMatrix; out float angleVSOutput; """# Variables assignmentv_assign=""" vertexMCVSOutput = vertexMC; centerMCVSOutput = center; scaleVSOutput = scale; angleVSOutput = angle; """# Rotation matrixrot_matrix=""" mat3 R = mat3(normalize(evec1), normalize(evec2), normalize(evec3)); float a = radians(90); mat3 rot = mat3(cos(a),-sin(a), 0, sin(a), cos(a), 0, 0 , 0, 1); rotationMatrix = transpose(R) * rot; """vs_impl=compose_shader([v_assign,rot_matrix])shader_to_actor(box_actor,"vertex",decl_code=vs_dec,impl_code=vs_impl)fs_vars_dec=""" in vec4 vertexMCVSOutput; in vec3 centerMCVSOutput; in float scaleVSOutput; in mat3 rotationMatrix; in float angleVSOutput; uniform mat4 MCVCMatrix; """# Importing the cone SDFsd_cone=import_fury_shader(os.path.join("sdf","sd_cone.frag"))# Importing the union operation SDFsd_union=import_fury_shader(os.path.join("sdf","sd_union.frag"))# SDF definitionsdf_map=""" float map(in vec3 position) { vec3 p = (position - centerMCVSOutput)/scaleVSOutput *rotationMatrix; float angle = clamp(angleVSOutput, 0, 6.283); vec2 a = vec2(sin(angle), cos(angle)); float h = .5 * a.y; return opUnion(sdCone(p,a,h), sdCone(-p,a,h)) * scaleVSOutput; } """# Importing central differences function for computing surface normalscentral_diffs_normal=import_fury_shader(os.path.join("sdf","central_diffs.frag"))# Importing raymarching functioncast_ray=import_fury_shader(os.path.join("ray_marching","cast_ray.frag"))# Importing the function that generates the ray componentsray_generation=import_fury_shader(os.path.join("ray_marching","gen_ray.frag"))# Importing Blinn-Phong model for lightingblinn_phong_model=import_fury_shader(os.path.join("lighting","blinn_phong_model.frag"))# Full fragment shader declarationfs_dec=compose_shader([fs_vars_dec,sd_cone,sd_union,sdf_map,central_diffs_normal,cast_ray,ray_generation,blinn_phong_model,])shader_to_actor(box_actor,"fragment",decl_code=fs_dec)ray_components=""" vec3 ro; vec3 rd; float t; gen_ray(ro, rd, t); """# Fragment shader output definition# If surface is detected, color is assigned, otherwise, nothing is paintedfrag_output_def=""" if(t < 20) { vec3 pos = ro + t * rd; vec3 normal = centralDiffsNormals(pos, .0001); // Light Direction vec3 ld = normalize(ro - pos); // Light Attenuation float la = dot(ld, normal); vec3 color = blinnPhongIllumModel(la, lightColor0, diffuseColor, specularPower, specularColor, ambientColor); fragOutput0 = vec4(color, opacity); } else { discard; } """# Full fragment shader implementationsdf_frag_impl=compose_shader([ray_components,frag_output_def])shader_to_actor(box_actor,"fragment",impl_code=sdf_frag_impl,block="light")returnbox_actor
[docs]defmain_dir_uncertainty(evals,evecs,signal,sigma,b_matrix):"""Calculate the angle of the cone of uncertainty that represents the perturbation of the main eigenvector of the diffusion tensor matrix. Parameters ---------- evals : ndarray (3, ) or (N, 3) Eigenvalues. evecs : ndarray (3, 3) or (N, 3, 3) Eigenvectors. signal : 3D or 4D ndarray Predicted signal. sigma : ndarray Standard deviation of the noise. b_matrix : array (N, 7) Design matrix for DTI. Returns ------- angles : array Notes ----- The uncertainty calculation is based on first-order matrix perturbation analysis described in [1]_. The idea is to estimate the variance of the main eigenvector which corresponds to the main direction of diffusion, directly from estimated D and its estimated covariance matrix :math:`\\Delta D` (see [2]_, equation 4). The angle :math:`\\Theta` between the perturbed principal eigenvector of D, :math:`\\epsilon_1 + \\Delta\\epsilon_1`, and the estimated eigenvector :math:`\\epsilon_1`, measures the angular deviation of the main fiber direction and can be approximated by: .. math:: \\Theta = tan^{-1}(\\|\\Delta \\ epsilon_1\\|) Giving way to a graphical construct for displaying both the main eigenvector of D and its associated uncertainty, with the so-called "uncertainty cone". References ---------- .. [1] Basser, P. J. (1997). Quantifying errors in fiber direction and diffusion tensor field maps resulting from MR noise. In 5th Scientific Meeting of the ISMRM (Vol. 1740). .. [2] Chang, L. C., Koay, C. G., Pierpaoli, C., & Basser, P. J. (2007). Variance of estimated DTI-derived parameters via first-order perturbation methods. Magnetic Resonance in Medicine: An Official Journal of the International Society for Magnetic Resonance in Medicine, 57(1), 141-149. """angles=np.ones(evecs.shape[0])foriinrange(evecs.shape[0]):sigma_e=np.diag(signal[i]/sigma**2)k=np.dot(np.transpose(b_matrix),sigma_e)sigma_=np.dot(k,b_matrix)dd=np.diag(sigma_)delta_DD=np.array([[dd[0],dd[3],dd[4]],[dd[3],dd[1],dd[5]],[dd[4],dd[5],dd[2]]])# perturbation matrix of tensor Dtry:delta_D=np.linalg.inv(delta_DD)exceptnp.linalg.LinAlgError:delta_D=delta_DDD_=evecseigen_vals=evals[i]e1,e2,e3=np.array(D_[i,:,0]),np.array(D_[i,:,1]),np.array(D_[i,:,2])lambda1,lambda2,lambda3=eigen_vals[0],eigen_vals[1],eigen_vals[2]iflambda1>lambda2andlambda1>lambda3:# The perturbation of the eigenvector associated with the largest# eigenvalue is given bya=np.dot(np.outer(np.dot(e1,delta_D),np.transpose(e2))/(lambda1-lambda2),e2,)b=np.dot(np.outer(np.dot(e1,delta_D),np.transpose(e3))/(lambda1-lambda3),e3,)delta_e1=a+b# The angle \theta between the perturbed principal eigenvector of Dtheta=np.arctan(np.linalg.norm(delta_e1))angles[i]=thetaelse:# If the condition is not satisfied it means that there is not a# predominant diffusion direction, hence the uncertainty will be# higher and a default value close to pi/2 is assignedtheta=1.39626returnangles