Skip to content

Commit 5b86b8e

Browse files
committed
Point Tracking for Silicone, w/o Specular Highlight Classification
1 parent fdb5023 commit 5b86b8e

13 files changed

Lines changed: 363 additions & 68 deletions

source/Correspondences.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ def initialize(laser, camera, maxima_image, minInterval, maxInterval):
1616
laserRay = laser.ray(y, x)
1717

1818
mask = helper.generateMask(np.zeros_like(maxima_image, dtype=np.uint8), camera.intrinsic(), laser.origin(), laserRay, minInterval, maxInterval, 2, 2)
19+
20+
vis_mask = np.clip(mask.astype(float) + maxima.astype(float) * 255.0, 0, 255).astype(np.uint8)
21+
cv2.imshow("Masks", vis_mask)
22+
cv2.waitKey(1)
23+
24+
1925
masked_maxima = maxima * mask
2026
masked_points = masked_maxima.nonzero()
2127

source/GUI/MainMenuWidget.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,15 @@ def __init__(self, viewer_palette, parent=None):
3434
"Segmentation",
3535
[
3636
("Koc et al", "checkbox", False),
37-
("Neural Segmentation", "checkbox", True),
38-
("Silicone Segmentation", "checkbox", False),
37+
("Neural Segmentation", "checkbox", False),
38+
("Silicone Segmentation", "checkbox", True),
39+
],
40+
)
41+
self.addSubMenu(
42+
"Point Tracking",
43+
[
44+
("Invivo", "checkbox", False),
45+
("Silicone", "checkbox", True),
3946
],
4047
)
4148
self.addSubMenu(
@@ -56,6 +63,12 @@ def __init__(self, viewer_palette, parent=None):
5663
("Iterations", "field", 2),
5764
("Weight", "field", 10000)],
5865
)
66+
self.addSubMenu(
67+
"CUDA",
68+
[
69+
("Use", "checkbox", False)
70+
],
71+
)
5972
self.addSubMenu(
6073
"Least Squares Optimization",
6174
[("Iterations", "field", 10), ("Learning Rate", "field", 0.1)],

source/GUI/zoomable.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from PyQt5.QtCore import Qt
22
from PyQt5.QtGui import QImage, QPixmap, QTransform
3-
from PyQt5.QtWidgets import QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QMenu
3+
from PyQt5.QtWidgets import (QGraphicsPixmapItem, QGraphicsScene,
4+
QGraphicsView, QMenu)
45

56

67
class Zoomable(QGraphicsView):
@@ -53,6 +54,7 @@ def __init__(self, parent=None):
5354

5455
scene = QGraphicsScene(self)
5556
self.setScene(scene)
57+
self._flipped = False
5658

5759
def wheelEvent(self, event) -> None:
5860
"""
@@ -131,7 +133,11 @@ def update_view(self) -> None:
131133
"""
132134
Updates the view transformation based on the current zoom level.
133135
"""
134-
self.setTransform(QTransform().scale(self._zoom, self._zoom))
136+
137+
if self._flipped:
138+
self.setTransform(QTransform().rotate(90.0).scale(self._zoom, self._zoom))
139+
else:
140+
self.setTransform(QTransform().scale(self._zoom, self._zoom))
135141

136142
def set_image(self, image: QImage) -> None:
137143
"""

source/GUI/zoomableVideo.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import zoomable
44
from PyQt5 import QtCore
5-
from PyQt5.QtGui import QImage
5+
from PyQt5.QtGui import QImage, QTransform
66
from PyQt5.QtWidgets import QMenu
77

88

@@ -42,6 +42,13 @@ def add_video(self, video: List[QImage]) -> None:
4242
self._image_width = video[0].width()
4343
self._image_height = video[0].height()
4444

45+
if self._image_width > self._image_height:
46+
self._flipped = True
47+
else:
48+
self._flipped = False
49+
50+
self.fit_view()
51+
4552
def contextMenuEvent(self, event) -> None:
4653
"""
4754
Opens a context menu with options for zooming in and out.

source/SiliconeSurfaceReconstruction.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def getNeighbours(faces, numPoints):
150150
# Given
151151
# 3D Points of type NumFrames X NumPoints x 3
152152
# Calibrated laser object
153-
def controlPointBasedARAP(triangulatedPoints, camera, segmentator, zSubdivisions=5, xSubdivisions=3, r_zero = 1.0, T = 2.5, psi = 0.0, ARAP_iterations=2, ARAP_weight = 10000.0):
153+
def controlPointBasedARAP(triangulatedPoints, camera, segmentator, zSubdivisions=5, xSubdivisions=3, r_zero = 1.0, T = 2.5, psi = 0.0, ARAP_iterations=2, ARAP_weight = 10000.0, flip_y = False):
154154
left_M5_list = []
155155
right_M5_list = []
156156
left_points_list = []
@@ -278,7 +278,8 @@ def controlPointBasedARAP(triangulatedPoints, camera, segmentator, zSubdivisions
278278
glottalOutlinePoints[:, 1] = 0.0
279279

280280
# Move vocal folds down a bit
281-
alignedPoints[:, 1] = -alignedPoints[:, 1]
281+
if flip_y:
282+
alignedPoints[:, 1] = -alignedPoints[:, 1]
282283
alignedPoints -= np.array([[0.0, alignedPoints[:, 1].min()/2.0, 0.0]])
283284

284285
# Split everything into left and right vocal fold
@@ -324,8 +325,8 @@ def controlPointBasedARAP(triangulatedPoints, camera, segmentator, zSubdivisions
324325
#left_anchors = generateARAPAnchors(M5_Left, leftPoints)
325326
#right_anchors = generateARAPAnchors(M5_Right, rightPoints)
326327

327-
left_anchors, constrained_vertices_left = SurfaceReconstruction.generateARAPAnchors(M5_Left, leftPoints, num_2d, glottalOutlinePoints[np.where(glottalOutlinePoints[:, 0] < 0)], isLeft=True)
328-
right_anchors, constrained_vertices_right = SurfaceReconstruction.generateARAPAnchors(M5_Right, rightPoints, num_2d, glottalOutlinePoints[np.where(glottalOutlinePoints[:, 0] >= 0)], isLeft=False)
328+
left_anchors, constrained_vertices_left = SurfaceReconstruction.generateARAPAnchors(M5_Left, leftPoints, num_2d, glottalOutlinePoints[np.where(glottalOutlinePoints[:, 0] < 0)], x_subdivisions=xSubdivisions, isLeft=True)
329+
right_anchors, constrained_vertices_right = SurfaceReconstruction.generateARAPAnchors(M5_Right, rightPoints, num_2d, glottalOutlinePoints[np.where(glottalOutlinePoints[:, 0] >= 0)], x_subdivisions=xSubdivisions, isLeft=False)
329330

330331
constrained_vertices_list_left.append(constrained_vertices_left.tolist())
331332
constrained_vertices_list_right.append(constrained_vertices_right.tolist())

source/SurfaceReconstruction.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def rotateZ(mat, degree, deg=True):
105105
return np.matmul(mat, rotation_matrix)
106106

107107

108-
def generateARAPAnchors(vertices, points, nPointsU, glottalOutlinePoints, isLeft=True):
108+
def generateARAPAnchors(vertices, points, nPointsU, glottalOutlinePoints, x_subdivisions = 2, isLeft=True):
109109
# We want to first set the lower points of our Control Point Set to be fixed
110110
lower_indices = np.where(vertices[:, 1] == vertices[vertices[:, 1].argmin(), 1])
111111
lower_fixed = vertices[lower_indices]
@@ -154,17 +154,19 @@ def generateARAPAnchors(vertices, points, nPointsU, glottalOutlinePoints, isLeft
154154
# Fit glottal out and midline
155155
if glottalOutlinePoints.size != 0:
156156
for i in range(nPointsV):
157-
for j in range(3):
158-
controlPointIndex = 4 + j + i*nPointsU
157+
for j in range(x_subdivisions + 2):
158+
controlPointIndex = (x_subdivisions + 2) + j + i*nPointsU
159159
controlPoint = vertices[controlPointIndex]
160160

161161
glottalPoints = glottalOutlinePoints + np.array([[0.0, controlPoint[1], 0.0]])
162162
nnIndex, dist = helper.findNearestNeighbour(controlPoint, glottalPoints)
163163

164+
'''
164165
if dist > 3.0:
165166
anchors[controlPointIndex] = controlPoint.tolist()
166167
continue
167-
168+
'''
169+
168170
direction = -glottalPoints[nnIndex] + controlPoint
169171
direction = direction / np.linalg.norm(direction)
170172

source/Viewer.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,10 @@ def __init__(self):
177177
self.timer_thread.start()
178178
self.image_timer_thread.start()
179179

180-
path = "/media/nu94waro/Windows_C/save/datasets/HLEDataset/dataset"
181180

181+
# (UN)COMMENT THIS TO LOAD HLE DATA AFTER STARTING
182182
'''
183-
self.loadData(
184-
"assets/camera_calibration.json",
185-
"assets/laser_calibration.json",
186-
"assets/example_vid.avi",
187-
)'''
183+
path = "/media/nu94waro/Windows_C/save/datasets/HLEDataset/dataset"
188184
189185
self.menu_widget.widget().ocs_widget.camera_calib_path = os.path.join(path, "camera_calibration.json")
190186
self.menu_widget.widget().ocs_widget.laser_calib_path = os.path.join(path, "laser_calibration.json")
@@ -201,7 +197,30 @@ def __init__(self):
201197
point_tracking.PointTracker(),
202198
correspondence_estimation.BruteForceEstimator(10, 30, 40, 100),
203199
surface_reconstruction.SurfaceReconstructor())
200+
'''
201+
202+
# (UN)COMMENT THIS TO LOAD SILICONE DATA AFTER STARTING
203+
204+
path = "assets"
204205

206+
self.menu_widget.widget().ocs_widget.camera_calib_path = os.path.join(path, "camera_calibration.json")
207+
self.menu_widget.widget().ocs_widget.laser_calib_path = os.path.join(path, "laser_calibration.json")
208+
self.loadData(
209+
"assets/camera_calibration.json",
210+
"assets/laser_calibration.json",
211+
"assets/example_vid.avi",
212+
)
213+
214+
self._reconstruction_pipeline = reconstruction_pipeline.ReconstructionPipeline(
215+
self.camera,
216+
self.laser,
217+
feature_estimation.SiliconeFeatureEstimator(),
218+
point_tracking.SiliconePointTracker(),
219+
correspondence_estimation.BruteForceEstimator(10, 30, 40, 100),
220+
surface_reconstruction.SurfaceReconstructor())
221+
222+
223+
205224
'''
206225
glottal_outline_images = torch.from_numpy(np.load("load/glottal_outline_images.npy")).cuda()
207226
glottis_segmentations = torch.from_numpy(np.load("load/glottis_segmentations.npy",)).cuda()
@@ -634,8 +653,10 @@ def computeFeatures(self):
634653
else:
635654
print("Please choose a Segmentation Algorithm")
636655

656+
use_cuda = self.menu_widget.widget().getSubmenuValue("CUDA", "Use")
657+
637658
self._reconstruction_pipeline.set_feature_estimator(feature_estimator)
638-
self._reconstruction_pipeline.estimate_features(self.video)
659+
self._reconstruction_pipeline.estimate_features(self.video if use_cuda else self.video.cpu())
639660

640661
self.graph_widget.updateGraph(
641662
feature_estimator.glottalAreaWaveform().tolist(), self.graph_widget.glottal_seg_graph
@@ -650,12 +671,27 @@ def computeFeatures(self):
650671
self.image_widget.point_viewer.add_glottal_midlines(feature_estimator.glottalMidlines())
651672
self.image_widget.point_viewer.add_glottal_outlines(feature_estimator.glottalOutlines())
652673

674+
675+
self.update_images_func()
653676
#self.segmentations = segmentations
654677
#self.laserdots = segmentations
655678

656679
def trackPoints(self):
657-
point_positions = self._reconstruction_pipeline.track_points(self.video)
680+
use_cuda = self.menu_widget.widget().getSubmenuValue("CUDA", "Use")
681+
682+
point_tracker: point_tracking.PointTrackerBase = None
683+
if self.menu_widget.widget().getSubmenuValue("Point Tracking", "Invivo"):
684+
point_tracker = point_tracking.InvivoPointTracker()
685+
elif self.menu_widget.widget().getSubmenuValue("Point Tracking", "Silicone"):
686+
point_tracker = point_tracking.SiliconePointTracker()
687+
else:
688+
print("Please choose a Segmentation Algorithm")
689+
690+
self._reconstruction_pipeline.set_point_tracker(point_tracker)
691+
point_positions = self._reconstruction_pipeline.track_points(self.video if use_cuda else self.video.cpu())
658692
self.image_widget.point_viewer.add_optimized_points(point_positions.detach().cpu().numpy())
693+
694+
self.update_images_func()
659695

660696
def buildCorrespondences(self):
661697
min_search_space = float(
@@ -738,6 +774,7 @@ def denseShapeEstimation(self):
738774
psi=psi,
739775
ARAP_iterations=ARAP_iterations,
740776
ARAP_weight=ARAP_weight,
777+
flip_y=False if self.menu_widget.widget().getSubmenuValue("Point Tracking", "Silicone") else True
741778
)
742779

743780
self.point_cloud_offsets = [0]
@@ -809,10 +846,6 @@ def denseShapeEstimation(self):
809846
self.pointcloud_go_mesh_core = self.viewer_widget.get_mesh(self.pointcloud_go_instance_id).mesh_core
810847

811848

812-
813-
814-
815-
816849
self.controlpoints_offsets = [0]
817850
self.controlpoints_elements = [len(self.leftDeformed[0]) + len(self.rightDeformed[0])]
818851

@@ -823,17 +856,13 @@ def denseShapeEstimation(self):
823856

824857
super_pc_controlpoints = np.concatenate([np.array(self.leftDeformed), np.array(self.rightDeformed)], axis=1)
825858
super_pc_controlpoints = np.concatenate(super_pc_controlpoints)
826-
827-
vertex_attributes = {}
828-
if not "vertexColor" in vertex_attributes:
829-
vertex_attributes["vertexColor"] = np.tile(
830-
np.array([0.0, 1.0, 1.0], dtype=np.float32), (super_pc_controlpoints.shape[0], 1)
831-
)
832-
859+
833860
# I'm so sorry for this.
834861
faces = []
835862
res_u = zSubdivisions
836863
res_v = len(self.leftDeformed[0]) // res_u
864+
half_verts = len(self.leftDeformed[0])
865+
num_verts = 2 * half_verts
837866
for b in range(len(self.leftDeformed)):
838867
a = b * num_verts
839868
for i in range(res_u - 1):
@@ -843,15 +872,15 @@ def denseShapeEstimation(self):
843872
p2 = p0 + res_v + 1
844873
p3 = p0 + res_v
845874
faces.append([p0, p1, p2, p3]) # a quad face
846-
faces.append([len(self.leftDeformed[0]) + p0, len(self.leftDeformed[0]) + p1, len(self.leftDeformed[0]) + p2, len(self.leftDeformed[0]) + p3])
875+
faces.append([half_verts + p0, half_verts + p1, half_verts + p2, half_verts + p3])
847876

848877
core_id = GlMeshCoreId()
849878
self.viewer_widget.add_mesh_(core_id, super_pc_controlpoints, np.array(faces))
850879
prefab_id = GlMeshPrefabId(core_id)
851880
self.viewer_widget.add_mesh_prefab_(
852881
prefab_id,
853882
shader="wireframe",
854-
vertex_attributes=vertex_attributes,
883+
vertex_attributes={},
855884
face_attributes={},
856885
uniforms={"lineColor": np.array([0.0, 1.0, 1.0])},
857886
)

source/correspondence_estimation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ def compute_correspondences(self, camera, laser, point_image):
5656
pixelLocations, laserGridIDs = Correspondences.initialize(
5757
laser,
5858
camera,
59-
point_image.detach().cpu().numpy(),
59+
point_image.detach().cpu().numpy().copy(),
6060
self._min_depth,
6161
self._max_depth,
6262
)
6363

6464
self._correspondences = bruteForceCorrespondence.compute(
6565
laserGridIDs,
6666
pixelLocations,
67-
point_image.detach().cpu().numpy(),
67+
point_image.detach().cpu().numpy().copy(),
6868
camera,
6969
laser,
7070
self._consensus_size,

source/cv.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ def compute_point_estimates_from_nearest_neighbors(closed_glottis_points: List[t
168168
return final_point_tensor
169169

170170

171+
def compute_pairwise_neighbors(a: torch.tensor, b: torch.tensor) -> torch.tensor:
172+
# Compute pairwise distances: [N, M]
173+
dists = torch.cdist(a, b, p=2) # Euclidean distance
174+
# Find index of nearest neighbor in M for each point in N
175+
nearest_indices = dists.argmin(dim=1) # [N]
176+
# Gather nearest neighbors
177+
nearest_points = b[nearest_indices] # [N, 2]
178+
return nearest_points
179+
180+
171181
def fill_nans_in_point_timeseries(neighbors_over_time: torch.tensor) -> torch.tensor:
172182
a = 1
173183
return None

source/feature_estimation.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,53 @@ def compute_local_maxima(self, image, kernelsize=7):
148148
kernel = torch.ones((kernelsize, kernelsize), device=image.device)
149149
kernel[math.floor(kernelsize // 2), math.floor(kernelsize // 2)] = 0.0
150150
maxima = (image > kornia.morphology.dilation(image.unsqueeze(0).unsqueeze(0).float(), kernel)).squeeze()
151+
'''
152+
maxima_locations = maxima.nonzero()
153+
maxima_locations = torch.concat([torch.zeros_like(maxima_locations)[:, :1], maxima_locations])
154+
155+
crops, y_windows, x_windows = cv.extract_windows_from_batch(image.unsqueeze(0), maxima_locations, device=image.device)
156+
per_crop_max = crops.amax([-1, -2], keepdim=True)
157+
per_crop_min = crops.amin([-1, -2], keepdim=True)
158+
159+
normalized_crops = (crops - per_crop_min) / (per_crop_max - per_crop_min)
160+
161+
new_points = []
162+
163+
for index, crop in enumerate(normalized_crops):
164+
local_maximum = cv.unravel_index(
165+
torch.argmax(crop[1:-1, 1:-1]), [5, 5]
166+
)
167+
168+
# Add one again, since we removed the border from the local maximum lookup
169+
x0, y0 = local_maximum[1] + 1, local_maximum[0] + 1
170+
171+
# Get 3x3 subwindow from crop, where the local maximum is centered.
172+
neighborhood = 1
173+
x_min = max(0, x0 - neighborhood)
174+
x_max = min(crop.shape[1], x0 + neighborhood + 1)
175+
y_min = max(0, y0 - neighborhood)
176+
y_max = min(crop.shape[0], y0 + neighborhood + 1)
177+
178+
sub_image = crop[y_min:y_max, x_min:x_max]
179+
sub_image = (sub_image - sub_image.min()) / (
180+
sub_image.max() - sub_image.min()
181+
)
182+
183+
centroids = cv.moment_method(
184+
sub_image.unsqueeze(0)
185+
).squeeze()
186+
187+
refined_x = (
188+
x_windows[index, 0, 0, 0] + centroids[0] + x0 - 1
189+
).item()
190+
refined_y = (
191+
y_windows[index, 0, 0, 0] + centroids[1] + y0 - 1
192+
).item()
193+
194+
new_points.append(torch.tensor([refined_y, refined_x], device=image.device, dtype=torch.float32))
195+
196+
'''
197+
151198
return maxima, maxima.nonzero()
152199

153200

0 commit comments

Comments
 (0)