Skip to content

Commit 35f3028

Browse files
author
André Böni
committed
+ clean minimal toe clearance
1 parent c4a2de4 commit 35f3028

File tree

2 files changed

+58
-51
lines changed

2 files changed

+58
-51
lines changed

gaitalytics/features.py

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -523,32 +523,41 @@ def _calculate(self, trial: model.Trial) -> xr.DataArray:
523523
marker_dict = self.select_markers_for_spatial_features(trial)
524524

525525
results_dict = self._calculate_step_length(
526-
trial, marker_dict["ipsi_heel"], marker_dict["contra_heel"]
526+
trial,
527+
marker_dict["ipsi_heel"], # type: ignore
528+
marker_dict["contra_heel"], # type: ignore
527529
)
528530
results_dict.update(
529531
self._calculate_step_width(
530-
trial, marker_dict["ipsi_heel"], marker_dict["contra_heel"]
532+
trial,
533+
marker_dict["ipsi_heel"], # type: ignore
534+
marker_dict["contra_heel"], # type: ignore
531535
)
532536
)
533537
results_dict.update(
534538
self._calculate_stride_length(
535-
trial, marker_dict["ipsi_heel"], marker_dict["contra_heel"]
539+
trial,
540+
marker_dict["ipsi_heel"], # type: ignore
541+
marker_dict["contra_heel"], # type: ignore
536542
)
537543
)
544+
545+
toe_markers = [marker_dict["ipsi_toe_2"]]
546+
if marker_dict["ipsi_toe_5"] is not None:
547+
toe_markers.append(marker_dict["ipsi_toe_5"])
538548
results_dict.update(
539549
self._calculate_minimal_toe_clearance(
540550
trial,
541-
marker_dict["ipsi_toe_2"],
542-
marker_dict["ipsi_heel"],
543-
marker_dict["ipsi_toe_5"],
551+
toe_markers, # type: ignore
552+
marker_dict["ipsi_heel"], # type: ignore
544553
)
545554
)
546555
if marker_dict["xcom"] is not None:
547556
results_dict.update(
548557
self._calculate_ap_margin_of_stability(
549558
trial,
550-
marker_dict["ipsi_toe_2"],
551-
marker_dict["contra_toe_2"],
559+
marker_dict["ipsi_toe_2"], # type: ignore
560+
marker_dict["contra_toe_2"], # type: ignore
552561
marker_dict["xcom"],
553562
)
554563
)
@@ -568,7 +577,7 @@ def _calculate(self, trial: model.Trial) -> xr.DataArray:
568577

569578
def select_markers_for_spatial_features(
570579
self, trial: model.Trial
571-
) -> dict[str, mapping.MappedMarkers]:
580+
) -> dict[str, mapping.MappedMarkers | None]:
572581
"""Select markers based on the trial's context (Right or Left). If some markers are missing, return them as None
573582
574583
Args:
@@ -736,63 +745,63 @@ def _calculate_stride_length(
736745
def _calculate_minimal_toe_clearance(
737746
self,
738747
trial: model.Trial,
739-
ipsi_toe_marker: mapping.MappedMarkers,
748+
ipsi_toe_markers: list[mapping.MappedMarkers],
740749
ipsi_heel_marker: mapping.MappedMarkers,
741-
*opt_ipsi_toe_markers: mapping.MappedMarkers,
742750
) -> dict[str, np.ndarray]:
743-
"""Calculate the minimal toe clearance for a trial. Toe clearance is computed for all toe markers passed, only the minimal is returned
751+
"""Calculate the minimal toe clearance for a trial. <br />
752+
Toe clearance is computed for all toe markers passed, only the minimal is returned. <br />
753+
Addition toe markers can be passed to improve the accuracy of the calculation. <br />
754+
If the minimal toe clearance cannot be calculated, an empty array is returned. <br />
744755
745756
Args:
746-
trial (model.Trial): The trial to compute the minimal toe clearance for
747-
ipsi_toe_marker (mapping.MappedMarkers): The ipsilateral toe marker
748-
ipsi_heel_marker (mapping.MappedMarkers): The ipsilateral heel marker
757+
trial: The trial to compute the minimal toe clearance for
758+
ipsi_toe_markers: The ipsi-lateral toe markers
759+
ipsi_heel_marker (mapping.MappedMarkers): The ipsi-lateral heel marker
749760
750761
Returns:
751762
dict[str, np.ndarray]: The calculated minimal toe clearance in a dict
763+
764+
Raises:
765+
ValueError: If no toe markers are found for minimal toe clearance calculation
752766
"""
753767
event_times = self.get_event_times(trial.events)
754768

755769
ipsi_heel = self._get_marker_data(trial, ipsi_heel_marker).sel(
756770
time=slice(event_times[3], event_times[4])
757771
)
758-
ipsi_toe = self._get_marker_data(trial, ipsi_toe_marker).sel(
759-
time=slice(event_times[3], event_times[4])
760-
)
772+
ipsi_toes = []
773+
ipsi_toe_velocities = None
761774

762-
toes_vel = linalg.calculate_speed_norm(ipsi_toe)
763-
764-
additional_meta_data = []
765-
766-
for meta_marker in opt_ipsi_toe_markers:
767-
if meta_marker is not None:
768-
meta_data = self._get_marker_data(trial, meta_marker).sel(
769-
time=slice(event_times[3], event_times[4])
770-
)
771-
toes_vel += linalg.calculate_speed_norm(meta_data)
772-
additional_meta_data.append(meta_data)
773-
774-
toes_vel /= 1 + len(additional_meta_data)
775+
# Get all ipsi toe marker positions and calculate the mean velocity of them
776+
for ipsi_toe_marker in ipsi_toe_markers:
777+
ipsi_toe = self._get_marker_data(trial, ipsi_toe_marker).sel(
778+
time=slice(event_times[3], event_times[4])
779+
)
780+
ipsi_toes.append(ipsi_toe)
781+
if ipsi_toe_velocities is None:
782+
ipsi_toe_velocities = linalg.calculate_speed_norm(ipsi_toe)
783+
else:
784+
ipsi_toe_velocities += linalg.calculate_speed_norm(ipsi_toe)
775785

776-
mtc_i = self._find_mtc_index(ipsi_toe, ipsi_heel, toes_vel)
777-
mtc_additional_indices = [
778-
self._find_mtc_index(meta_data, ipsi_heel, toes_vel)
779-
for meta_data in additional_meta_data
780-
]
786+
if ipsi_toe_velocities is None:
787+
raise ValueError(
788+
"No toe markers found for minimal toe clearance calculation"
789+
)
781790

782-
# Handle NaN cases and find minimal clearance
783-
mtc_values = [] if np.isnan(mtc_i) else [ipsi_toe.sel(axis="z")[mtc_i]]
784-
for i, meta_data in zip(mtc_additional_indices, additional_meta_data):
785-
if not np.isnan(i):
786-
mtc_values.append(meta_data.sel(axis="z")[i])
791+
ipsi_toe_velocities /= len(ipsi_toe_markers)
787792

788-
if not mtc_values:
789-
return {"minimal_toe_clearance": np.empty(1)}
793+
minimal_toe_clearance = np.full(1, np.inf)
794+
for ipsi_toe in ipsi_toes:
795+
i = self._find_mtc_index(ipsi_toe, ipsi_heel, ipsi_toe_velocities)
796+
clearance = ipsi_toe.sel(axis="z")[i].values
797+
if clearance < minimal_toe_clearance:
798+
minimal_toe_clearance = clearance
790799

791-
return {"minimal_toe_clearance": min(mtc_values)}
800+
return {"minimal_toe_clearance": minimal_toe_clearance}
792801

793802
@staticmethod
794803
def _find_mtc_index(
795-
toe_position: xr.DataArray, heel_position: xr.DataArray, toes_vel: xr.DataArray
804+
toe_position: xr.DataArray, heel_position: xr.DataArray, toes_vel: np.ndarray
796805
):
797806
"""Find the time corresponding to minimal toe clearance of a specific toe.
798807
Valid minimal toe clearance point must pass conditions
@@ -813,7 +822,7 @@ def _find_mtc_index(
813822
mtc_i = [i for i in mtc_i if toes_vel[i] >= toes_vel_up_quant]
814823
mtc_i = [i for i in mtc_i if toe_z[i] <= heel_z[i]]
815824

816-
return None if not mtc_i else min(mtc_i, key=lambda i: toe_z[i])
825+
return None if not mtc_i else min(mtc_i, key=lambda i: toe_z[i]) # type: ignore
817826

818827
def _calculate_ap_margin_of_stability(
819828
self,

gaitalytics/utils/linalg.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def normalize_vector(vector: xr.DataArray) -> xr.DataArray:
6161
return vector / vector.meca.norm(dim="axis")
6262

6363

64-
def calculate_speed_norm(position: xr.DataArray, dt: float = 0.01) -> xr.DataArray:
64+
def calculate_speed_norm(position: xr.DataArray, dt: float = 0.01) -> np.ndarray:
6565
"""
6666
Compute the speed from a 3xN position data array obtained with constant sampling rate
6767
@@ -71,7 +71,7 @@ def calculate_speed_norm(position: xr.DataArray, dt: float = 0.01) -> xr.DataArr
7171
dt: Time interval between samples.
7272
7373
Returns:
74-
A 1D xarray.DataArray of velocity at each time point.
74+
An np array with the speed values.
7575
"""
7676
velocity_squared_sum = sum(
7777
(np.diff(position.sel(axis=axis).values) / dt) ** 2
@@ -80,6 +80,4 @@ def calculate_speed_norm(position: xr.DataArray, dt: float = 0.01) -> xr.DataArr
8080
speed_values = np.sqrt(velocity_squared_sum)
8181
speed_values = np.append(speed_values, speed_values[-1])
8282

83-
return xr.DataArray(
84-
speed_values, dims=["time"], coords={"time": position.coords["time"]}
85-
)
83+
return speed_values

0 commit comments

Comments
 (0)