Skip to content

Commit 9911b5a

Browse files
authored
Merge pull request #30 from childmindresearch/alperkent/issue1
chore/issue1/update-metadata
2 parents 2cee2aa + ab83e70 commit 9911b5a

7 files changed

Lines changed: 211 additions & 32 deletions

File tree

.github/workflows/pypi.yaml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Publish to PyPI
2+
3+
permissions:
4+
id-token: write # Required for trusted publishing
5+
contents: read # Required to checkout code
6+
actions: write # Required to cancel runs
7+
8+
on:
9+
workflow_run:
10+
workflows: [Python Tests]
11+
types:
12+
- completed
13+
branches:
14+
- main
15+
16+
jobs:
17+
pypi-release:
18+
name: PyPI Release
19+
runs-on: ubuntu-latest
20+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
21+
environment:
22+
name: pypi
23+
url: https://pypi.org/project/graphomotor/
24+
25+
steps:
26+
- uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 2 # Need 2 commits to compare for version changes
29+
30+
- uses: actions/setup-python@v5
31+
with:
32+
python-version-file: pyproject.toml
33+
34+
- name: Install uv
35+
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
36+
with:
37+
enable-cache: true
38+
39+
- name: Check if version changed
40+
id: version-check
41+
run: |
42+
if git diff HEAD^1 HEAD --name-only | grep -q "pyproject.toml"; then
43+
version_change=$(git diff HEAD^1 HEAD pyproject.toml | grep -E "^(\+|-)version =")
44+
if [[ -n "$version_change" ]]; then
45+
echo "version_changed=true" >> $GITHUB_OUTPUT
46+
echo "Version changed detected:"
47+
echo "$version_change"
48+
else
49+
echo "version_changed=false" >> $GITHUB_OUTPUT
50+
echo "pyproject.toml changed but version did not change"
51+
fi
52+
else
53+
echo "version_changed=false" >> $GITHUB_OUTPUT
54+
echo "pyproject.toml did not change"
55+
fi
56+
57+
- name: Skip release if version unchanged
58+
if: steps.version-check.outputs.version_changed == 'false'
59+
run: |
60+
echo "No version change detected, skipping release"
61+
gh run cancel ${{ github.run_id }}
62+
env:
63+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64+
65+
- name: Build package
66+
if: steps.version-check.outputs.version_changed == 'true'
67+
run: uv build
68+
69+
- name: Verify build artifacts
70+
if: steps.version-check.outputs.version_changed == 'true'
71+
run: |
72+
ls -la dist/
73+
python -c "import tarfile; tarfile.open('dist/*.tar.gz').getnames()" || true
74+
75+
- name: Publish to PyPI
76+
if: steps.version-check.outputs.version_changed == 'true'
77+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
78+
# Alternative: use uv publish with trusted publishing
79+
# run: uv publish --token ${{ secrets.PYPI_API_TOKEN }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# data
2+
data/
3+
4+
# scripts
5+
scripts/
6+
17
# experiments
28
experiments/
39

CODE_OF_CONDUCT.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ representative at an online or offline event.
6060

6161
Instances of abusive, harassing, or otherwise unacceptable behavior may be
6262
reported to the community leaders responsible for enforcement at
63-
mobi@childmind.org.
63+
<mobi@childmind.org>.
6464

6565
All complaints will be reviewed and investigated promptly and fairly.
6666

@@ -117,13 +117,13 @@ the community.
117117

118118
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119119
version 2.0, available at
120-
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120+
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
121121

122122
Community Impact Guidelines were inspired by [Mozilla's code of conduct
123123
enforcement ladder](https://github.com/mozilla/diversity).
124124

125125
[homepage]: https://www.contributor-covenant.org
126126

127127
For answers to common questions about this code of conduct, see the FAQ at
128-
https://www.contributor-covenant.org/faq. Translations are available at
129-
https://www.contributor-covenant.org/translations.
128+
<https://www.contributor-covenant.org/faq>. Translations are available at
129+
<https://www.contributor-covenant.org/translations>.

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ Pull requests are always welcome, and we appreciate any help you give. Note that
55
When submitting a pull request, we ask you to check the following:
66

77
1. **Unit tests**, **documentation**, and **code style** are in order.
8-
See the Continuous Integration for up to date information on the current code style, tests, and any other requirements.
8+
See the Continuous Integration for up-to-date information on the current code style, tests, and any other requirements.
9+
10+
See the following [Google Python style guide](https://google.github.io/styleguide/pyguide.html#3164-guidelines-derived-from-guidos-recommendations) for coding style guidelines.
911

1012
It is also OK to submit work in progress if you're unsure of what this exactly means, in which case you'll likely be asked to make some further changes.
1113

README.md

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,132 @@
11
# Graphomotor Study Toolkit
22

3+
A Python toolkit for analysis of graphomotor data collected via Curious.
4+
5+
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15800191.svg)](https://doi.org/10.5281/zenodo.15800191)
6+
37
[![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)
48
[![codecov](https://codecov.io/gh/childmindresearch/graphomotor/branch/main/graph/badge.svg?token=22HWWFWPW5)](https://codecov.io/gh/childmindresearch/graphomotor)
59
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
610
![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
711
[![LGPL--2.1 License](https://img.shields.io/badge/license-LGPL--2.1-blue.svg)](https://github.com/childmindresearch/graphomotor/blob/main/LICENSE)
8-
[![pages](https://img.shields.io/badge/api-docs-blue)](https://childmindresearch.github.io/graphomotor)
12+
[![Documentation](https://img.shields.io/badge/api-docs-blue)](https://childmindresearch.github.io/graphomotor)
13+
14+
Welcome to `graphomotor`, a specialized Python library for analyzing graphomotor data collected via [Curious](https://www.gettingcurious.com/). This toolkit provides comprehensive tools for processing, analyzing, and visualizing data from various graphomotor assessment tasks including spiral drawing, trails making, alphabetic writing, digit symbol substitution, and the Rey-Osterrieth Complex Figure Test.
915

10-
A Python toolkit for the analysis of Graphomotor study.
16+
## Development Progress
1117

12-
## Progress
18+
⚠️ **This package is under active development.** Currently, the focus is on the Spiral task. After finalizing feature extraction, the next steps will involve implementing both preprocessing and visualization for this task. Once these parts are in place, we plan to extend support to other tasks.
1319

14-
| Task name | Preprocessing | Feature extraction | Visualization |
20+
| Task | Preprocessing | Feature Extraction | Visualization |
1521
| :--- | :---: | :---: | :---: |
16-
| Spiral | ![data_cleaning](https://img.shields.io/badge/pending-red) | ![feature_extraction](https://img.shields.io/badge/in_progress-yellow) | ![visualization](https://img.shields.io/badge/pending-red) |
17-
| Rey-Osterrieth Complex Figure | ![data_cleaning](https://img.shields.io/badge/pending-red) | ![feature_extraction](https://img.shields.io/badge/pending-red) | ![visualization](https://img.shields.io/badge/pending-red) |
18-
| Alphabetic Writing | ![data_cleaning](https://img.shields.io/badge/pending-red)| ![feature_extraction](https://img.shields.io/badge/pending-red) | ![visualization](https://img.shields.io/badge/pending-red) |
19-
| Digit Symbol Substitute | ![data_cleaning](https://img.shields.io/badge/pending-red)| ![feature_extraction](https://img.shields.io/badge/pending-red) | ![visualization](https://img.shields.io/badge/pending-red) |
20-
| Trails Making | ![data_cleaning](https://img.shields.io/badge/pending-red) | ![feature_extraction](https://img.shields.io/badge/pending-red) | ![visualization](https://img.shields.io/badge/pending-red) |
22+
| Spiral | ![Spiral: Preprocessing Pending](https://img.shields.io/badge/pending-red) | ![Spiral: Feature Extraction In Progress](https://img.shields.io/badge/in_progress-yellow) | ![Spiral: Visualization Pending](https://img.shields.io/badge/pending-red) |
23+
| Rey-Osterrieth Complex Figure | ![Rey-Osterrieth: Preprocessing Pending](https://img.shields.io/badge/pending-red) | ![Rey-Osterrieth: Feature Extraction Pending](https://img.shields.io/badge/pending-red) | ![Rey-Osterrieth: Visualization Pending](https://img.shields.io/badge/pending-red) |
24+
| Alphabetic Writing | ![Alphabetic Writing: Preprocessing Pending](https://img.shields.io/badge/pending-red) | ![Alphabetic Writing: Feature Extraction Pending](https://img.shields.io/badge/pending-red) | ![Alphabetic Writing: Visualization Pending](https://img.shields.io/badge/pending-red) |
25+
| Digit Symbol Substitution | ![Digit Symbol Substitution: Preprocessing Pending](https://img.shields.io/badge/pending-red) | ![Digit Symbol Substitution: Feature Extraction Pending](https://img.shields.io/badge/pending-red) | ![Digit Symbol Substitution: Visualization Pending](https://img.shields.io/badge/pending-red) |
26+
| Trails Making | ![Trails Making: Preprocessing Pending](https://img.shields.io/badge/pending-red) | ![Trails Making: Feature Extraction Pending](https://img.shields.io/badge/pending-red) | ![Trails Making: Visualization Pending](https://img.shields.io/badge/pending-red) |
27+
28+
## Data Format Requirements
29+
30+
⚠️ **This implementation requires data to adhere to a specific format matching the standard output from [Curious drawing responses](https://mindlogger.atlassian.net/servicedesk/customer/portal/3/article/859242501).**
31+
32+
When exporting drawing data from Curious, you typically receive the following files:
33+
34+
- **report.csv**: Contains the participants' actual responses.
35+
- **activity_user_journey.csv**: Logs the entire journey through the activity, including button actions like "Next", "Skip", "Back", and "Undo", regardless of whether a response was provided.
36+
- **drawing-responses-{date}.zip**: A ZIP archive with raw drawing response CSV files for each participant (e.g., `drawing-responses-Mon May 29 2023.zip`).
37+
- **media-responses-{date}.zip**: A ZIP archive containing SVG files for the drawing responses (e.g., `media-responses-Mon May 29 2023.zip`).
38+
- **trails-responses-{date}.zip**: A ZIP archive with raw trail making response CSV files (if there are any) for each participant (e.g., `trails-responses-Mon May 29 2023.zip`).
39+
40+
For Spiral tasks, the toolkit uses only the CSV files from the drawing responses ZIP. Support for additional tasks will be added in future releases.
41+
42+
### File Naming Convention
43+
44+
Your spiral data files must follow this naming convention:
45+
46+
```text
47+
[5123456]a7f3b2e9-d4c8-f1a6-e5b9-c2d7f8a3e6b4-spiral_trace1_Dom.csv
48+
```
49+
50+
Where:
51+
52+
- **Participant ID**: Must be enclosed in brackets `[]` and be a 7-digit number starting with `5` (e.g., `[5123456]`) that matches the `target_secret_id` column in the **report.csv** file.
53+
- **Activity Submission ID**: Must be a 32-character hexadecimal string (e.g., `18f2-45ea-a1e4-2334e07cc706`) that matches the `id` column in the **report.csv** file.
54+
- **Task**: Must be one of the following that matches the `item` column in the **report.csv** file:
55+
- `spiral_trace1_Dom` through `spiral_trace5_Dom` (dominant hand tracing tasks)
56+
- `spiral_trace1_NonDom` through `spiral_trace5_NonDom` (non-dominant hand tracing tasks)
57+
- `spiral_recall1_Dom` through `spiral_recall3_Dom` (dominant hand recall tasks)
58+
- `spiral_recall1_NonDom` through `spiral_recall3_NonDom` (non-dominant hand recall tasks)
59+
60+
### Data Format
61+
62+
Your spiral data CSV file must contain the following columns:
63+
64+
```text
65+
line_number, x, y, UTC_Timestamp, seconds, epoch_time_in_seconds_start
66+
```
67+
68+
This format represents the standard output from [Curious drawing responses data dictionary](https://mindlogger.atlassian.net/servicedesk/customer/portal/3/article/596082739).
69+
70+
## Feature Extraction Capabilities
71+
72+
The toolkit extracts clinically relevant metrics from digitized drawing data. Currently implemented features include:
73+
74+
- **Temporal Features**: Task completion duration.
75+
- **Velocity Features**: Velocity analysis including linear, radial, and angular velocity components with statistical measures (sum, median, variation, skewness, kurtosis).
76+
- **Distance Features**: Spatial accuracy measurements using Hausdorff distance metrics with temporal normalizations and segment-specific analysis.
77+
- **Drawing Error Features**: Area under the curve (AUC) calculations between drawn paths and ideal reference trajectories to quantify spatial accuracy.
2178

2279
## Installation
2380

24-
Install the newest development version via :
81+
Install the graphomotor package from PyPI:
82+
83+
```sh
84+
pip install graphomotor
85+
```
86+
87+
Or install the latest development version directly from GitHub:
2588

2689
```sh
2790
pip install git+https://github.com/childmindresearch/graphomotor
2891
```
2992

30-
## Links or References
93+
## Quick Start
94+
95+
Currently, `graphomotor` is available as an importable Python library. CLI functionality is planned for future releases.
96+
97+
### Extracting Features from Spiral Drawing Data
98+
99+
```python
100+
from graphomotor.core import orchestrator
101+
102+
# Path to your spiral drawing data file
103+
input_file = "path/to/your/spiral_data.csv"
104+
105+
# Directory where extracted features will be saved
106+
output_dir = "path/to/output/directory"
107+
108+
# Run the analysis pipeline
109+
features = orchestrator.run_pipeline(
110+
input_path=input_file,
111+
output_path=output_dir
112+
)
113+
114+
# Features are returned as a dictionary and saved as CSV
115+
print(f"Successfully extracted {len(features)} feature categories")
116+
```
117+
118+
For detailed configuration options and additional parameters, refer to the [`run_pipeline` documentation](https://childmindresearch.github.io/graphomotor/graphomotor/core/orchestrator.html#run_pipeline).
119+
120+
> **Note:** Currently, only single file processing is supported, with batch processing planned for future releases.
121+
122+
## Future Directions
123+
124+
The Graphomotor Study Toolkit is under active development. For more detailed information about upcoming features and development plans, please refer to our [GitHub Issues](https://github.com/childmindresearch/graphomotor/issues) page.
125+
126+
## Contributing
127+
128+
We welcome contributions from the community! If you're interested in contributing, please review our [Contributing Guidelines](CONTRIBUTING.md) for information on how to get started, coding standards, and the pull request process.
129+
130+
## References
31131

32-
- [A very important resource](https://www.youtube.com/watch?v=dQw4w9WgXcQ)
132+
1. Messan, K. S., Kia, S. M., Narayan, V. A., Redmond, S. J., Kogan, A., Hussain, M. A., McKhann, G. M. II, & Vahdat, S. (2022). Assessment of Smartphone-Based Spiral Tracing in Multiple Sclerosis Reveals Intra-Individual Reproducibility as a Major Determinant of the Clinical Utility of the Digital Test. Frontiers in Medical Technology, 3, 714682. [https://doi.org/10.3389/fmedt.2021.714682](https://doi.org/10.3389/fmedt.2021.714682)

SECURITY.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
# This file is a placeholder, will be updated later.
2-
31
# Security
42

53
Child Mind Institute values the security of our users, data, and servers. We take the security of our systems seriously and encourage users to report any suspected security vulnerabilities promptly.
64

75
## Supported Versions
86

9-
MODIFY:
10-
11-
Note which version(s) will receive security updates. For example:
12-
13-
| Version | Supported |
14-
| ------- | ------------------ |
15-
| 1.0.x | :white_check_mark: |
16-
| 0.9.x | :x: |
7+
Only the latest minor version will receive security updates. All older minor versions should be considered unmaintained.
178

189
## Reporting Vulnerabilities
1910

20-
To report security vulnerabilities, please do NOT use our issues page. Instead, kindly email us at mobi@childmind.org. Please refrain from using other communication channels.
11+
To report security vulnerabilities, please do NOT use our issues page. Instead, kindly email us at <mobi@childmind.org>. Please refrain from using other communication channels.
2112

2213
For non-security-related issues, we welcome your input and feedback on our issues page. Feel free to share your ideas and suggestions to help us improve our services.

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[project]
22
name = "graphomotor"
3-
version = "0.1.0"
4-
description = "Toolkit for analysis of Graphomotor study tasks"
3+
version = "0.1.1"
4+
description = "A Python toolkit for analysis of graphomotor data collected via Curious"
55
authors = [
6-
{name = "Alp Erkent", email = "alp.erkent@childmind.org"}
6+
{name = "Alp Erkent", email = "alp.erkent@childmind.org"},
7+
{name = "Adam Santorelli", email = "adam.santorelli@childmind.org"}
78
]
89
license = "LGPL-2.1"
910
readme = "README.md"
@@ -39,7 +40,7 @@ src = ["src"]
3940
target-version = "py312"
4041

4142
[tool.ruff.lint]
42-
select = ["ANN", "D", "E", "F", "I"]
43+
select = ["ANN", "D", "E", "F", "I", "INP"]
4344
ignore = []
4445
fixable = ["ALL"]
4546
unfixable = []

0 commit comments

Comments
 (0)