Switch to unified view

a b/registration/spine model/registration.py
1
"""
2
Created by: Rishav Raj and Qie Shang Pua, University of New South Wales
3
This program registers a segmented spine model from an ultrasound scan to a ground truth spine model.
4
Input: 1. The spine model in STL format
5
       2. The csv file for segmented points.
6
Output: Two Figures - Registration of STL Model (Ground Truth) with the Segmented Spine Model
7
                    - Error Distribution
8
9
Note: This program is only valid for phantom 3 scan 2
10
"""
11
12
# Modules to import
13
import numpy as np
14
import math
15
import pandas as pd
16
from stl.mesh import Mesh
17
import open3d
18
import scipy.spatial
19
import scipy.io
20
import copy
21
import trimesh
22
import matplotlib.pyplot as plt
23
24
25
# ICP registration
26
def draw_registration_result_original_color(source, target, transformation):
27
    source_temp = copy.deepcopy(source)
28
    source_temp.transform(transformation)
29
    source_temp.paint_uniform_color([0.5, 0.5, 0.5])
30
    open3d.visualization.draw_geometries([source_temp, target])
31
32
def registration(raw):
33
34
    correct = np.array(raw)
35
36
    # To shift up the points, 11 is a threshold to determine whether to shift up or not
37
    numCols = len(raw[2])
38
    to_shift_up = [0] * numCols
39
    for col in range(numCols):
40
        if raw[2][col] < 11:
41
            to_shift_up[col] = 30
42
        else:
43
            to_shift_up[col] = 0
44
45
    raw[0] = raw[0] + to_shift_up
46
47
    # Axes Transformation, values are selected through trial and error as per MATLAB code
48
    x_shift = -43.5;
49
    y_shift = 95;
50
    z_shift = 148;
51
52
    correct[0] = raw[1] * 90.0 / 569.0 + x_shift
53
    correct[1] = (raw[0] * (-88) / 569) + y_shift
54
    correct[2] = raw[2] * (-5.01) + z_shift
55
56
    np.set_printoptions(precision=3)  # Prints array in 3 decimal places
57
58
    # Rotation
59
    skew_value = 0.08
60
    skew_y = np.array([[1, 0, skew_value], [0, 1, 0], [0, 0, 1]])
61
62
    correct = np.dot(skew_y, correct)
63
64
    tz = np.deg2rad(1)
65
    ty = np.deg2rad(-0.5)
66
67
    Rz = [[math.cos(tz), -1 * math.sin(tz), 0], [math.sin(tz), math.cos(tz), 0], [0, 0, 1]]
68
    Ry = [[math.cos(ty), 0, math.sin(ty)], [0, 1, 0], [-1 * math.sin(ty), 0, math.cos(ty)]]
69
70
    correct = np.dot(Rz, correct)
71
    correct = np.dot(Ry, correct)
72
73
    # Get STL model
74
    path = "/Users/puaqieshang/Desktop/Taste of Research/everything/models/Segmentation_bone.stl"
75
    stl_mesh = Mesh.from_file(path)
76
    points = np.around(np.unique(stl_mesh.vectors.reshape([stl_mesh.vectors.size // 3, 3]), axis=0), 2)
77
78
    # Displays the model, eliminate outlier points that are too far
79
    # Range values for each axis as selected per MATLAB code
80
    minz = -200
81
    maxz = 400
82
83
    miny = -100
84
    maxy = 400
85
86
    minx = -100
87
    maxx = 500
88
89
    # p represents point cloud
90
    px = np.bitwise_and(points[0, :] > minx, points[0, :] < maxx)
91
    py = np.bitwise_and(points[1, :] > miny, points[1, :] < maxy)
92
    pz = np.bitwise_and(points[2, :] > minz, points[2, :] < maxz)
93
    p = np.bitwise_and(px, py, pz)
94
95
    # Transpose segmented points to be in dimensions (number of rows, 3)
96
    segmented_points = np.transpose(correct)
97
98
    num_rows = np.ma.size(segmented_points, 0)
99
    points = np.transpose(points)
100
101
    # Array to store the error for colour map
102
    error = np.ones((num_rows, 1))
103
104
    # For loop determines the colour map or the error when mapping segmented points to stl
105
    for i in range(num_rows):  # 39372
106
        point = segmented_points[i, :]  # extracting each row from segmented points
107
        point_p2 = points[:, i]
108
109
        # euclidean distance between the segmented points and the stl model
110
        dist = scipy.spatial.distance.euclidean(point_p2, point)
111
        dist = dist / 100
112
113
        if (dist > 10):
114
            error[i] = 0; # if the error is too big then it won't display on the colour map because
115
                          # it is a point of less interest
116
        elif np.mean(points[p, 1]) < point[1]:
117
            error[i] = np.mean(dist)
118
        else:
119
            error[i] = np.mean(dist)
120
121
    # ignore negative errors
122
    for i in range(num_rows):
123
        if (error[i] < 0):
124
            error[i] = 0
125
126
    # Outputs maximum and minimum error
127
    highest = max(error)
128
    lowest = min(error)
129
    print(f"The maximum error is {highest}mm")
130
    print(f"The minimum error is {lowest}mm")
131
132
    # Use a temp variable to store the error array of each point
133
    temp = error;
134
135
    # Convert error array into rgb and store in colours array
136
    error = error * 255 / highest
137
    zeroes = np.zeros((39372, 2))
138
    colours = np.append(zeroes, error, axis=1)
139
140
    # Displays colour map depending on error, the threshold are selected using trial and error
141
    for i in range(len(temp)):
142
        if temp[i] > 1.7:
143
            colours[i] = [0, 255, 0] # green colour means more error
144
            continue
145
146
        if temp[i] < 0.5:
147
            colours[i] = [0, 0, 255] # blue colour means less error
148
            continue
149
150
151
    # Display the STL Point Cloud and Segmented Points
152
    pcd_image = open3d.geometry.PointCloud()
153
    pcd_image.points = open3d.utility.Vector3dVector(segmented_points)
154
    pcd_image.colors = open3d.utility.Vector3dVector(colours)
155
156
    # Writing segmented points cloud to an open3d image
157
    open3d.io.write_point_cloud("point_cloud_segmented.ply", pcd_image)
158
    # Read the point cloud
159
    pcd_image = open3d.io.read_point_cloud("point_cloud_segmented.ply")
160
161
    # Load original STL file
162
    mesh1 = trimesh.load('/Users/puaqieshang/Desktop/Taste of Research/everything/models/Segmentation_bone.stl')
163
    # Convert stl file to ply. The STl file is the ground truth model
164
    mesh2 = mesh1.copy()
165
    mesh2.export('ground_truth.ply')
166
167
    # Set parameters for icp registration
168
    source = open3d.io.read_point_cloud("ground_truth.ply")
169
    target = open3d.io.read_point_cloud("point_cloud_segmented.ply")
170
    threshold = 0.005
171
172
    # Transformation matrix - Identity Matrix
173
    trans_init = np.identity(4)
174
175
    # Evaluate registration performance
176
    evaluation = open3d.registration.evaluate_registration(source, target,threshold, trans_init)
177
178
    # Obtain a registered model
179
    reg_p2p = open3d.registration.registration_icp(source, target, threshold,
180
                                               trans_init,open3d.registration.TransformationEstimationPointToPoint())
181
182
    # Plot registered model
183
    draw_registration_result_original_color(source, target, reg_p2p.transformation)
184
185
    # Plot the error distribution
186
    plt.hist(temp, bins=50)
187
    plt.gca().set(title='Error/Distance', ylabel='Frequency');
188
    plt.show()
189
190
# Main file
191
def main():
192
193
    # Retrieve segmented_points data by running registration.m on MATLAB
194
    # change path if located at another file
195
    segmented_points_path = "/Users/puaqieshang/Desktop/raw_segmented_points.csv"
196
    df = pd.read_csv(segmented_points_path, header=None)
197
    registration(np.array(df))
198
199
main()