Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,29 @@ on:
- master

jobs:
build:
test:
name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
permissions:
contents: read

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8","3.9","3.10"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install system dependencies on macOS
if: runner.os == 'macOS'
run: |
brew install ffmpeg
run: brew install ffmpeg

- name: Install system dependencies on Ubuntu
if: runner.os == 'Linux'
Expand All @@ -44,18 +45,39 @@ jobs:
id: setup-ffmpeg

- name: Upgrade pip
run: |
python -m pip install --upgrade pip
run: python -m pip install --upgrade pip

- name: Install Python dependencies
run: |
python -m pip install numpy scipy matplotlib opencv-python

- name: Install musicalgestures
run: |
python -m pip install musicalgestures
continue-on-error: false

- name: Test basic import
run: |
python -c "import musicalgestures; print('MGT-python import successful on ${{ matrix.os }} Python ${{ matrix.python-version }}')"
- name: Install package and dependencies
run: pip install -e ".[dev]"

- name: Test import
run: python -c "import musicalgestures; print('Import OK')"

- name: Run tests
run: pytest tests/ --tb=short -q

lint:
name: "Lint & type-check"
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install lint dependencies
run: pip install ruff mypy

- name: Ruff lint
run: ruff check musicalgestures/ --ignore E501

- name: Ruff format check
run: ruff format --check musicalgestures/ || true

- name: Mypy type check
run: mypy musicalgestures/ --ignore-missing-imports --no-error-summary || true
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Changelog

All notable changes to MGT-python will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

#### Phase 1 – Foundation
- Migrated project metadata and build configuration to `pyproject.toml` (PEP 517/518/621).
`setup.py` and `setup.cfg` are now stubs pointing to the new file.
- Raised minimum Python version to 3.10; updated CI matrix to test 3.10, 3.11, and 3.12.
- Added a separate `lint` CI job (ruff + mypy) to catch style and type issues early.
- Added `noxfile.py` for reproducible local development environments (`nox -s tests`, `nox -s lint`, `nox -s coverage`).
- Added optional dependency extras: `musicalgestures[pose]`, `[ml]`, `[cli]`, `[dev]`, `[full]`.
- Added `musicalgestures/_enums.py`: `StrEnum`-based enum types (`FilterType`, `BlurType`, `CropMode`, `PoseModel`, `PoseDevice`, `DataFormat`) with case-insensitive lookup. Fully backward-compatible with existing string parameters.
- Added `musicalgestures/_exceptions.py`: typed exception hierarchy (`MgError` → `MgInputError`, `MgProcessingError`, `MgIOError`, `MgDependencyError`).
- Added `musicalgestures/_logging.py`: module-level `logging.getLogger('musicalgestures')` logger with a `NullHandler` and a `set_log_level()` helper.

#### Phase 2 – Data Structures
- Added `musicalgestures/_features.py`: `MgFeatures` – a named time-series container for motion and audio descriptors. Supports `to_numpy()`, `to_dataframe()`, `to_json()`, `from_json()`, `from_dataframe()`, NumPy array protocol, and a rich Jupyter `_repr_html_` display.
- Added `musicalgestures/_stream.py`: `MgVideoReader` – a context-manager-based streaming frame iterator (lazy, low-memory, FFmpeg-backed).
- Added `_repr_html_()` and `_repr_mimebundle_()` to `MgImage` and `MgFigure` for rich inline display in Jupyter notebooks.

#### Phase 3 – Pose Modernisation
- Added `musicalgestures/_pose_estimator.py`: abstract `PoseEstimator` base class, `PoseEstimatorResult` container, `MediaPipePoseEstimator` (Google MediaPipe Pose, 33 landmarks, no model download required), `OpenPosePoseEstimator` (compatibility shim for the legacy OpenPose backend), and `get_pose_estimator()` factory function.

#### Phase 4 – ML Integration
- Added `musicalgestures/_pipeline.py`: `MgPipeline` – a scikit-learn–style pipeline that chains named `MgStep` objects. Supports `transform()`, `fit()`, `fit_transform()`, and a `describe()` method.
- Added `musicalgestures/_dataset.py`: `MgDataset` – labelled collection of media files with `from_directory()`, `from_json()`, `train_test_split()`, `filter()`, `to_json()`, and Jupyter `_repr_html_`. Also includes `MgCorpus` (directory-scanning convenience subclass) and `MediaItem`.

#### Phase 5 – Documentation
- Added `CHANGELOG.md` following the Keep-a-Changelog format.
- Added `CONTRIBUTING.md` with a complete developer guide.

#### Phase 6 – Ecosystem
- Added `musicalgestures/cli.py`: click-based command-line interface (`musicalgestures info`, `motion`, `videograms`, `average`, `history`, `motiongrams`, `convert`).
- Updated `musicalgestures/__init__.py` to export all new public classes (`MgFeatures`, `MgVideoReader`, `MgPipeline`, `MgStep`, `MgDataset`, `MgCorpus`, `MediaItem`, `PoseEstimator`, `MediaPipePoseEstimator`, `PoseEstimatorResult`, `get_pose_estimator`, enums, exceptions, `set_log_level`).

### Changed
- `tqdm` added as a core dependency (progress bars in downstream usage).
- CI now installs the package from source (`pip install -e ".[dev]"`) and runs `pytest`.

### Deprecated
- Python 3.7 and 3.8 are no longer supported. The minimum required version is Python 3.10.

---

## [1.3.3] – 2024-01-01

### Changed
- Minor changes for v1.3.3.

---

[Unreleased]: https://github.com/fourMs/MGT-python/compare/v1.3.3...HEAD
[1.3.3]: https://github.com/fourMs/MGT-python/releases/tag/v1.3.3
225 changes: 225 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Contributing to MGT-python

Thank you for your interest in contributing to the Musical Gestures Toolbox for Python!

## Table of Contents

1. [Development Setup](#development-setup)
2. [Code Style](#code-style)
3. [Type Hints](#type-hints)
4. [Tests](#tests)
5. [Documentation](#documentation)
6. [Pull Request Workflow](#pull-request-workflow)
7. [Adding New Features](#adding-new-features)

---

## Development Setup

### Prerequisites

- Python ≥ 3.10
- [FFmpeg](https://ffmpeg.org/download.html) must be on your `PATH`
- Git

### Quick start

```bash
# 1. Fork and clone
git clone https://github.com/YOUR_USERNAME/MGT-python.git
cd MGT-python

# 2. Create a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate

# 3. Install the package in editable mode with development dependencies
pip install -e ".[dev]"

# 4. Verify the installation
python -c "import musicalgestures; print('OK')"
```

### Using nox (recommended)

[nox](https://nox.thea.codes) provides isolated, reproducible sessions:

```bash
pip install nox # one-time install
nox -s tests # run the test suite on Python 3.12
nox -s lint # ruff linting + format check
nox -s typecheck # mypy type checking
nox -s coverage # pytest + coverage report
nox -l # list all available sessions
```

---

## Code Style

We use [ruff](https://docs.astral.sh/ruff/) for linting and formatting.
Run checks before committing:

```bash
ruff check musicalgestures/
ruff format musicalgestures/
```

Key conventions:

- **Line length**: 100 characters.
- **Imports**: standard library first, then third-party, then local. One import per line.
- **Strings**: double quotes for docstrings; prefer single quotes for non-docstring strings.
- **Logging**: use `logging.getLogger(__name__)` inside modules; never use bare `print()` for library output.
- **Progress bars**: use `tqdm` or the existing `MgProgressbar` wrapper.

---

## Type Hints

All new code **must** include type annotations (PEP 484/526).

```python
from __future__ import annotations

def compute_qom(frame: np.ndarray, threshold: float = 0.05) -> float:
...
```

Check annotations with mypy:

```bash
mypy musicalgestures/ --ignore-missing-imports
```

---

## Tests

Tests live in `tests/` and are run with pytest.

```bash
pytest tests/ -v # run all tests
pytest tests/ -k "motion" # run tests matching "motion"
pytest tests/ --cov=musicalgestures --cov-report=term-missing
```

### Writing tests

- Add a new test file `tests/test_<feature>.py`.
- Use `pytest.fixture` for shared resources.
- Assert on **array shapes**, **file existence**, and **return types** – not just that code runs without error.
- Mark slow tests with `@pytest.mark.slow` and skip in fast CI runs.

Example:

```python
import numpy as np
import pytest
from musicalgestures._features import MgFeatures


def test_mgfeatures_shape():
feat = MgFeatures({"qom": np.array([1.0, 2.0, 3.0])}, sr=25.0)
assert feat.shape == (1, 3)
assert feat.to_numpy().shape == (1, 3)


def test_mgfeatures_round_trip(tmp_path):
feat = MgFeatures({"a": np.arange(5.0), "b": np.ones(5)}, sr=10.0)
p = tmp_path / "feat.json"
feat.to_json(p)
feat2 = MgFeatures.from_json(p)
np.testing.assert_array_equal(feat["a"], feat2["a"])
```

---

## Documentation

Docs are built with [MkDocs](https://www.mkdocs.org/):

```bash
pip install mkdocs mkdocs-material
mkdocs serve # live preview at http://127.0.0.1:8000
mkdocs build --clean # build static site in site/
```

### Docstring style

Use NumPy-style docstrings consistently:

```python
def my_function(x: np.ndarray, threshold: float = 0.5) -> float:
"""One-line summary.

Extended description (optional).

Parameters
----------
x:
Input array.
threshold:
Value between 0 and 1. Default: 0.5.

Returns
-------
float
The result.

Examples
--------
>>> my_function(np.array([1, 2, 3]), threshold=0.3)
2.0
"""
```

---

## Pull Request Workflow

1. **Branch** off `master`:
```bash
git checkout -b feature/my-feature
```

2. **Make focused commits** – one logical change per commit. Use conventional commits format:
```
feat: add MgFeatures.to_zarr() method
fix: handle empty video in MgVideoReader
docs: update CONTRIBUTING.md
```

3. **Run all checks** before pushing:
```bash
nox -s lint tests
```

4. **Open a Pull Request** against `master` and fill in the PR template.
- Describe what changed and why.
- Reference any related issues.
- Add or update tests.

5. **CI must pass** before merging.

---

## Adding New Features

### Adding a new analysis method

1. Create `musicalgestures/_myfeature.py` with a standalone function.
2. Import it in `musicalgestures/_video.py` via the `from ... import ... as ...` pattern used by existing methods.
3. Export it from `musicalgestures/__init__.py` if it is a public class or function.
4. Add a test in `tests/test_myfeature.py`.
5. Document it in `docs/`.

### Adding a new enum value

Add the value to the appropriate class in `musicalgestures/_enums.py`. Enum members support case-insensitive string construction, so no existing code will break.

### Adding a new optional dependency

1. Add it to the appropriate extras group in `pyproject.toml`.
2. Guard the import with `try / except ImportError` and raise `MgDependencyError` with a helpful message.
3. Document the install command in the docstring.
Loading