def test_rototrans_creation(): dims = ("row", "col", "time") array = Rototrans() np.testing.assert_array_equal(x=array, y=xr.DataArray(np.eye(4)[..., np.newaxis])) assert array.dims == dims data = Markers(MARKERS_DATA.values) array = Rototrans.from_markers( origin=data.isel(channel=[0]), axis_1=data.isel(channel=[0, 1]), axis_2=data.isel(channel=[0, 2]), axes_name="xy", axis_to_recalculate="y", ) is_expected_array(array, **EXPECTED_VALUES[67]) size = 4, 4, 100 array = Rototrans.from_random_data(size=size) assert array.shape == size assert array.dims == dims with pytest.raises(ValueError): Angles(ANALOGS_DATA)
class Viz: def __init__( self, model_path=None, loaded_model=None, show_meshes=True, show_global_center_of_mass=True, show_segments_center_of_mass=True, segments_center_of_mass_size=0.005, show_global_ref_frame=True, show_local_ref_frame=True, show_markers=True, markers_size=0.010, show_contacts=True, contacts_size=0.010, show_muscles=True, show_wrappings=True, show_analyses_panel=True, background_color=(0.5, 0.5, 0.5), **kwargs, ): """ Class that easily shows a biorbd model Args: loaded_model: reference to a biorbd loaded model (if both loaded_model and model_path, load_model is selected model_path: path of the model to load """ # Load and store the model if loaded_model is not None: if not isinstance(loaded_model, biorbd.Model): raise TypeError( "loaded_model should be of a biorbd.Model type") self.model = loaded_model elif model_path is not None: self.model = biorbd.Model(model_path) else: raise ValueError("loaded_model or model_path must be provided") # Create the plot self.vtk_window = VtkWindow(background_color=background_color) self.vtk_model = VtkModel( self.vtk_window, markers_color=(0, 0, 1), markers_size=markers_size, contacts_size=contacts_size, segments_center_of_mass_size=segments_center_of_mass_size) self.is_executing = False self.animation_warning_already_shown = False # Set Z vertical cam = self.vtk_window.ren.GetActiveCamera() cam.SetFocalPoint(0, 0, 0) cam.SetPosition(5, 0, 0) cam.SetRoll(-90) # Get the options self.show_markers = show_markers self.show_contacts = show_contacts self.show_global_ref_frame = show_global_ref_frame self.show_global_center_of_mass = show_global_center_of_mass self.show_segments_center_of_mass = show_segments_center_of_mass self.show_local_ref_frame = show_local_ref_frame if self.model.nbMuscles() > 0: self.show_muscles = show_muscles else: self.show_muscles = False show_wrappings = False self.show_wrappings = show_wrappings if sum([ len(i) for i in self.model.meshPoints(np.zeros(self.model.nbQ())) ]) > 0: self.show_meshes = show_meshes else: self.show_meshes = 0 # Create all the reference to the things to plot self.nQ = self.model.nbQ() self.Q = np.zeros(self.nQ) if self.show_markers: self.Markers = InterfacesCollections.Markers(self.model) self.markers = Markers(np.ndarray((3, self.model.nbMarkers(), 1))) if self.show_contacts: self.Contacts = InterfacesCollections.Contact(self.model) self.contacts = Markers(np.ndarray( (3, self.model.nbContacts(), 1))) if self.show_global_center_of_mass: self.CoM = InterfacesCollections.CoM(self.model) self.global_center_of_mass = Markers(np.ndarray((3, 1, 1))) if self.show_segments_center_of_mass: self.CoMbySegment = InterfacesCollections.CoMbySegment(self.model) self.segments_center_of_mass = Markers( np.ndarray((3, self.model.nbSegment(), 1))) if self.show_meshes: self.mesh = [] self.meshPointsInMatrix = InterfacesCollections.MeshPointsInMatrix( self.model) for i, vertices in enumerate( self.meshPointsInMatrix.get_data(Q=self.Q, compute_kin=False)): triangles = np.array([p.face() for p in self.model.meshFaces()[i]], dtype="int32") \ if len(self.model.meshFaces()[i]) else np.ndarray((0, 3), dtype="int32") self.mesh.append(Mesh(vertex=vertices, triangles=triangles.T)) if self.show_muscles: self.model.updateMuscles(self.Q, True) self.muscles = [] for group_idx in range(self.model.nbMuscleGroups()): for muscle_idx in range( self.model.muscleGroup(group_idx).nbMuscles()): musc = self.model.muscleGroup(group_idx).muscle(muscle_idx) tp = np.zeros( (3, len(musc.position().musclesPointsInGlobal()), 1)) self.muscles.append(Mesh(vertex=tp)) self.musclesPointsInGlobal = InterfacesCollections.MusclesPointsInGlobal( self.model) if self.show_local_ref_frame or self.show_global_ref_frame: self.rt = [] self.allGlobalJCS = InterfacesCollections.AllGlobalJCS(self.model) for rt in self.allGlobalJCS.get_data(Q=self.Q, compute_kin=False): self.rt.append(Rototrans(rt)) if self.show_global_ref_frame: self.vtk_model.create_global_ref_frame() if self.show_wrappings: self.wraps_base = [] self.wraps_current = [] for m in range(self.model.nbMuscles()): path_modifier = self.model.muscle(m).pathModifier() wraps = [] wraps_current = [] for w in range(path_modifier.nbWraps()): wrap = path_modifier.object(w) if wrap.typeOfNode() == biorbd.VIA_POINT: continue # Do not show via points elif wrap.typeOfNode() == biorbd.WRAPPING_HALF_CYLINDER: wrap_cylinder = biorbd.WrappingHalfCylinder(wrap) res = 11 # resolution x = np.sin(np.linspace(0, np.pi, res)) * wrap_cylinder.radius() y = np.cos(np.linspace(0, np.pi, res)) * wrap_cylinder.radius() z = np.ones((res, )) * wrap_cylinder.length() vertices = np.concatenate([ np.array([0, 0, z[0]])[:, np.newaxis], [x, y, z], np.array([0, 0, -z[0]])[:, np.newaxis], [x, y, -z] ], axis=1) tri_0_0 = np.zeros((res - 1, 1)) tri_1_0 = np.arange(1, res)[:, np.newaxis] tri_2_0 = np.arange(2, res + 1)[:, np.newaxis] tri_0 = np.concatenate([tri_0_0, tri_1_0, tri_2_0], axis=1) tri_1 = tri_0 + res + 1 tri_2 = np.concatenate( [tri_1_0, tri_2_0, tri_1_0 + res + 1], axis=1) tri_3 = np.concatenate( [tri_1_0 + res + 1, tri_1_0 + res + 2, tri_2_0], axis=1) tri_4 = np.array([[1, res, res + 2], [res, res + 2, res + res + 1]]) triangles = np.array(np.concatenate( (tri_0, tri_1, tri_2, tri_3, tri_4)), dtype="int32").T else: raise NotImplementedError( "The wrapping object is not implemented in bioviz") wraps.append( Mesh(vertex=vertices[:, :, np.newaxis], triangles=triangles)) wraps_current.append( Mesh(vertex=vertices[:, :, np.newaxis], triangles=triangles)) self.wraps_base.append(wraps) self.wraps_current.append(wraps_current) self.show_analyses_panel = show_analyses_panel if self.show_analyses_panel: self.muscle_analyses = [] self.palette_active = QPalette() self.palette_inactive = QPalette() self.set_viz_palette() self.animated_Q = [] self.play_stop_push_button = [] self.is_animating = False self.is_recording = False self.start_icon = QIcon( QPixmap(f"{os.path.dirname(__file__)}/ressources/start.png")) self.pause_icon = QIcon( QPixmap(f"{os.path.dirname(__file__)}/ressources/pause.png")) self.record_icon = QIcon( QPixmap(f"{os.path.dirname(__file__)}/ressources/record.png")) self.add_icon = QIcon( QPixmap(f"{os.path.dirname(__file__)}/ressources/add.png")) self.stop_icon = QIcon( QPixmap(f"{os.path.dirname(__file__)}/ressources/stop.png")) self.double_factor = 10000 self.sliders = list() self.movement_slider = [] self.active_analyses_widget = None self.analyses_layout = QHBoxLayout() self.analyses_muscle_widget = QWidget() self.add_options_panel() # Update everything at the position Q=0 self.set_q(self.Q) def reset_q(self): self.Q = np.zeros(self.Q.shape) for slider in self.sliders: slider[1].setValue(0) slider[2].setText(f"{0:.2f}") self.set_q(self.Q) # Reset also muscle analyses graphs self.__update_muscle_analyses_graphs(False, False, False, False) def copy_q_to_clipboard(self): pandas.DataFrame(self.Q[np.newaxis, :]).to_clipboard(sep=',', index=False, header=False) def set_q(self, Q, refresh_window=True): """ Manually update Args: Q: np.array Generalized coordinate refresh_window: bool If the window should be refreshed now or not """ if isinstance(Q, (tuple, list)): Q = np.array(Q) if not isinstance( Q, np.ndarray) and len(Q.shape) > 1 and Q.shape[0] != self.nQ: raise TypeError(f"Q should be a {self.nQ} column vector") self.Q = Q self.model.UpdateKinematicsCustom(self.Q) if self.show_muscles: self.__set_muscles_from_q() if self.show_local_ref_frame: self.__set_rt_from_q() if self.show_meshes: self.__set_meshes_from_q() if self.show_global_center_of_mass: self.__set_global_center_of_mass_from_q() if self.show_segments_center_of_mass: self.__set_segments_center_of_mass_from_q() if self.show_markers: self.__set_markers_from_q() if self.show_contacts: self.__set_contacts_from_q() if self.show_wrappings: self.__set_wrapping_from_q() # Update the sliders if self.show_analyses_panel: for i, slide in enumerate(self.sliders): slide[1].blockSignals(True) slide[1].setValue(int(self.Q[i] * self.double_factor)) slide[1].blockSignals(False) slide[2].setText(f"{self.Q[i]:.2f}") if refresh_window: self.refresh_window() def refresh_window(self): """ Manually refresh the window. One should be aware when manually managing the window, that the plot won't even rotate if not refreshed """ self.vtk_window.update_frame() def update(self): if self.show_analyses_panel and self.is_animating: self.movement_slider[0].setValue( (self.movement_slider[0].value() + 1) % (self.movement_slider[0].maximum() + 1)) if self.is_recording: self.__record() if self.movement_slider[0].value() + 1 == ( self.movement_slider[0].maximum() + 1): self.__start_stop_animation() self.refresh_window() def exec(self): self.is_executing = True while self.vtk_window.is_active: self.update() self.is_executing = False def set_viz_palette(self): self.palette_active.setColor(QPalette.WindowText, QColor(Qt.black)) self.palette_active.setColor(QPalette.ButtonText, QColor(Qt.black)) self.palette_inactive.setColor(QPalette.WindowText, QColor(Qt.gray)) def add_options_panel(self): # Prepare the sliders options_layout = QVBoxLayout() options_layout.addStretch() # Centralize the sliders sliders_layout = QVBoxLayout() max_label_width = -1 # Get min and max for all dof ranges = [] for i in range(self.model.nbSegment()): seg = self.model.segment(i) for r in seg.QRanges(): ranges.append([r.min(), r.max()]) for i in range(self.model.nbQ()): slider_layout = QHBoxLayout() sliders_layout.addLayout(slider_layout) # Add a name name_label = QLabel() name = f"{self.model.nameDof()[i].to_string()}" name_label.setText(name) name_label.setPalette(self.palette_active) label_width = name_label.fontMetrics().boundingRect( name_label.text()).width() if label_width > max_label_width: max_label_width = label_width slider_layout.addWidget(name_label) # Add the slider slider = QSlider(Qt.Horizontal) slider.setMinimumSize(100, 0) slider.setMinimum(int(ranges[i][0] * self.double_factor)) slider.setMaximum(int(ranges[i][1] * self.double_factor)) slider.setPageStep(self.double_factor) slider.setValue(0) slider.valueChanged.connect(self.__move_avatar_from_sliders) slider.sliderReleased.connect( partial(self.__update_muscle_analyses_graphs, False, False, False, False)) slider_layout.addWidget(slider) # Add the value value_label = QLabel() value_label.setText(f"{0:.2f}") value_label.setPalette(self.palette_active) slider_layout.addWidget(value_label) # Add to the main sliders self.sliders.append((name_label, slider, value_label)) # Adjust the size of the names for name_label, _, _ in self.sliders: name_label.setFixedWidth(max_label_width + 1) # Put the sliders in a scrollable area sliders_widget = QWidget() sliders_widget.setLayout(sliders_layout) sliders_scroll = QScrollArea() sliders_scroll.setFrameShape(0) sliders_scroll.setWidgetResizable(True) sliders_scroll.setWidget(sliders_widget) options_layout.addWidget(sliders_scroll) # Add reset button button_layout = QHBoxLayout() options_layout.addLayout(button_layout) reset_push_button = QPushButton("Reset") reset_push_button.setPalette(self.palette_active) reset_push_button.released.connect(self.reset_q) button_layout.addWidget(reset_push_button) copyq_push_button = QPushButton("Copy Q to clipboard") copyq_push_button.setPalette(self.palette_active) copyq_push_button.released.connect(self.copy_q_to_clipboard) button_layout.addWidget(copyq_push_button) # Add the radio button for analyses option_analyses_group = QGroupBox() option_analyses_layout = QVBoxLayout() # Add text analyse_text = QLabel() analyse_text.setPalette(self.palette_active) analyse_text.setText("Analyses") option_analyses_layout.addWidget(analyse_text) # Add the no analyses radio_none = QRadioButton() radio_none.setPalette(self.palette_active) radio_none.setChecked(True) radio_none.toggled.connect( lambda: self.__select_analyses_panel(radio_none, 0)) radio_none.setText("None") option_analyses_layout.addWidget(radio_none) # Add the muscles analyses radio_muscle = QRadioButton() radio_muscle.setPalette(self.palette_active) radio_muscle.toggled.connect( lambda: self.__select_analyses_panel(radio_muscle, 1)) radio_muscle.setText("Muscles") option_analyses_layout.addWidget(radio_muscle) # Add the layout to the interface option_analyses_group.setLayout(option_analyses_layout) options_layout.addWidget(option_analyses_group) # Finalize the options panel options_layout.addStretch() # Centralize the sliders # Animation panel animation_layout = QVBoxLayout() animation_layout.addWidget(self.vtk_window.avatar_widget) # Add the animation slider animation_slider_layout = QHBoxLayout() animation_layout.addLayout(animation_slider_layout) load_push_button = QPushButton("Load movement") load_push_button.setPalette(self.palette_active) load_push_button.released.connect(self.__load_movement_from_button) animation_slider_layout.addWidget(load_push_button) # Controllers self.play_stop_push_button = QPushButton() self.play_stop_push_button.setIcon(self.start_icon) self.play_stop_push_button.setPalette(self.palette_active) self.play_stop_push_button.setEnabled(False) self.play_stop_push_button.released.connect( self.__start_stop_animation) animation_slider_layout.addWidget(self.play_stop_push_button) slider = QSlider(Qt.Horizontal) slider.setMinimum(0) slider.setMaximum(100) slider.setValue(0) slider.setEnabled(False) slider.valueChanged.connect(self.__animate_from_slider) animation_slider_layout.addWidget(slider) self.record_push_button = QPushButton() self.record_push_button.setIcon(self.record_icon) self.record_push_button.setPalette(self.palette_active) self.record_push_button.setEnabled(True) self.record_push_button.released.connect(self.__record) animation_slider_layout.addWidget(self.record_push_button) self.stop_record_push_button = QPushButton() self.stop_record_push_button.setIcon(self.stop_icon) self.stop_record_push_button.setPalette(self.palette_active) self.stop_record_push_button.setEnabled(False) self.stop_record_push_button.released.connect(self.__stop_record, True) animation_slider_layout.addWidget(self.stop_record_push_button) # Add the frame count frame_label = QLabel() frame_label.setText(f"{0}") frame_label.setPalette(self.palette_inactive) animation_slider_layout.addWidget(frame_label) self.movement_slider = (slider, frame_label) # Global placement of the window self.vtk_window.main_layout.addLayout(options_layout, 0, 0) self.vtk_window.main_layout.addLayout(animation_layout, 0, 1) self.vtk_window.main_layout.setColumnStretch(0, 1) self.vtk_window.main_layout.setColumnStretch(1, 2) # Change the size of the window to account for the new sliders self.vtk_window.resize(self.vtk_window.size().width() * 2, self.vtk_window.size().height()) # Prepare all the analyses panel self.muscle_analyses = MuscleAnalyses(self.analyses_muscle_widget, self) if biorbd.currentLinearAlgebraBackend() == 1: radio_muscle.setEnabled(False) else: if self.model.nbMuscles() == 0: radio_muscle.setEnabled(False) self.__select_analyses_panel(radio_muscle, 1) def __select_analyses_panel(self, radio_button, panel_to_activate): if not radio_button.isChecked(): return # Hide previous analyses panel if necessary self.__hide_analyses_panel() size_factor_none = 1 size_factor_muscle = 1.40 # Find the size factor to get back to normal size if self.active_analyses_widget is None: reduction_factor = size_factor_none elif self.active_analyses_widget == self.analyses_muscle_widget: reduction_factor = size_factor_muscle else: raise RuntimeError( "Non-existing panel asked... This should never happen, please report this issue!" ) # Prepare the analyses panel and new size of window if panel_to_activate == 0: self.active_analyses_widget = None enlargement_factor = size_factor_none elif panel_to_activate == 1: self.active_analyses_widget = self.analyses_muscle_widget enlargement_factor = size_factor_muscle else: raise RuntimeError( "Non-existing panel asked... This should never happen, please report this issue!" ) # Activate the required panel self.__show_local_ref_frame() # Enlarge the main window self.vtk_window.resize( int(self.vtk_window.size().width() * enlargement_factor / reduction_factor), self.vtk_window.size().height()) def __hide_analyses_panel(self): if self.active_analyses_widget is None: return # Remove from main window self.active_analyses_widget.setVisible(False) self.vtk_window.main_layout.removeWidget(self.active_analyses_widget) self.vtk_window.main_layout.setColumnStretch(2, 0) def __show_local_ref_frame(self): # Give the parent as main window if self.active_analyses_widget is not None: self.vtk_window.main_layout.addWidget(self.active_analyses_widget, 0, 2) self.vtk_window.main_layout.setColumnStretch(2, 4) self.active_analyses_widget.setVisible(True) # Update graphs if needed self.__update_muscle_analyses_graphs(False, False, False, False) def __move_avatar_from_sliders(self): for i, slide in enumerate(self.sliders): self.Q[i] = slide[1].value() / self.double_factor slide[2].setText(f" {self.Q[i]:.2f}") self.set_q(self.Q) def __update_muscle_analyses_graphs(self, skip_muscle_length, skip_moment_arm, skip_passive_forces, skip_active_forces): # Adjust muscle analyses if needed if self.active_analyses_widget == self.analyses_muscle_widget: self.muscle_analyses.update_all_graphs(skip_muscle_length, skip_moment_arm, skip_passive_forces, skip_active_forces) def __animate_from_slider(self): # Move the avatar self.movement_slider[1].setText(f"{self.movement_slider[0].value()}") self.Q = copy.copy(self.animated_Q[self.movement_slider[0].value() - 1]) # 1-based self.set_q(self.Q) # Update graph of muscle analyses self.__update_muscle_analyses_graphs(True, True, True, True) def __start_stop_animation(self): if not self.is_executing and not self.animation_warning_already_shown: QMessageBox.warning( self.vtk_window, "Not executing", "bioviz has detected that it is not actually executing.\n\n" "Unless you know what you are doing, the automatic play of the animation will " "therefore not work. Please call the bioviz.exec() method to be able to play " "the animation.\n\nPlease note that the animation slider will work in any case.", ) self.animation_warning_already_shown = True if self.is_animating: self.is_animating = False self.play_stop_push_button.setIcon(self.start_icon) self.record_push_button.setEnabled(True) self.stop_record_push_button.setEnabled(self.is_recording) else: self.is_animating = True self.play_stop_push_button.setIcon(self.pause_icon) self.record_push_button.setEnabled(False) self.stop_record_push_button.setEnabled(False) def __stop_record(self): self.__record(finish=True) def __record(self, finish=False): file_name = None if not self.is_recording: options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog file_name = QFileDialog.getSaveFileName(self.vtk_window, "Save the video", "", "OGV files (*.ogv)", options=options) file_name, file_extension = os.path.splitext(file_name[0]) if file_name == "": return file_name += ".ogv" self.record_push_button.setIcon(self.add_icon) self.stop_record_push_button.setEnabled(True) self.is_recording = True self.vtk_window.record(button_to_block=[ self.record_push_button, self.stop_record_push_button ], finish=finish, file_name=file_name) if finish: self.is_recording = False self.record_push_button.setIcon(self.record_icon) self.stop_record_push_button.setEnabled(False) def __load_movement_from_button(self): # Load the actual movement options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog file_name = QFileDialog.getOpenFileName(self.vtk_window, "Movement to load", "", "All Files (*)", options=options) if not file_name[0]: return if os.path.splitext( file_name[0] )[1] == ".Q1": # If it is from a Matlab reconstruction QLD self.animated_Q = scipy.io.loadmat(file_name[0])["Q1"].transpose() elif os.path.splitext( file_name[0] )[1] == ".Q2": # If it is from a Matlab reconstruction Kalman self.animated_Q = scipy.io.loadmat(file_name[0])["Q2"].transpose() else: # Otherwise assume this is a numpy array self.animated_Q = np.load(file_name[0]).T self.__load_movement() def load_movement(self, all_q, auto_start=True, ignore_animation_warning=True): self.animated_Q = all_q.T self.__load_movement() if ignore_animation_warning: self.animation_warning_already_shown = True if auto_start: self.__start_stop_animation() def __load_movement(self): # Activate the start button self.is_animating = False self.play_stop_push_button.setEnabled(True) self.play_stop_push_button.setIcon(self.start_icon) # Update the slider bar and frame count self.movement_slider[0].setEnabled(True) self.movement_slider[0].setMinimum(1) self.movement_slider[0].setMaximum(self.animated_Q.shape[0]) pal = QPalette() pal.setColor(QPalette.WindowText, QColor(Qt.black)) self.movement_slider[1].setPalette(pal) # Put back to first frame self.movement_slider[0].setValue(1) # Add the combobox in muscle analyses self.muscle_analyses.add_movement_to_dof_choice() def __set_markers_from_q(self): self.markers[0:3, :, :] = self.Markers.get_data(Q=self.Q, compute_kin=False) self.vtk_model.update_markers(self.markers.isel(time=[0])) def __set_contacts_from_q(self): self.contacts[0:3, :, :] = self.Contacts.get_data(Q=self.Q, compute_kin=False) self.vtk_model.update_contacts(self.contacts.isel(time=[0])) def __set_global_center_of_mass_from_q(self): com = self.CoM.get_data(Q=self.Q, compute_kin=False) self.global_center_of_mass.loc[{ "channel": 0, "time": 0 }] = com.squeeze() self.vtk_model.update_global_center_of_mass( self.global_center_of_mass.isel(time=[0])) def __set_segments_center_of_mass_from_q(self): coms = self.CoMbySegment.get_data(Q=self.Q, compute_kin=False) for k, com in enumerate(coms): self.segments_center_of_mass.loc[{ "channel": k, "time": 0 }] = com.squeeze() self.vtk_model.update_segments_center_of_mass( self.segments_center_of_mass.isel(time=[0])) def __set_meshes_from_q(self): for m, meshes in enumerate( self.meshPointsInMatrix.get_data(Q=self.Q, compute_kin=False)): self.mesh[m][0:3, :, :] = meshes self.vtk_model.update_mesh(self.mesh) def __set_muscles_from_q(self): muscles = self.musclesPointsInGlobal.get_data(Q=self.Q) idx = 0 cmp = 0 for group_idx in range(self.model.nbMuscleGroups()): for muscle_idx in range( self.model.muscleGroup(group_idx).nbMuscles()): musc = self.model.muscleGroup(group_idx).muscle(muscle_idx) for k, pts in enumerate( musc.position().musclesPointsInGlobal()): self.muscles[idx].loc[{ "channel": k, "time": 0 }] = np.append(muscles[cmp], 1) cmp += 1 idx += 1 self.vtk_model.update_muscle(self.muscles) def __set_wrapping_from_q(self): for i, wraps in enumerate(self.wraps_base): for j, wrap in enumerate(wraps): if self.model.muscle(i).pathModifier().object( j).typeOfNode() == biorbd.WRAPPING_HALF_CYLINDER: rt = biorbd.WrappingHalfCylinder( self.model.muscle(i).pathModifier().object(j)).RT( self.model, self.Q).to_array() self.wraps_current[i][j][0:3, :, 0] = np.dot(rt, wrap[:, :, 0])[0:3, :] else: raise NotImplementedError( "__set_wrapping_from_q is not ready for these wrapping object" ) self.vtk_model.update_wrapping(self.wraps_current) def __set_rt_from_q(self): for k, rt in enumerate( self.allGlobalJCS.get_data(Q=self.Q, compute_kin=False)): self.rt[k] = Rototrans(rt) self.vtk_model.update_rt(self.rt)
def test_rt_from_markers(): all_m = Markers.from_random_data() rt_xy = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 1]), axis_2=all_m.isel(channel=[0, 2]), axes_name="xy", axis_to_recalculate="y", ) rt_yx = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 2]), axis_2=all_m.isel(channel=[0, 1]), axes_name="yx", axis_to_recalculate="y", ) rt_xy_x_recalc = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 1]), axis_2=all_m.isel(channel=[0, 2]), axes_name="yx", axis_to_recalculate="x", ) rt_xy_x_recalc = rt_xy_x_recalc.isel(col=[1, 0, 2, 3]) rt_xy_x_recalc[:, 2, :] = -rt_xy_x_recalc[:, 2, :] rt_yz = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 1]), axis_2=all_m.isel(channel=[0, 2]), axes_name="yz", axis_to_recalculate="z", ) rt_zy = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 2]), axis_2=all_m.isel(channel=[0, 1]), axes_name="zy", axis_to_recalculate="z", ) rt_xy_from_yz = rt_yz.isel(col=[1, 2, 0, 3]) rt_xz = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 1]), axis_2=all_m.isel(channel=[0, 2]), axes_name="xz", axis_to_recalculate="z", ) rt_zx = Rototrans.from_markers( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 2]), axis_2=all_m.isel(channel=[0, 1]), axes_name="zx", axis_to_recalculate="z", ) rt_xy_from_zx = rt_xz.isel(col=[0, 2, 1, 3]) rt_xy_from_zx[:, 2, :] = -rt_xy_from_zx[:, 2, :] np.testing.assert_array_equal(rt_xy, rt_xy_x_recalc) np.testing.assert_array_equal(rt_xy, rt_yx) np.testing.assert_array_equal(rt_yz, rt_zy) np.testing.assert_array_equal(rt_xz, rt_zx) np.testing.assert_array_equal(rt_xy, rt_xy_from_yz) np.testing.assert_array_equal(rt_xy, rt_xy_from_zx) # Produce one that we know the solution ref_m = Markers( np.array(((1, 2, 3), (4, 5, 6), (6, 5, 4))).T[:, :, np.newaxis]) rt_xy_from_known_m = Rototrans.from_markers( origin=ref_m.isel(channel=[0]), axis_1=ref_m.isel(channel=[0, 1]), axis_2=ref_m.isel(channel=[0, 2]), axes_name="xy", axis_to_recalculate="y", ) rt_xy_expected = Rototrans( np.array([ [0.5773502691896257, 0.7071067811865475, -0.408248290463863, 1.0], [0.5773502691896257, 0.0, 0.816496580927726, 2.0], [0.5773502691896257, -0.7071067811865475, -0.408248290463863, 3.0], [0, 0, 0, 1.0], ])) np.testing.assert_array_equal(rt_xy_from_known_m, rt_xy_expected) exception_default_params = dict( origin=all_m.isel(channel=[0]), axis_1=all_m.isel(channel=[0, 1]), axis_2=all_m.isel(channel=[0, 2]), axes_name="xy", axis_to_recalculate="y", ) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(origin=all_m.isel(channel=[0, 1])) }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axis_1=all_m.isel(channel=[0])) }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axis_2=all_m.isel(channel=[0])) }) with pytest.raises(ValueError): Rototrans.from_markers( **{ **exception_default_params, **dict(axis_1=all_m.isel(channel=[0, 1], time=slice(None, 50))), }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axes_name="yyz") }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axes_name="xxz") }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axes_name="zzz") }) with pytest.raises(ValueError): Rototrans.from_markers(**{ **exception_default_params, **dict(axis_to_recalculate="h") })