I'm trying to align a rectangular prism contained in a 3D numpy array to the principal axes of the array. Specifically, I need to accomplish this alignment by rotating the full array, as opposed to extracting the coordinates of the object and then rotating these points (this is because a perfect segmentation of the object from the background is unrealistic for my application).
I have a method that works under very specific circumstances, but it seems surprisingly sensitive given how general the approach is. In short, my method only works for very specific object orientations.
I'm looking for guidance on either (a) what's wrong with my approach or (b) a different approach that accomplishes the same objectives.
My approach (inspired by this post and this post):
from scipy.ndimage import affine_transform
from skimage import measure
def find_orientation(prism):
# Calculate second-order moments
M = measure.moments_central(prism, order=2)
# Constructing the covariance matrix from the central moments
cov_matrix = np.array(
[
[M[2, 0, 0], M[1, 1, 0], M[1, 0, 1]],
[M[1, 1, 0], M[0, 2, 0], M[0, 1, 1]],
[M[1, 0, 1], M[0, 1, 1], M[0, 0, 2]],
]
)
# Compute eigenvectors from the covariance matrix
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
return eigenvectors
def rotate_to_align(prism):
# Calculate orientation matrix using moments method
orientation_matrix = find_orientation(prism)
# Rotate the prism using the orientation matrix
aligned_prism = affine_transform(prism, orientation_matrix.T, order=3)
return aligned_prism
aligned_array = rotate_to_align(misaligned_array)
Please see this notebook for the full details (too long for SO): https://github.com/petermattia/3d-alignment/blob/main/prism_alignment.ipynb
Related but distinct questions/resources:
- How to align principal axes of 3D density map in numpy with Cartesian axes? Very similar question, but no sufficient answer
- Transforming and Resampling a 3D volume with numpy/scipy This approach might work in principle but it's pretty clunky (lots of transformations between indices/coordinates and the volume)
- https://github.com/ljmartin/align/blob/main/0.2%20aligning%20principal%20moments%20of%20inertia.ipynb This notebook is great, but crucially, it aligns the points in a point cloud instead of aligning the full volume. For my application, aligning the full volume instead of the points is important (see discussion in intro paragraph). However, this approach could work if we are able to use the rotation matrices from the point cloud approach for the full array.
Thank you!
In your notebook, you simply rotate around the origin. For severe rotations, that can move your content our of the domain you look at.
In your transformation, introduce translations from and to the center.
Ok so you have a volume, containing a solid box, oriented in some way. You were looking for the axes of that object.
Calculating axes from moments appears to be unstable. The longest axis through a box may be a diagonal. That does not appear to be what you want.
I propose working with the surface normals of each face of the object.
You can get those from clustering the gradients of the volume. That'll give you six stable clusters of surface normals. I've chosen Sobel because the included lowpass is more stable than straight
np.gradient().Pair up opposite vectors. Sort them sensibly to reduce surprising rotations. Then apply some more linear algebra to ensure you actually have vectors that are normal to each other. That need not be the case if the box happens to be a little sheared or just from errors due to finite arithmetic.
This is not perfect. Interpolation/sampling and finite arithmetic are issues.
The clustering of gradients can be handled in various ways. One could attempt to discard outliers and recalculate the centroids. One could attempt to normalize the vectors, once background gradient (zero) was identified and discarded.
One could even perform (part of) Marching Cubes and fit planes! I think that's enough for tonight.