Switch to side-by-side view

--- a
+++ b/registration/simple geometric model/registration_simple.py
@@ -0,0 +1,190 @@
+"""
+Created by: Rishav Raj and Qie Shang Pua, University of New South Wales
+This program registers a simple geometric model that is segmented from an ultrasound scan to a ground truth model.
+Input: 1. The simple geometric model in STL format
+       2. The csv file for segmented points.
+Output: Two Figures - Registration of STL Model (Ground Truth) with the Segmented Geometric Model
+                    - Error Distribution
+"""
+
+# Modules to import
+import numpy as np
+import math
+import pandas as pd
+from stl.mesh import Mesh
+import open3d
+import scipy.spatial
+import scipy.io
+import copy
+import trimesh
+import matplotlib.pyplot as plt
+
+# ICP Registration
+def draw_registration_result_original_color(source, target, transformation):
+    source_temp = copy.deepcopy(source)
+    source_temp.transform(transformation)
+    source_temp.paint_uniform_color([0.5, 0.5, 0.5])
+    open3d.visualization.draw_geometries([source_temp, target])
+
+def registration(raw):
+
+    correct = np.array(raw)
+
+    # To shift up the points, 11 is a threshold to determine whether to shift up or not
+    numCols = len(raw[2])
+    to_shift_up = [0] * numCols
+    for col in range(numCols):
+        if raw[2][col] < 11:
+            to_shift_up[col] = 30
+        else:
+            to_shift_up[col] = 0
+
+    raw[0] = raw[0] + to_shift_up
+
+    # Axes Transformation, values are selected through trial and error as per MATLAB code
+    x_shift = -43.5;
+    y_shift = 95;
+    z_shift = 148;
+
+    correct[0] = raw[1] * 90.0 / 569.0 + x_shift
+    correct[1] = (raw[0] * (-88) / 569) + y_shift
+    correct[2] = raw[2] * (-5.01) + z_shift
+
+    np.set_printoptions(precision=3)  # Prints array in 3 decimal places
+
+    # Rotation
+    skew_value = 0.08
+    skew_y = np.array([[1, 0, skew_value], [0, 1, 0], [0, 0, 1]])
+
+    correct = np.dot(skew_y, correct)
+
+    tz = np.deg2rad(1)
+    ty = np.deg2rad(-0.5)
+
+    Rz = [[math.cos(tz), -1 * math.sin(tz), 0], [math.sin(tz), math.cos(tz), 0], [0, 0, 1]]
+    Ry = [[math.cos(ty), 0, math.sin(ty)], [0, 1, 0], [-1 * math.sin(ty), 0, math.cos(ty)]]
+
+    correct = np.dot(Rz, correct)
+    correct = np.dot(Ry, correct)
+
+    # Get STL (ground truth) model
+    path = "ground_truth_in_stl_form.stl"
+    stl_mesh = Mesh.from_file(path)
+    stl_points = np.around(np.unique(stl_mesh.vectors.reshape([stl_mesh.vectors.size // 3, 3]), axis=0), 2)
+
+    # Displays the model, eliminate outlier points that are too far
+    # Range values for each axis as selected per MATLAB code
+    minz = -200
+    maxz = 400
+
+    miny = -100
+    maxy = 400
+
+    minx = -100
+    maxx = 500
+
+    # p represents point cloud
+    px = np.bitwise_and(stl_points[0, :] > minx, stl_points[0, :] < maxx)
+    py = np.bitwise_and(stl_points[1, :] > miny, stl_points[1, :] < maxy)
+    pz = np.bitwise_and(stl_points[2, :] > minz, stl_points[2, :] < maxz)
+    p = np.bitwise_and(px, py, pz)
+
+    # Transpose segmented points to be in dimensions (number of rows, 3)
+    segmented_points = np.transpose(correct)
+
+    num_rows = np.ma.size(segmented_points, 0)
+    stl_points = np.transpose(stl_points)
+
+    # Array to store the error for colour map
+    error = np.ones((num_rows, 1))
+
+    # For loop determines the colour map or the error when mapping segmented points to stl
+    for i in range(num_rows):
+        point = segmented_points[i, :]  # extracting each row from segmented points
+        point_p2 = stl_points[:, i]
+
+        # euclidean distance between the segmented points and the stl model
+        dist = scipy.spatial.distance.euclidean(point_p2, point)
+        dist = dist / 100
+
+        if (dist > 10):
+            error[i] = 0 # if the error is too big then it won't display on the colour map because
+                         # it is a point of less interest
+        elif np.mean(stl_points[p, 1]) < point[1]:
+            error[i] = np.mean(dist)
+        else:
+            error[i] = np.mean(dist)
+
+    # Outputs maximum and minimum error
+    highest = max(error)
+    lowest = min(error)
+    print(f"The maximum error is {highest}mm")
+    print(f"The minimum error is {lowest}mm")
+
+    # Use a temp variable to store the error array of each point
+    temp = error
+
+    # Convert error array into rgb and store in colours array
+    error = error * 255 / highest
+    zeroes = np.zeros((5640, 2))
+    colours = np.append(zeroes, error, axis=1)
+
+    # Displays colour map depending on error, the threshold are selected using trial and error
+    for i in range(len(temp)):
+        if temp[i] > 1.5:
+            colours[i] = [255, 0, 0]  # green colour means less accurate
+            continue
+
+        if temp[i] < 1.1:
+            colours[i] = [0, 255, 0]  # blue colour means more accurate
+            continue
+
+    # Display the STL Point Cloud and Segmented Points
+    pcd_image = open3d.geometry.PointCloud()
+    pcd_image.points = open3d.utility.Vector3dVector(segmented_points)
+    pcd_image.colors = open3d.utility.Vector3dVector(colours)
+
+    # Writing segmented points cloud to an open3d image
+    open3d.io.write_point_cloud("point_cloud_segmented.ply", pcd_image)
+    # Read the point cloud
+    pcd_image = open3d.io.read_point_cloud("point_cloud_segmented.ply")
+
+    # Load original STL file
+    mesh1 = trimesh.load('ground_truth_in_stl_form.stl')
+    # Convert stl file to ply. The STl file is the ground truth model
+    mesh2 = mesh1.copy()
+    mesh2.export('ground_truth.ply')
+
+    # Set parameters for icp registration
+    source = open3d.io.read_point_cloud("ground_truth.ply")
+    target = open3d.io.read_point_cloud("point_cloud_segmented.ply")
+    threshold = 0.005
+
+    # Transformation matrix - Identity Matrix
+    initial_trans = np.identity(4)
+
+    # Evaluate registration performance
+    evaluation = open3d.registration.evaluate_registration(source, target,threshold, initial_trans)
+
+    # Obtain a registered model
+    reg_p2p = open3d.registration.registration_icp(source, target, threshold, initial_trans)
+
+    # Plot registered model
+    draw_registration_result_original_color(source, target, reg_p2p.transformation)
+
+    # Plot the error distribution
+    plt.hist(temp, bins =50)
+    plt.gca().set(title='Error/Distance', ylabel='Frequency');
+    plt.show()
+
+
+# Main file
+def main():
+
+    # Retrieve segmented_points data by running registration.m on MATLAB
+    # change path if located at another file
+    segmented_points_path = "raw_segmented_points.csv"
+    df = pd.read_csv(segmented_points_path, header=None)
+    registration(np.array(df))  # Make it a np array - easier
+
+main()