Skip to content

Commit 1a95356

Browse files
authored
Merge pull request #20 from childmindresearch/alperkent/issue19
feature/issue-19/implement-drawing-error-features
2 parents cebeeb0 + 0723f64 commit 1a95356

5 files changed

Lines changed: 99 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ requires-python = ">=3.12"
1111
dependencies = [
1212
"pandas>=2.2.3",
1313
"pydantic>=2.11.1",
14-
"scipy>=1.15.2"
14+
"scipy>=1.15.2",
15+
"shapely>=2.1.0"
1516
]
1617

1718
[dependency-groups]

src/graphomotor/features/distance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _segment_data(data: np.ndarray, start_prop: float, end_prop: float) -> np.nd
3131

3232
def calculate_hausdorff_metrics(
3333
spiral: models.Spiral, reference_spiral: np.ndarray
34-
) -> dict:
34+
) -> dict[str, float]:
3535
"""Calculate Hausdorff distance metrics for a spiral object.
3636
3737
This function computes multiple features based on the Hausdorff distance between a
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Feature extraction module for drawing error-based metrics in spiral drawing data."""
2+
3+
import numpy as np
4+
from shapely import geometry, ops
5+
6+
from graphomotor.core import models
7+
8+
9+
def calculate_area_under_curve(
10+
drawn_spiral: models.Spiral, reference_spiral: np.ndarray
11+
) -> dict[str, float]:
12+
"""Calculate the area between drawn and reference spirals.
13+
14+
This function measures the deviation between drawn and reference spirals by
15+
computing the enclosed area between them using the shapely library. Lower values
16+
indicate better adherence to the template. The algorithm works by creating polygons
17+
that connect spiral endpoints, finding intersections between lines, and calculating
18+
the total area of the resulting polygons.
19+
20+
Args:
21+
drawn_spiral: The spiral drawn by the subject.
22+
reference_spiral: The reference spiral.
23+
24+
Returns:
25+
Dictionary containing the area under curve metric
26+
"""
27+
spiral = drawn_spiral.data[["x", "y"]].values
28+
line_drawn = geometry.LineString(spiral)
29+
line_reference = geometry.LineString(reference_spiral)
30+
first_segment = geometry.LineString([spiral[0], reference_spiral[0]])
31+
last_segment = geometry.LineString([spiral[-1], reference_spiral[-1]])
32+
merged_line = ops.unary_union(
33+
[line_drawn, line_reference, first_segment, last_segment]
34+
)
35+
polygons = list(ops.polygonize(merged_line))
36+
return {"area_under_curve": sum(p.area for p in polygons)}

tests/unit/test_drawing_error.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Test cases for drawing_error.py functions."""
2+
3+
import numpy as np
4+
import pandas as pd
5+
6+
from graphomotor.core import models
7+
from graphomotor.features import drawing_error
8+
9+
10+
def test_calculate_area_under_curve(valid_spiral: models.Spiral) -> None:
11+
"""Test that the area under the curve is calculated correctly."""
12+
x = np.linspace(-np.pi / 2, 3 * np.pi / 2, 100)
13+
y1 = np.sin(x)
14+
y2 = np.sin(x + np.pi)
15+
16+
expected_area = 8.0
17+
18+
valid_spiral.data = pd.DataFrame({"x": x, "y": y1})
19+
calculated_area = drawing_error.calculate_area_under_curve(
20+
valid_spiral, np.column_stack((x, y2))
21+
)["area_under_curve"]
22+
23+
assert np.isclose(calculated_area, expected_area, rtol=1e-3)

uv.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)