Skip to content

Commit ec1f299

Browse files
authored
Merge pull request #90 from pariterre/master
Added the distance between markers and experimental markers
2 parents b074abe + c596f37 commit ec1f299

File tree

3 files changed

+203
-37
lines changed

3 files changed

+203
-37
lines changed

bioviz/__init__.py

+31-10
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ def __init__(
146146
soft_contacts_size=soft_contacts_size,
147147
soft_contacts_color=soft_contacts_color,
148148
)
149-
self.vtk_model_markers: VtkModel | None = None
150149
self.is_executing = False
151150
self.animation_warning_already_shown = False
152151

@@ -161,6 +160,7 @@ def __init__(
161160
self.show_experimental_markers = False
162161
self.experimental_markers = None
163162
self.experimental_markers_color = experimental_markers_color
163+
self.virtual_to_experimental_markers_indices = None
164164
self.show_experimental_forces = False
165165
self.experimental_forces = None
166166
self.segment_forces = []
@@ -348,6 +348,10 @@ def __init__(
348348

349349
# Update everything at the position Q=0
350350
self.set_q(self.Q)
351+
if self.show_floor:
352+
self.__set_floor()
353+
if self.show_gravity_vector:
354+
self.__set_gravity_vector()
351355

352356
def reset_q(self):
353357
self.Q = np.zeros(self.Q.shape)
@@ -386,8 +390,6 @@ def set_q(self, Q, refresh_window=True):
386390
self.__set_rt_from_q()
387391
self.__set_meshes_from_q()
388392
self.__set_global_center_of_mass_from_q()
389-
self.__set_gravity_vector()
390-
self.__set_floor()
391393
self.__set_segments_center_of_mass_from_q()
392394
self.__set_markers_from_q()
393395
self.__set_contacts_from_q()
@@ -1032,12 +1034,32 @@ def __load_experimental_data_from_button(self):
10321034
def load_c3d(self, data, auto_start=True, ignore_animation_warning=True):
10331035
self.load_experimental_markers(data, auto_start, ignore_animation_warning)
10341036

1035-
def load_experimental_markers(self, data, auto_start=True, ignore_animation_warning=True):
1037+
def load_experimental_markers(
1038+
self,
1039+
data,
1040+
auto_start=True,
1041+
ignore_animation_warning=True,
1042+
experimental_markers_mapping_to_virtual: list[int, ...] = None,
1043+
):
10361044
if isinstance(data, str):
10371045
self.experimental_markers = Markers.from_c3d(data)
10381046
if self.experimental_markers.units == "mm":
10391047
self.experimental_markers = self.experimental_markers * 0.001
10401048

1049+
# Try to find a correspondence between the loaded experimental markers and the model
1050+
self.virtual_to_experimental_markers_indices = experimental_markers_mapping_to_virtual
1051+
if self.virtual_to_experimental_markers_indices is None:
1052+
try:
1053+
virtual_marker_names = [n.to_string() for n in self.model.markerNames()]
1054+
exp_marker_names = list(self.experimental_markers.channel.data)
1055+
self.virtual_to_experimental_markers_indices = [
1056+
virtual_marker_names.index(name) if name in virtual_marker_names else None
1057+
for name in exp_marker_names
1058+
]
1059+
except ValueError:
1060+
# Did not find direct correspondence
1061+
pass
1062+
10411063
self.c3d_file_name = data
10421064
self.radio_c3d_editor_model.setEnabled(True)
10431065

@@ -1050,11 +1072,6 @@ def load_experimental_markers(self, data, auto_start=True, ignore_animation_warn
10501072
f"Allowed type are numpy array (3xNxT), data array (3xNxT) or .c3d file (str)."
10511073
)
10521074

1053-
if not self.vtk_model_markers:
1054-
self.vtk_model_markers = VtkModel(
1055-
self.vtk_window, markers_color=self.experimental_markers_color, markers_size=self.vtk_markers_size
1056-
)
1057-
10581075
self.__set_movement_slider()
10591076
self.show_experimental_markers = True
10601077

@@ -1110,7 +1127,11 @@ def __set_experimental_markers_from_frame(self):
11101127

11111128
t_slider = self.movement_slider[0].value() - 1
11121129
t = t_slider if t_slider < self.experimental_markers.shape[2] else self.experimental_markers.shape[2] - 1
1113-
self.vtk_model_markers.update_markers(self.experimental_markers[:, :, t : t + 1].isel(time=[0]))
1130+
self.vtk_model.update_experimental_markers(
1131+
self.experimental_markers[:, :, t : t + 1].isel(time=[0]),
1132+
with_link=True,
1133+
virtual_to_experimental_markers_indices=self.virtual_to_experimental_markers_indices,
1134+
)
11141135

11151136
def __set_experimental_forces_from_frame(self):
11161137
if not self.show_experimental_forces:

bioviz/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from packaging.version import parse as parse_version
22

3-
__version__ = "2.3.0"
3+
__version__ = "2.3.2"
44

55

66
def check_version(tool_to_compare, min_version, max_version):

bioviz/biorbd_vtk.py

+171-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Visualization toolkit in pyomeca
33
"""
4+
from dataclasses import dataclass
45
import os
56
import time
67
import sys
@@ -198,13 +199,25 @@ def record(self, finish=False, button_to_block=(), file_name=None):
198199
self.setMaximumSize(self.maximum_size)
199200

200201

202+
@dataclass
203+
class _MarkerInternal:
204+
data: Markers
205+
size: float
206+
color: tuple[float, float, float]
207+
opacity: float
208+
actors: list[QVTKRenderWindowInteractor, ...]
209+
210+
201211
class VtkModel(QtWidgets.QWidget):
202212
def __init__(
203213
self,
204214
parent,
205215
markers_size=0.010,
206216
markers_color=(1, 1, 1),
207217
markers_opacity=1.0,
218+
experimental_markers_size=0.010,
219+
experimental_markers_color=(1, 1, 1),
220+
experimental_markers_opacity=1.0,
208221
contacts_color=(0, 1, 0),
209222
contacts_size=0.01,
210223
contacts_opacity=1.0,
@@ -258,11 +271,19 @@ def __init__(
258271
self.setAutoFillBackground(True)
259272
self.setPalette(palette)
260273

261-
self.markers = Markers()
262-
self.markers_size = markers_size
263-
self.markers_color = markers_color
264-
self.markers_opacity = markers_opacity
265-
self.markers_actors = list()
274+
self.markers = {
275+
"model": _MarkerInternal(
276+
data=Markers(), color=markers_color, size=markers_size, opacity=markers_opacity, actors=list()
277+
),
278+
"experimental": _MarkerInternal(
279+
data=Markers(),
280+
color=experimental_markers_color,
281+
size=experimental_markers_size,
282+
opacity=experimental_markers_opacity,
283+
actors=list(),
284+
),
285+
}
286+
self.markers_link_actors: list[QVTKRenderWindowInteractor, ...] = list()
266287

267288
self.contacts = Markers()
268289
self.contacts_size = contacts_size
@@ -343,21 +364,53 @@ def set_markers_color(self, markers_color):
343364
markers_color : tuple(int)
344365
Color the markers should be drawn (1 is max brightness)
345366
"""
346-
self.markers_color = markers_color
347-
self.update_markers(self.markers)
367+
self._set_markers_color(markers_color, key="model")
368+
369+
def set_experimental_markers_color(self, markers_color):
370+
"""
371+
Dynamically change the color of the markers
372+
Parameters
373+
----------
374+
markers_color : tuple(int)
375+
Color the markers should be drawn (1 is max brightness)
376+
"""
377+
self._set_markers_color(markers_color, key="experimental")
378+
379+
def _set_markers_color(self, markers_color, key):
380+
"""
381+
Dynamically change the color of the markers
382+
Parameters
383+
----------
384+
markers_color : tuple(int)
385+
Color the markers should be drawn (1 is max brightness)
386+
"""
387+
self.markers_color[key] = markers_color
388+
self._update_markers(self.markers, key)
348389

349390
def set_markers_size(self, markers_size):
391+
self._set_markers_size(markers_size, "model")
392+
393+
def set_experimental_markers_size(self, markers_size):
394+
self._set_markers_size(markers_size, "experimental")
395+
396+
def _set_markers_size(self, markers_size, key):
350397
"""
351398
Dynamically change the size of the markers
352399
Parameters
353400
----------
354401
markers_size : float
355402
Size the markers should be drawn
356403
"""
357-
self.markers_size = markers_size
358-
self.update_markers(self.markers)
404+
self.markers_size[key] = markers_size
405+
self._update_markers(self.markers, key)
359406

360407
def set_markers_opacity(self, markers_opacity):
408+
self._set_markers_opacity(markers_opacity, "model")
409+
410+
def set_experimental_markers_opacity(self, markers_opacity):
411+
self._set_markers_opacity(markers_opacity, "experimental")
412+
413+
def _set_markers_opacity(self, markers_opacity, key):
361414
"""
362415
Dynamically change the opacity of the markers
363416
Parameters
@@ -368,10 +421,10 @@ def set_markers_opacity(self, markers_opacity):
368421
-------
369422
370423
"""
371-
self.markers_opacity = markers_opacity
372-
self.update_markers(self.markers)
424+
self.markers_opacity[key] = markers_opacity
425+
self._update_markers(self.markers, key)
373426

374-
def new_marker_set(self, markers):
427+
def _new_marker_set(self, markers, key):
375428
"""
376429
Define a new marker set. This function must be called each time the number of markers change
377430
Parameters
@@ -382,28 +435,52 @@ def new_marker_set(self, markers):
382435
"""
383436
if len(markers.shape) > 2 and markers.shape[2] > 1:
384437
raise IndexError("Markers should be from one frame only")
385-
self.markers = markers
438+
self.markers[key].data = markers
386439

387440
# Remove previous actors from the scene
388-
for actor in self.markers_actors:
441+
for actor in self.markers[key].actors:
389442
self.parent_window.ren.RemoveActor(actor)
390-
self.markers_actors = list()
443+
self.markers[key].actors = list()
391444

392445
# Create the geometry of a point (the coordinate) points = vtk.vtkPoints()
393446
for i in range(markers.channel.size):
394447
# Create a mapper
395448
mapper = vtkPolyDataMapper()
396449

397450
# Create an actor
398-
self.markers_actors.append(vtkActor())
399-
self.markers_actors[i].SetMapper(mapper)
451+
self.markers[key].actors.append(vtkActor())
452+
self.markers[key].actors[i].SetMapper(mapper)
400453

401-
self.parent_window.ren.AddActor(self.markers_actors[i])
454+
self.parent_window.ren.AddActor(self.markers[key].actors[i])
402455

403456
# Update marker position
404-
self.update_markers(self.markers)
457+
self._update_markers(self.markers[key].data, key)
405458

406459
def update_markers(self, markers):
460+
self._update_markers(markers, "model")
461+
462+
def update_experimental_markers(
463+
self, markers, with_link: bool = True, virtual_to_experimental_markers_indices: list[int, ...] = None
464+
):
465+
"""
466+
Update position of the experimental markers on the screen (but do not repaint)
467+
Parameters
468+
----------
469+
markers : Markers3d
470+
One frame of markers
471+
with_link : bool
472+
If links to the virtual markers should be added (implies that coll to update_markers was done prior
473+
virtual_to_experimental_markers_indices : list[int, ...]
474+
A list that links the virtual markers indices to the experimental marker indices. If left None,
475+
it is assumed to be a one to one (0=>0, 1=>1, ..., n_virtual_markers=>n_experimental_markers)
476+
"""
477+
self._update_markers(markers, "experimental")
478+
if with_link:
479+
if virtual_to_experimental_markers_indices is None:
480+
virtual_to_experimental_markers_indices = tuple(range(self.markers["model"].data.channel.size))
481+
self._update_experimental_marker_link(virtual_to_experimental_markers_indices)
482+
483+
def _update_markers(self, markers, key):
407484
"""
408485
Update position of the markers on the screen (but do not repaint)
409486
Parameters
@@ -415,22 +492,90 @@ def update_markers(self, markers):
415492

416493
if len(markers.shape) > 2 and markers.shape[2] > 1:
417494
raise IndexError("Markers should be from one frame only")
418-
if markers.channel.size != self.markers.channel.size:
419-
self.new_marker_set(markers)
495+
if markers.channel.size != self.markers[key].data.channel.size:
496+
self._new_marker_set(markers, key)
420497
return # Prevent calling update_markers recursively
421-
self.markers = markers
498+
self.markers[key].data = markers
422499
markers = np.array(markers)
423500

424-
for i, actor in enumerate(self.markers_actors):
501+
for i, actor in enumerate(self.markers[key].actors):
425502
# mapper = actors.GetNextActor().GetMapper()
426503
mapper = actor.GetMapper()
427-
self.markers_actors[i].GetProperty().SetColor(self.markers_color)
428-
self.markers_actors[i].GetProperty().SetOpacity(self.markers_opacity)
504+
self.markers[key].actors[i].GetProperty().SetColor(self.markers[key].color)
505+
self.markers[key].actors[i].GetProperty().SetOpacity(self.markers[key].opacity)
429506
source = vtkSphereSource()
430507
source.SetCenter(markers[0:3, i])
431-
source.SetRadius(self.markers_size)
508+
source.SetRadius(self.markers[key].size)
432509
mapper.SetInputConnection(source.GetOutputPort())
433510

511+
def _new_experimental_marker_link(self, virtual_to_experimental_markers_indices):
512+
"""
513+
Define a new marker link. This function must be called each time the number of markers change
514+
Parameters
515+
"""
516+
517+
# Remove previous actors from the scene
518+
for actor in self.markers_link_actors:
519+
self.parent_window.ren.RemoveActor(actor)
520+
self.markers_link_actors = list()
521+
522+
cells = vtkCellArray()
523+
for i, j in enumerate(virtual_to_experimental_markers_indices):
524+
if j is None:
525+
continue
526+
points = vtkPoints()
527+
points.InsertNextPoint(self.markers["model"].data[:3, j, 0].data)
528+
points.InsertNextPoint(self.markers["experimental"].data[:3, i, 0].data)
529+
530+
# Create the polygons
531+
poly = vtkPolyLine()
532+
poly.GetPointIds().SetNumberOfIds(2) # make a line
533+
poly.GetPointIds().SetId(0, 0)
534+
poly.GetPointIds().SetId(1, 1)
535+
cells.InsertNextCell(poly)
536+
537+
poly_data = vtkPolyData()
538+
poly_data.SetPoints(points)
539+
poly_data.SetLines(cells)
540+
541+
# Create a mapper
542+
mapper = vtkPolyDataMapper()
543+
mapper.SetInputData(poly_data)
544+
545+
# Create an actor
546+
self.markers_link_actors.append(vtkActor())
547+
self.markers_link_actors[-1].SetMapper(mapper)
548+
self.markers_link_actors[-1].GetProperty().SetColor((1, 0, 0))
549+
550+
self.parent_window.ren.AddActor(self.markers_link_actors[-1])
551+
552+
# Update marker position
553+
self._update_experimental_marker_link(virtual_to_experimental_markers_indices)
554+
555+
def _update_experimental_marker_link(self, virtual_to_experimental_markers_indices):
556+
"""
557+
Update position of the line between experimental markers and virtual markers on the screen (but do not repaint)
558+
Parameters
559+
"""
560+
561+
if len(self.markers_link_actors) != len(
562+
tuple(i for i in virtual_to_experimental_markers_indices if i is not None)
563+
):
564+
self._new_experimental_marker_link(virtual_to_experimental_markers_indices)
565+
return # Prevent calling update_markers recursively
566+
567+
cmp = 0
568+
for i, j in enumerate(virtual_to_experimental_markers_indices):
569+
if j is None:
570+
continue
571+
points = vtkPoints()
572+
points.InsertNextPoint(self.markers["model"].data[:3, j, 0].data)
573+
points.InsertNextPoint(self.markers["experimental"].data[:3, i, 0].data)
574+
575+
poly_line = self.markers_link_actors[cmp].GetMapper().GetInput()
576+
poly_line.SetPoints(points)
577+
cmp += 1
578+
434579
def set_contacts_color(self, contacts_color):
435580
"""
436581
Dynamically change the color of the contacts

0 commit comments

Comments
 (0)