Skip to content

Commit fcaf7d9

Browse files
authored
Merge pull request #43 from childmindresearch/alperkent/clean-up-documentation
2 parents bf344cb + a5c7882 commit fcaf7d9

11 files changed

Lines changed: 393 additions & 141 deletions

File tree

.github/workflows/pypi.yaml

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
name: Publish to PyPI
22

33
permissions:
4-
actions: write
4+
contents: write # Required for creating releases and tags
5+
actions: read
6+
id-token: write # Required for OIDC
57

68
on:
79
workflow_run:
@@ -12,40 +14,123 @@ on:
1214
- main
1315

1416
jobs:
17+
check-version:
18+
name: Check Version Change
19+
runs-on: ubuntu-latest
20+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
21+
outputs:
22+
version-changed: ${{ steps.version-check.outputs.changed }}
23+
new-version: ${{ steps.version-check.outputs.version }}
24+
steps:
25+
- name: Checkout code
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 2 # Need previous commit for comparison
29+
30+
- name: Check version change
31+
id: version-check
32+
run: |
33+
# Extract current version from pyproject.toml
34+
current_version=$(grep -Po '(?<=^version = ")[^"]*' pyproject.toml)
35+
echo "Current version: $current_version"
36+
37+
# Check if version changed in last commit
38+
if git diff HEAD~1 HEAD pyproject.toml | grep -q "^[+-]version ="; then
39+
echo "Version changed in this commit"
40+
echo "changed=true" >> "$GITHUB_OUTPUT"
41+
echo "version=$current_version" >> "$GITHUB_OUTPUT"
42+
else
43+
echo "Version unchanged - skipping release"
44+
echo "changed=false" >> "$GITHUB_OUTPUT"
45+
fi
46+
1547
pypi-release:
1648
name: PyPI Release
1749
runs-on: ubuntu-latest
18-
if: ${{ github.event.workflow_run.conclusion == 'success' }}
50+
needs: check-version
51+
if: ${{ needs.check-version.outputs.version-changed == 'true' }}
1952
environment:
2053
name: pypi
2154
url: https://pypi.org/project/graphomotor/
55+
timeout-minutes: 10
2256

2357
steps:
24-
- uses: actions/checkout@v4
58+
- name: Checkout code
59+
uses: actions/checkout@v4
2560
with:
26-
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
61+
fetch-depth: 1 # Only need current commit for build
2762

28-
- uses: actions/setup-python@v5
63+
- name: Set up Python
64+
uses: actions/setup-python@v5
2965
with:
3066
python-version-file: pyproject.toml
3167

32-
- name: Skip release if version unchanged
33-
run: |
34-
version_change=$(git diff -r HEAD^1 pyproject.toml | grep -E "^(\+|-)version =")
35-
if [[ -z "$version_change" ]]; then
36-
gh run cancel ${{ github.run_id }}
37-
gh run watch ${{ github.run_id }}
38-
fi
39-
env:
40-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41-
4268
- name: Install uv
4369
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
4470
with:
4571
enable-cache: true
4672

47-
- name: Build package
48-
run: uv build
73+
- name: Cache uv dependencies
74+
uses: actions/cache@v4
75+
with:
76+
path: ~/.cache/uv
77+
key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }}
78+
restore-keys: |
79+
${{ runner.os }}-uv-
80+
81+
- name: Validate package metadata
82+
run: |
83+
echo "Building version: ${{ needs.check-version.outputs.new-version }}"
84+
uv build --wheel --sdist
85+
ls -la dist/
86+
87+
# Validate package contents
88+
uv run python -c "
89+
import tarfile
90+
import zipfile
91+
import os
92+
93+
# Check sdist contents
94+
for f in os.listdir('dist'):
95+
if f.endswith('.tar.gz'):
96+
with tarfile.open(f'dist/{f}', 'r:gz') as tar:
97+
print(f'SDist contents ({f}):')
98+
for member in tar.getnames()[:10]: # Show first 10 files
99+
print(f' {member}')
100+
elif f.endswith('.whl'):
101+
with zipfile.ZipFile(f'dist/{f}', 'r') as zip_file:
102+
print(f'Wheel contents ({f}):')
103+
for member in zip_file.namelist()[:10]: # Show first 10 files
104+
print(f' {member}')
105+
"
106+
107+
- name: Upload build artifacts
108+
uses: actions/upload-artifact@v4
109+
with:
110+
name: python-package-distributions
111+
path: dist/
112+
retention-days: 30
49113

50114
- name: Publish to PyPI
51115
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
116+
with:
117+
print-hash: true
118+
verbose: true
119+
# OIDC authentication - no API token needed
120+
121+
- name: Create GitHub Release
122+
env:
123+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124+
run: |
125+
gh release create "v${{ needs.check-version.outputs.new-version }}" \
126+
--title "Release v${{ needs.check-version.outputs.new-version }}" \
127+
--notes "## What's Changed
128+
129+
Automated release for version ${{ needs.check-version.outputs.new-version }}
130+
131+
### Package Information
132+
- **PyPI**: https://pypi.org/project/graphomotor/${{ needs.check-version.outputs.new-version }}/
133+
- **Changelog**: See commit history for detailed changes
134+
135+
Full package verification and testing completed via CI pipeline." \
136+
dist/*

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,25 @@ Welcome to `graphomotor`, a specialized Python library for analyzing graphomotor
1818
1919
## Feature Extraction Capabilities
2020

21-
The toolkit extracts 25 clinically relevant metrics from digitized drawing data. Currently implemented feature categories include:
21+
The toolkit extracts [25 clinically relevant metrics](https://childmindresearch.github.io/graphomotor/graphomotor/features.html) from digitized drawing data. Currently implemented feature categories include:
2222

23-
- **Velocity Features (15)**: Velocity analysis including linear, radial, and angular velocity components with statistical measures (sum, median, variation, skewness, kurtosis).
24-
- **Distance Features (8)**: Spatial accuracy measurements using Hausdorff distance metrics with temporal normalizations and segment-specific analysis.
25-
- **Drawing Error Features (1)**: Area under the curve (AUC) calculations between drawn paths and ideal reference trajectories to quantify spatial accuracy.
26-
- **Temporal Features (1)**: Task completion duration.
23+
- **[Velocity Features](https://childmindresearch.github.io/graphomotor/graphomotor/features/velocity.html) (15)**: Velocity analysis including linear, radial, and angular velocity components with statistical measures (sum, median, variation, skewness, kurtosis).
24+
- **[Distance Features](https://childmindresearch.github.io/graphomotor/graphomotor/features/distance.html) (8)**: Spatial accuracy measurements using Hausdorff distance metrics with temporal normalizations and segment-specific analysis.
25+
- **[Drawing Error Features](https://childmindresearch.github.io/graphomotor/graphomotor/features/drawing_error.html) (1)**: Area under the curve (AUC) calculations between drawn paths and ideal reference trajectories to quantify spatial accuracy.
26+
- **[Temporal Features](https://childmindresearch.github.io/graphomotor/graphomotor/features/time.html) (1)**: Task completion duration.
2727

2828
## Feature Visualization Capabilities
2929

3030
The toolkit provides several plotting functions to visualize extracted features:
3131

32-
- **Distribution Plots**: Kernel density estimation plots showing feature distributions grouped by task type and hand.
33-
- **Trend Plots**: Line plots displaying feature progression across task sequences with individual participant trajectories and group means.
34-
- **Box Plots**: Box-and-whisker plots comparing feature distributions across different tasks and hand conditions.
35-
- **Cluster Heatmaps**: Hierarchically clustered heatmaps of z-score standardized features to identify patterns across conditions.
32+
- **[Distribution Plots](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature_plots.html#plot_feature_distributions)**: Kernel density estimation plots showing feature distributions grouped by task type and hand.
33+
- **[Trend Plots](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature_plots.html#plot_feature_trends)**: Line plots displaying feature progression across task sequences with individual participant trajectories and group means.
34+
- **[Box Plots](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature_plots.html#plot_feature_boxplots)**: Box-and-whisker plots comparing feature distributions across different tasks and hand conditions.
35+
- **[Cluster Heatmaps](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature_plots.html#plot_feature_clusters)**: Hierarchically clustered heatmaps of z-score standardized features to identify patterns across conditions.
3636

3737
## Installation
3838

39-
Install `graphomotor` from PyPI:
39+
Install `graphomotor` from [PyPI](https://pypi.org/project/graphomotor/):
4040

4141
```sh
4242
pip install graphomotor
@@ -225,7 +225,7 @@ fig.savefig(f"path/to/customized_boxplots.png", dpi=300)
225225
```
226226

227227
> [!NOTE]
228-
> For all available feature plotting options and the list of all 25 features extracted by the toolkit, refer to the [`feature_plots` documentation](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature-plots.html).
228+
> For all available feature plotting options, refer to the [`feature_plots` documentation](https://childmindresearch.github.io/graphomotor/graphomotor/plot/feature_plots.html).
229229
230230
## Development Progress
231231

src/graphomotor/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1-
""".. include:: ../../README.md""" # noqa: D415
1+
"""A Python toolkit for analysis of graphomotor data collected via Curious.
2+
3+
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15800191.svg)](https://doi.org/10.5281/zenodo.15800191)
4+
[![Build](https://github.com/childmindresearch/graphomotor/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/childmindresearch/graphomotor/actions/workflows/test.yaml?query=branch%3Amain)
5+
[![codecov](https://codecov.io/gh/childmindresearch/graphomotor/branch/main/graph/badge.svg?token=22HWWFWPW5)](https://codecov.io/gh/childmindresearch)
6+
7+
Welcome to `graphomotor`, a specialized Python library for analyzing graphomotor
8+
data collected via [Curious](https://www.gettingcurious.com/). This toolkit provides
9+
comprehensive tools for processing, analyzing, and visualizing data from various
10+
graphomotor assessment tasks.
11+
12+
For complete documentation, examples, and API reference, visit the
13+
[full documentation](https://childmindresearch.github.io/graphomotor/).
14+
"""

src/graphomotor/core/__init__.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,40 @@
1-
""".. include:: ../../README.md""" # noqa: D415
1+
"""Core functionality for graphomotor data processing and analysis.
2+
3+
This module provides the essential infrastructure for the graphomotor toolkit:
4+
5+
## Data Models and Validation
6+
7+
- **`Spiral`**: Pydantic model for spiral drawing data validation with strict
8+
metadata requirements (7-digit participant IDs starting with '5', Dom/NonDom
9+
hand designation, spiral_trace/spiral_recall tasks numbered 1-5)
10+
- **`FeatureCategories`**: Registry of all 4 feature extractors (duration,
11+
velocity, hausdorff, AUC) with dynamic extractor mapping
12+
13+
## Configuration and Logging
14+
15+
- **`SpiralConfig`**: Immutable dataclass for Archimedean spiral parameters
16+
(center coordinates, growth rate, angles, point density) with custom
17+
parameter override support
18+
- **Logger**: Centralized logging with configurable verbosity levels
19+
(WARNING/INFO/DEBUG) and structured formatting
20+
21+
## Pipeline Orchestration
22+
23+
- **`run_pipeline()`**: Main entry point supporting both single-file and
24+
batch directory processing with progress tracking, error handling, and
25+
configurable feature extraction
26+
- **Feature Extraction**: Coordinates spiral centering, reference generation,
27+
and execution of all feature extractors
28+
- **Export System**: Automated CSV export with metadata preservation and
29+
timestamp-based naming
30+
31+
## Command-Line Interface
32+
33+
- **`extract`**: CLI command for feature extraction with extensive configuration
34+
options and help documentation
35+
- **`plot-features`**: CLI command for generating publication-ready visualizations
36+
from extracted feature datasets
37+
38+
The core module serves as the foundation for all graphomotor operations, ensuring
39+
data quality, processing consistency, and user-friendly interfaces.
40+
"""
Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,84 @@
1-
""".. include:: ../../README.md""" # noqa: D415
1+
"""Feature extraction modules for graphomotor data analysis.
2+
3+
This module contains specialized extractors for computing clinically relevant metrics
4+
across four categories: velocity (15 features), distance (8 features), drawing error
5+
(1 feature), and time (1 feature). Each feature extractor follows a standardized
6+
interface and returns a dictionary of computed metrics with descriptive keys.
7+
8+
Available Features
9+
------------------
10+
The graphomotor pipeline extracts 25 clinically validated features organized into
11+
four categories. These features quantify different aspects of drawing performance
12+
and motor control:
13+
14+
**Distance-based Features (8 features):**
15+
16+
These features use Hausdorff distance to measure spatial accuracy between drawn and
17+
reference spirals. The Hausdorff distance captures the maximum distance from any point
18+
in one set to the nearest point in another set.
19+
20+
- **hausdorff_distance_maximum**: Maximum directed Hausdorff distance between drawn
21+
and reference spiral points
22+
- **hausdorff_distance_sum**: Sum of directed Hausdorff distances
23+
- **hausdorff_distance_sum_per_second**: Sum of Hausdorff distances normalized by
24+
total drawing duration
25+
- **hausdorff_distance_interquartile_range**: Interquartile range of directed
26+
Hausdorff distances, measuring variability in spatial accuracy
27+
- **hausdorff_distance_start_segment_maximum_normalized**: Maximum Hausdorff distance
28+
in beginning segment (0-25%) normalized by segment length
29+
- **hausdorff_distance_end_segment_maximum_normalized**: Maximum Hausdorff distance
30+
in ending segment (75-100%) normalized by segment length
31+
- **hausdorff_distance_middle_segment_maximum**: Maximum Hausdorff distance in middle
32+
segment (15-85%) without normalization
33+
- **hausdorff_distance_middle_segment_maximum_per_second**: Middle segment maximum
34+
Hausdorff distance normalized by total drawing duration
35+
36+
**Velocity-based Features (15 features):**
37+
38+
These features analyze movement dynamics by computing velocity in three coordinate
39+
systems, each providing 5 statistical measures.
40+
41+
*Linear velocity (5 features):*
42+
Euclidean velocity magnitude in Cartesian coordinates (pixels/second).
43+
44+
- **linear_velocity_sum**: Sum of absolute linear velocity values
45+
- **linear_velocity_median**: Median of absolute linear velocity values
46+
- **linear_velocity_coefficient_of_variation**: Ratio of standard deviation to mean,
47+
measuring velocity consistency
48+
- **linear_velocity_skewness**: Asymmetry of velocity distribution (positive =
49+
right-skewed)
50+
- **linear_velocity_kurtosis**: Tailedness of velocity distribution (higher = more
51+
extreme values)
52+
53+
*Radial velocity (5 features):*
54+
Rate of change in distance from center (pixels/second).
55+
56+
- **radial_velocity_sum**: Sum of absolute radial velocity values
57+
- **radial_velocity_median**: Median of absolute radial velocity values
58+
- **radial_velocity_coefficient_of_variation**: Consistency of radial movement speed
59+
- **radial_velocity_skewness**: Asymmetry of radial velocity distribution (positive =
60+
right-skewed)
61+
- **radial_velocity_kurtosis**: Tailedness of radial velocity distribution
62+
(higher = more extreme values)
63+
64+
*Angular velocity (5 features):*
65+
Rate of angular rotation around center (radians/second).
66+
67+
- **angular_velocity_sum**: Sum of absolute angular velocity values
68+
- **angular_velocity_median**: Median of absolute angular velocity values
69+
- **angular_velocity_coefficient_of_variation**: Consistency of rotational speed
70+
- **angular_velocity_skewness**: Asymmetry of angular velocity distribution (positive =
71+
right-skewed)
72+
- **angular_velocity_kurtosis**: Tailedness of angular velocity distribution
73+
(higher = more extreme values)
74+
75+
**Time-based Features (1 feature):**
76+
77+
- **duration**: Total time taken to complete the spiral drawing task (seconds)
78+
79+
**Drawing Error Features (1 feature):**
80+
81+
- **area_under_curve**: Total enclosed area between drawn and reference spiral paths,
82+
computed using polygon intersection algorithms. Lower values indicate better
83+
adherence to the template spiral.
84+
"""

src/graphomotor/features/distance.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,32 @@ def calculate_hausdorff_metrics(
3939
is based on the original R script provided with the publication. The Hausdorff
4040
distance measures the maximum distance of a set to the nearest point in the other
4141
set. This metric and its derivatives capture various aspects of the spatial
42-
relationship between the drawn and reference spirals. Calculated features include:
43-
- hausdorff_distance_maximum: The maximum of the directed Hausdorff distances
44-
between the data points and the reference data points,
45-
- hausdorff_distance_sum: The sum of the directed Hausdorff distances,
46-
- hausdorff_distance_sum_per_second: The sum of the directed Hausdorff distances
47-
divided by the total drawing duration,
48-
- hausdorff_distance_interquartile_range: The interquartile range of the
49-
directed Hausdorff distances,
50-
- hausdorff_distance_start_segment_maximum_normalized: The maximum of the
51-
directed Hausdorff distances between the beginning segment (0% to 25%) of
52-
data points and the beginning segment of reference data points divided by
53-
the number of data points in the beginning segment,
54-
- hausdorff_distance_end_segment_maximum_normalized: The maximum of the directed
55-
Hausdorff distances in the ending segment (75% to 100%) of data points and
56-
the ending segment of reference data points divided by the number of data
57-
points in the ending segment,
58-
- hausdorff_distance_middle_segment_maximum: The maximum of the directed
59-
Hausdorff distances in the middle segment (15% to 85%) of data points and
60-
the ending segment of reference data points (this metric is not divided by
61-
the number of data points in the middle segment unlike previous ones),
62-
- hausdorff_distance_middle_segment_maximum_per_second: The maximum of the
63-
directed Hausdorff distances in the middle segment divided by the total
64-
drawing duration.
42+
relationship between the drawn and reference spirals.
43+
44+
Calculated features include:
45+
46+
- **hausdorff_distance_maximum**: The maximum of the directed Hausdorff distances
47+
between the data points and the reference data points
48+
- **hausdorff_distance_sum**: The sum of the directed Hausdorff distances
49+
- **hausdorff_distance_sum_per_second**: The sum of the directed Hausdorff distances
50+
divided by the total drawing duration
51+
- **hausdorff_distance_interquartile_range**: The interquartile range of the
52+
directed Hausdorff distances
53+
- **hausdorff_distance_start_segment_maximum_normalized**: The maximum of the
54+
directed Hausdorff distances between the beginning segment (0% to 25%) of
55+
data points and the beginning segment of reference data points divided by
56+
the number of data points in the beginning segment
57+
- **hausdorff_distance_end_segment_maximum_normalized**: The maximum of the directed
58+
Hausdorff distances in the ending segment (75% to 100%) of data points and
59+
the ending segment of reference data points divided by the number of data
60+
points in the ending segment
61+
- **hausdorff_distance_middle_segment_maximum**: The maximum of the directed
62+
Hausdorff distances in the middle segment (15% to 85%) of data points and
63+
the ending segment of reference data points (this metric is not divided by
64+
the number of data points in the middle segment unlike previous ones)
65+
- **hausdorff_distance_middle_segment_maximum_per_second**: The maximum of the
66+
directed Hausdorff distances in the middle segment divided by the total
67+
drawing duration
6568
6669
Args:
6770
spiral: Spiral object with drawing data.

0 commit comments

Comments
 (0)