1010from sklearn .neighbors import NearestNeighbors
1111import numpy as np
1212from prepmd .lib .icp import icp
13-
14- """
15- # via Oriol Manzano Duran
16- def compute_cross_covariace(src_points: np.array, dst_points: np.array, centroid_src_points: np.array, centroid_dst_points: np.array):
17- '''
18- Compute the cross-covariance between two set of point clouds.
19- It will tell us how the coordinates of point in src_points change with changes in coodrinates of points belonging to dst_points
20-
21- Args:
22- src_points(np.array): The coordinates of the first point cloud to correlate.
23- dst_points(np.array): The coordinates of the second points to correlate with the first.
24- centroid_src_points(np.array): Center position of all points in first point cloud, equivalent to get the mean of all the points
25- centroid_dst_points(np.array): Center position of all points in second point cloud, equivalent to get the mean of all the points
26-
27- Return
28- (np.array)(src_points.shape[1]xsrc_points.shape[1]): The cross-covariance matrix between the points
29- '''
30- centered_src_points: np.array = src_points - centroid_src_points
31- centered_dst_points: np.array = dst_points - centroid_dst_points
32- cov: np.array = np.dot(centered_src_points.T, centered_dst_points)
33- return cov
34-
35- def calculate_best_transform(src_points: np.array, dst_points: np.array):
36- '''
37- Calculates the best transform that maps points between two point clouds.
38- The cross-covariance matrix between the point clouds is calculated, then
39- rotations and translations are extracted using singular value decomposition.
40-
41- Args:
42- src_points(np.array): The coordinates of the first point cloud.
43- dst_points(np.array): The coordiantes of the second point cloud.
44-
45- Returns:
46- (np.array)(src_points.shape[1]+1)x(src_points.shape[1]+1) homogeneous transformation matrix that maps src_points on to dst_points
47- (np.array)(src_points.shape[1]xsrc_points.shape[1]) rotation matrix
48- (np.array)(src_points.shape[1]x1) translation vector
49- '''
50- # translate points to their centroids
51- centroid_src_points = np.mean(src_points, axis=0)
52- centroid_dst_points = np.mean(dst_points, axis=0)
53- # compute covariance
54- cov = compute_cross_covariace(src_points, dst_points, centroid_src_points, centroid_dst_points)
55- # rotation matrix
56- U, S, Vt = np.linalg.svd(cov)
57- R = np.dot(Vt.T, U.T)
58- # get number of dimensions
59- m = src_points.shape[1]
60- # special reflection case
61- if np.linalg.det(R) < 0:
62- Vt[m-1,:] *= -1
63- R = np.dot(Vt.T, U.T)
64- # translation
65- t = centroid_dst_points.T - np.dot(R,centroid_src_points.T)
66- # homogeneous transformation
67- T = np.identity(m+1)
68- T[:m, :m] = R
69- T[:m, m] = t
70- return T, R, t
71-
72- def nearest_neighbor(src_points: np.array, dst_points: np.array):
73- '''
74- Find the nearest neighbor between the point clouds
75-
76- Args:
77- src_points(np.array): The coordinates of the first point cloud.
78- dst_points(np.array): The coordiantes of the second point cloud.
79-
80- Return:
81- (np.ndarray): Distances from the src_points to the closest points in dst_points
82- (np.ndarray): Indices of the nearest points in the point clouds.
83- '''
84- neigh = NearestNeighbors(n_neighbors=1)
85- neigh.fit(dst_points)
86- neigh_dist, neigh_ind = neigh.kneighbors(src_points, return_distance=True)
87- return neigh_dist.ravel(), neigh_ind.ravel()
88-
89- def icp(src_points: np.array, dst_points: np.array, init_pose: np.array=None, max_iterations: int=20, tolerance: float=0.001):
90- '''
91- Finds best-fit transform that maps points in src_points on to points in dst_points
92- using Iterative Closest Point method.
93-
94- Input:
95- src_points(np.array): The coordinates of the first point cloud.
96- dst_points(np.array): The coordiantes of the second point cloud.
97- init_pose(np.array): homogeneous transformation
98- max_iterations(int): exit algorithm after max_iterations
99- tolerance(float): convergence criteria
100-
101- Return:
102- (np.array)(src_points.shape[1]+1)x(src_points.shape[1]+1) homogeneous transformation matrix that maps src_points on to dst_points
103- Euclidean distances (errors) of the nearest neighbor
104- number of iterations to converge
105- '''
106- # get number of dimensions
107- m = src_points.shape[1]
108- # make points homogeneous, copy them to maintain the originals
109- src = np.ones((m+1, src_points.shape[0]))
110- dst = np.ones((m+1, dst_points.shape[0]))
111- src[:m,:] = np.copy(src_points.T)
112- dst[:m,:] = np.copy(dst_points.T)
113- # apply the initial pose estimation
114- if init_pose is not None:
115- src = np.dot(init_pose, src)
116- prev_error = 0
117- for i in range(max_iterations):
118- # find the nearest neighbors between the current source and destination points
119- distances, indices = nearest_neighbor(src[:m,:].T, dst[:m,:].T)
120- # compute the transformation between the current source and nearest destination points
121- T,_,_ = calculate_best_transform(src[:m,:].T, dst[:m,indices].T)
122- # update the current source
123- src = np.dot(T, src)
124- # check error
125- mean_error = np.mean(distances)
126- if np.abs(prev_error - mean_error) < tolerance:
127- break
128- prev_error = mean_error
129- # calculate final transformation
130- T,_,_ = calculate_best_transform(src_points, src[:m,:].T)
131- return T, distances, i
132-
133- def icp_pos(src_points, dst_points, init_pose=None, max_iterations: int=20, tolerance: float=0.001):
134- '''
135- Translate position of src_points to match best-fit transform that maps points in src_points on to points in dst_points
136- using Iterative Closest Point method.
137-
138- Input:
139- src_points(np.array): The coordinates of the first point cloud.
140- dst_points(np.array): The coordiantes of the second point cloud.
141- init_pose(np.array): homogeneous transformation
142- max_iterations(int): exit algorithm after max_iterations
143- tolerance(float): convergence criteria
144-
145- Return:
146- (np.array)(src_points.shape): Translated src_points to match best-fit transform to points dst_points
147- '''
148- m = src_points.shape[1]
149- T, distances, i = icp(src_points, dst_points, init_pose, max_iterations, tolerance)
150- src = np.ones((m+1, src_points.shape[0]))
151- src[:m,:] = np.copy(src_points.T)
152- src = np.dot(T, src)
153- return src[:m,:].T
154- """
15513import mrcfile
15614import numpy as np
15715import MDAnalysis as mda
15816
15917
16018def to_point_cloud (mrcdata , voxel , contour_level ):
19+ """
20+ Convert an EM density map to a point cloud.
21+ Args:
22+ mrcdata - ndarray of size resolutionXresolutionXresolution
23+ containg MRC data.
24+ voxel - voxel size, from the mrcfile library, should contain three
25+ member variables, x, y, and z (floats), for the voxel size in those
26+ dimensions.
27+ contour_level: density above which to add a point to the point cloud,
28+ a float.
29+ Returns:
30+ point cloud as an ndarray with three columns (x, y, z) and a row for
31+ each point.
32+ """
16133 point_cloud = []
16234 for x in range (len (mrcdata )):
16335 for y in range (len (mrcdata [x ])):
@@ -167,7 +39,18 @@ def to_point_cloud(mrcdata, voxel, contour_level):
16739 return np .array (point_cloud )
16840
16941def score_pdb_map (pdb , em_map , contour_level ):
170-
42+ """
43+ For a given pdb and em_map, convert them to point clouds and score their
44+ similarity based on the error in an alignment between two point clouds.
45+ Args:
46+ pdb - path to a pdb file, a string
47+ em_map - path to an an em map file for the same structure as that pdb,
48+ a string.
49+ contour_level: density above which to add a point to the point cloud,
50+ a float.
51+ Returns:
52+ the error, a float
53+ """
17154 with mrcfile .open (em_map ) as mrc :
17255 vsize = mrc .voxel_size
17356 point_cloud = to_point_cloud (mrc .data , mrc .voxel_size , 0.01 )
0 commit comments