How to get correct pixel size in 2D after cut 3D voxel using obliqueslice
54 views (last 30 days)
Show older comments
Hi,
I have a 3D voxel created from a CT scanner. I cut it with obliqueslice to get the surface that I would like to calculate. As the cut was not orthogonal cut I guess my pixel size in space changed. There is a way to correct it? I think I should calculate the angle between each axis and apply it to each pixels (x, y,z)? Is it correct way to do it? Thank you for any respons.
0 Comments
Answers (4)
Rik
on 18 Dec 2024 at 9:26
Edited: Rik
on 18 Dec 2024 at 9:52
If you use the second output format:
this function already returns the coordinates for each pixel in your oblique slice.
To compute the pixel size, you can simply take the difference and multiply that by the native voxel size. Pay attention to the difference between slice increment and slice thickness.
load mri,V = squeeze(D);
point = [73 50 15.5];
normal = [0 15 20];
[B,x,y,z] = obliqueslice(V,point,normal);
imshow(B)
corners=[...
x(1,1) x(2,2);
y(1,1) y(2,2);
z(1,1) z(2,2)];
disp(diff(corners,[],2))
For this example we don't know the native voxel size, but this is the distance traversed going from one corner to the other of a single pixel in the slice.
Just like every other parallelogram you can calculate the area with the cross product of two side vectors:
A = [x(2,1)-x(1,1), y(2,1)-y(1,1), z(2,1)-z(1,1)];
B = [x(1,2)-x(1,1), y(1,2)-y(1,1), z(1,2)-z(1,1)];
disp(A),disp(B)
% Here you need to multiply by the size of the voxels before calling cross().
area = double(norm(cross(A,B)))
4 Comments
Rik
on 19 Dec 2024 at 9:33
What is there to think about?
SliceInterval = 0.7;%mm
FOV = 350;%mm
VoxelDimensions = [FOV/512 FOV/512 SliceInterval];
area = double(norm(cross(A.*VoxelDimensions,B.*VoxelDimensions)))
And you're done.
Matt J
on 18 Dec 2024 at 22:26
Edited: Matt J
on 19 Dec 2024 at 2:45
IMHO obliqueslice is a bit of a crude tool for slice sampling. One of the many things you can do with this FEX package,
is set up a planar grid XYZ of sample points at which to interpolate the image values. However, unlike obliqueslice, this gives you direct control not over the spacing between the sample points (i.e. the pixel size in your slice image), but also the orientation of the planar grid relative to the volume.
load mri,V = squeeze(single(D));
V=squeeze(single(D));
V=imresize3( V , round(size(V).*[1,1,2.5]) ); %make isotropic
[nx,ny,nz]=size(V);
F=griddedInterpolant(V,'linear','none');
origin=[nx,ny,nz]/2-0.5; %Origin
basis1=[0,0,nz]; %Plane basis vector 1
basis2=[nx,ny,0]; %Plane basis vector 2
pixelLength=0.8;
t1=(0:ceil(norm(basis1))/pixelLength)/norm(basis1); t1=t1-max(t1)/2; %step size 1
t2=(0:ceil(norm(basis2))/pixelLength)/norm(basis2); t2=t2-max(t2)/2; %step size 2
XYZ=planarFit.xyzsim(origin,basis1,basis2,t1,t2)';%Oblique sample points
Vs = F( XYZ ); %Oblique image values
Vs=reshape(Vs,numel(t1),numel(t2));
imshow(Vs,[0,max(Vs(:))]); axis xy
Catalytic
on 19 Dec 2024 at 18:44
Edited: Catalytic
on 19 Dec 2024 at 19:03
The pixels sizes in the slice images are always 1, as you can see below. The dimensions of the slice are a harder thing to predict as you also see below. The intersection of the slice plane with the image cube forms a polygon-shaped region. To form the final image, obliqueslice must pixelize either a sub-rectangle inside the polygon or a super-rectangle enclosing the polygon. I don't think there's any easy way to determine how it is done, or to predict the final image dimensions even if we could.
for i=1:5
normal=normalize(rand(1,3),'norm'); %random slice normal
[Slice,x,y,z] = obliqueslice(rand(300,300,300),[151,151,151],normal);
sliceDimensions = size(Slice)
pixelHeight=norm([x(2,1)-x(1,1), y(2,1)-y(1,1), z(2,1)-z(1,1)]) ,
pixelWidth =norm([x(1,2)-x(1,1), y(1,2)-y(1,1), z(1,2)-z(1,1)]) ,
disp ' '
end
0 Comments
William Rose
20 minutes ago
Edited: William Rose
16 minutes ago
[edit: fix spelling errors, no changes to code.]
@Catalytic is right that the pixel size in the new, oblique slice is 1, when measured in theunits of the pixels in the original image (i.e. assuming each of the original pizels has size 1x1x1).
obliqueslice(V,point,normal)
It helps me to think of an oblique slice as a setting up a new reference frame that is oriented obliquely relative to the original frame. The original frame is Xo,Yo,Zo. The new frame is Xn,Yn,Zn. Zn points along vector "normal". The new image is in the Xn,Yn plane. Let Zn=0 at point "point". Then all the points in the oblique slice have Zn=0. Now ccreate a grid of points with spacing 1x1 in the Xn,Zn plane. Those are the pixel centers in the new image. We can transform those back to their locaiton in the original volume. The pixel centers in the oblique slice will not exactly coincide with voxel centers in the original slice, so at each pixel center, obliqueslice() takes a weighted average of the image intensities at the nearest voxel centers.
Since the pixels in the oblique slice have size 1x1, and the plane is tilted relative to the original volume, the oblique plane is likely to have more pixels that the original volume, at least if it passes through the center of the volume. And this is in fact what you will observe.
One thing that is interesting about the change of cooridnates way of thinking about obliqueslice() is that the direction of Xn and Yn is not specified by vector "normal". So obliqueslice() decides how to orient Xn and Yn. Sometimes I like obliqueslice()'s choice for Xn and Yn, and sometimes I do not.
I have attached a script, image3dslice.m, to illustrate some apsects of how obliqueslice() works.
image3dslice.m creates a mostly black volume with array dimensions 81x71x91, i.e 81 pixels along axis Yo, 71 along Xo, and 91 along Zo. There is a white plane at every 10th pixel, i.e. at 1, 11, 21,..., in all 3 directions.
The first part of image3dslice.m takes slices parallel to the original axes, and plots them. When normal=[0,0,1], i.e. normal points along the Zo axis, then the slice is in the X-Y plane. Point "point" is chosen to be in a black area, so the resulting slice is mostly black with size 81 rows x 71 columns, and a grid of white lines up/down and across, at every 10th pixel. See left panel in figure below. I call imshow() with the "xy" option, which puts the X,Y origin at the bottom left. When normal=[0,1,0], i.e normal points along Yo, then the slice is parallel to the X-Z plane, and the image slice has size 91 rows by 71 columns. It is mostly black, with white lines at every 10th pixel. See center panel in figure below. When normal =[1,0,0], the normal ponts along Xo, and the image slice is in the Y-Z plane, and it has th expected dimensions - see right panel in image below.
The second part of image3dslice.m take slices at oblique angles. For simplicity, the normal vecotrr in the three examples below is roated about just one of the orignal zaxes. this means the normal vector has one of its coordinates=0 in each of the three exanmples shown. The normal vector is rotated by 18.4 degrees. I chose this angle because the tangent of 18.4 degrees is 1/3, so the tilted plane goes 1 original voxel up for every 3 voxels over, and we will see this pattern in the results.
Note that the tilted planes have more pixels, in some directions, than the original volume dimensions. The left panel below shows the slice when normal=[ 0,.316,.949]. This normal vector is close to the Zo-axis, but it is tilted 18.4 degress toward the Yo-axis. The plane normal to "normal" has x-values identical to the original x values (because the x-component of normal=0), and the y-values are close to, but not identical to, the original y values. That is why I labeled the axes "X" and "Y*". There are 84 rows in the slice, compared to 81 rows in the oprthogonal slice on the left n the previous figure. The extra pixels in the oblique slice reflect the fact that the tilted plane inside the volume is longer than the volume is tall.
The right hand panel in the image blow shows a case where opbliqueslice selected Xn and Yn axes in what I think is an unhelpful way. Since normal=[.316,.949,0], one could have chosen to let Yn (columns of the oblique slice) point in direction [0,0,1] (in the originial reference frame), and let Xn (rows of the slice) point in direction [.949,-.316,0]. Then the rows of the slice image would correspond to Zo in the volume, and the right hand slice below would look nice and rectangular, instead of being tilited.
There is a lot more one could say about the image slices above, including to evidence for 3 to 1 ratios, etc.
Matlb's obliqueslice() does not work with color volume images. Matlab's sliceViewer does display color volume images, but it does not allow oblique slicing. If you make a volume image like the one I made above, but with red, green, and blue planes in the different directions, instead of all white planes, then the oblique slices would be more revealing. With a small effort, you could write a function to apply obliqueslice() to the red, green, and blue planes of an image, then combine the slics to make a color oblique slice. So I did.
I'm not sure why the x-axis range in the right hand panels of the two figures above are so large. Needs work.
0 Comments
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!