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

Feature request: oblique volume slice #2119

Open
henry2004y opened this issue Jul 7, 2022 · 3 comments
Open

Feature request: oblique volume slice #2119

henry2004y opened this issue Jul 7, 2022 · 3 comments

Comments

@henry2004y
Copy link
Contributor

henry2004y commented Jul 7, 2022

It maybe nice to have a similar feature like obliqueslice in MATLAB. I now have an ugly but working script:

using LinearAlgebra
using LazyGrids
using CoordinateTransformations, Rotations, StaticArrays, Interpolations

meshgrid(y,x) = (ndgrid(x,y)[[2,1]]...,)

"""
   obliqueslice(v, point, normal; method=Linear(), fillvalue::Real=0)

Extract an oblique slice from a 3-D volume `v`, by using the `point` on the output slice and
`normal` to the output slice.

# Arguments
- `point::Vector`: vector containing X, Y, Z coordinates of the point in which output slice
passes through.
- `normal:Vector`: a normal vector to the output slice.
- `method`: `Constant()` for nearest neighbor interpolation, `Linear()` for linear
interpolation.
- `fillvalue::Real=0`: Numeric scalar value used to fill pixels in the output slice that are
outside the limits of the volume.

# Output Arguments
- `slice`: interpolated values on the oblique plane.
- `xdata, ydata, zdata`: the X, Y and Z coordinates of the extracted slice in volume `v`.
"""
function obliqueslice(v, point, normal; method=Linear(), fillvalue::Real=0)

   point = Float64.(point)
   sz = size(v)
   xhalf, yhalf, zhalf = ntuple(i -> round(sz[i] / 2 + 1e-12), Val(3))

   # Initial normal vector along z axis= [0., 0., 1.]

   # Unit normal vector= normalize(normal)
   n̂ = normalize(normal)

   # Compute axis of rotation
   if== n̂
      W =else
      W =×normalize!(W)
   end

   # Compute angle of rotation in radians
   angle = acos(ẑ  n̂)

   # Quaternion rotation matrix
   t_quat = quat_matrix(W, -angle)

   # X, Y and Z coordinates of a plane with origin as the center
   (xp, yp), zp = let
      planeSize = 3*maximum(sz)
      xrange = round(-planeSize/2):round(planeSize/2)
      yrange = round(-planeSize/2):round(planeSize/2)
      meshgrid(yrange, xrange), zeros(length(yrange), length(xrange))
   end

   # Rotate coordinates of a plane
   xr, yr, zr = similar(xp), similar(yp), similar(zp)
   for i in eachindex(xp)
      coords = SVector(xp[i], yp[i], 0.0) 
      xr[i], yr[i], zr[i] = t_quat * coords
   end

   # Shift user input point, relative to input volume having origin as center
   point[1] -= yhalf
   point[2] -= xhalf
   point[3] -= zhalf

   # Find the shortest distance between the plane that passes through input point and origin
   D = n̂[1]*point[1] + n̂[2]*point[2] + n̂[3]*point[3]

   # Translate a plane that passes from origin to input point
   xq = @. xr + D*n̂[1] + yhalf
   yq = @. yr + D*n̂[2] + xhalf
   zq = @. zr + D*n̂[3] + zhalf

   # Obtain slice at desired coordinates using interpolation
   nodes = (axes(v,2), axes(v,1), axes(v,3))
   vpermute = permutedims(v, (2,1,3))
   itp = interpolate(nodes, vpermute, Gridded(method))
   etp = extrapolate(itp, fillvalue)

   # Make bounding box around the data in oblique slice
   slicelimit = @. (xq>=1) & (xq<=sz[2]) & (yq>=1) & (yq<=sz[1]) & (zq>=1) & (zq<=sz[3])

   slicerange = find_bounding_box(slicelimit)
   XData = xq[slicerange[1], slicerange[2]]
   YData = yq[slicerange[1], slicerange[2]]
   ZData = zq[slicerange[1], slicerange[2]]

   croppedslice = etp.(XData, YData, ZData)

   croppedslice, XData, YData, ZData
end

"Return Quaternion matrix for a rotation by a given angle `θ` about a fixed axis `w`."
function quat_matrix(w, θ)
   a_x, a_y, a_z = w

   c = cos(θ)
   s = sin(θ)

   t1 = c + a_x^2*(1-c)
   t2 = a_x*a_y*(1-c) - a_z*s
   t3 = a_x*a_z*(1-c) + a_y*s
   t4 = a_y*a_x*(1-c) + a_z*s
   t5 = c + a_y^2*(1-c)
   t6 = a_y*a_z*(1-c)-a_x*s
   t7 = a_z*a_x*(1-c)-a_y*s
   t8 = a_z*a_y*(1-c)+a_x*s
   t9 = c+a_z^2*(1-c)

   r = RotMatrix{3}(t1, t2, t3, t4, t5, t6, t7, t8, t9)
   q = QuatRotation(r)
end

"Find the position of the smallest box containing the 2D `region`."
function find_bounding_box(region)
   edges = Vector{CartesianIndex}(undef, 4)

   for j in axes(region,2), i in axes(region,1)
      if region[i,j]
         edges[1] = CartesianIndex(i, j)
         break
      end
   end

   for i in axes(region,1), j in axes(region,2)
      if region[i,j]
         edges[2] = CartesianIndex(i, j)
         break
      end
   end

   for j in reverse(axes(region,2)), i in reverse(axes(region,1))
      if region[i,j]
         edges[3] = CartesianIndex(i, j)
         break
      end
   end

   for i in reverse(axes(region,1)), j in reverse(axes(region,2))
      if region[i,j]
         edges[4] = CartesianIndex(i, j)
         break
      end
   end

   imin, imax = extrema(x -> x[1], edges)
   jmin, jmax = extrema(x -> x[2], edges)

   imin:imax, jmin:jmax
end

A simple comparison shows identical results with MATLAB defaults:

using PyPlot
point = [4,4,6]
normal = [1,1,1]
v = [i + (j-1)*10 + (k-1)*20  for i in 1:10, j in 1:20, k in 1:20]
slice, XData, YData, ZData = obliqueslice(v, point, normal)

imshow(slice, cmap="gray")

Julia:
obliqueslice_julia

MATLAB:
obliqueslice_matlab


Is this already available somewhere in Makie? If not, please consider adding a similar feature like this! The sample code above is not so elegant, but simply for demonstration purpose.

@glanzkaiser
Copy link

It is a great work, I try it works.

Can we create an oblique slice from any kind of functions in 3 dimension, e.g. f(x,y) = x^2 + y^2 ?

@henry2004y
Copy link
Contributor Author

Can we create an oblique slice from any kind of functions in 3 dimension, e.g. f(x,y) = x^2 + y^2 ?

If you mean f(x,y,z) = x^2 + y^2 + z^2, with this kind of approach we first need to generate a discrete 3d array from the analytical formula. Something like

point = [4.0,4.0,6.0]
normal = [1,1,1]
v = [x^2 + y^2 + z^2  for x in 0:1.0:10, y in 0:1.0:10, z in 0:1.0:10]
slice, XData, YData, ZData = obliqueslice(v, point, normal)

should work? The tricky part may be how to handle the size of oblique slices, and what values to set for outliers.

@glanzkaiser
Copy link

It works, but I still need to learn oblique slice more.

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

No branches or pull requests

2 participants