diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47b4b3b..31db852 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [1.7.1] — 2026-08-05
+
+### Fixed
+- The committed API pages published a band this package no longer uses. They showed
+ `group_qom(points, fs, lo=0.3, hi=15.0)` and deep-linked into `musicalgestures/_qom.py` at line
+ numbers that stopped existing when those functions moved to `micromotion` on 2026-07-29. The
+ pages are regenerated from the current source.
+- `docs/user-guide/pose-tracking.md` and `docs/user-guide/sound-movement-toolkit.md` said the band
+ is 0.3–5 Hz and passed `lo=0.3, hi=15.0`. It is `micromotion.BAND`, 0.2–5 Hz. The regeneration
+ script does not touch hand-written pages, so these were corrected by hand.
+- The `micromotion` floor was `>=0.3`. No such release exists on PyPI below 0.6, and the functions
+ re-exported here arrived far later, so the constraint permitted installations in which
+ `from musicalgestures import group_qom` fails. It is now `>=0.15.2`, which is also the floor that
+ makes the committed API pages true, since they are generated from that package's docstrings.
+
+### Changed
+- The three re-export shims now point at for the API
+ reference. Their generated pages describe the shim rather than the functions, which is correct
+ but left a reader with nowhere to go.
+
## [1.7.0] — 2026-08-03
### Added
diff --git a/docs/MODULES.md b/docs/MODULES.md
index 1a28e44..1023eb8 100644
--- a/docs/MODULES.md
+++ b/docs/MODULES.md
@@ -54,13 +54,17 @@ Full list of [Mgt-python](README.md#mgt-python) project modules.
- [Posture](musicalgestures/_posture.md#posture)
- [Pulse](musicalgestures/_pulse.md#pulse)
- [Qom](musicalgestures/_qom.md#qom)
+ - [Remap360](musicalgestures/_remap360.md#remap360)
- [Show](musicalgestures/_show.md#show)
- [Show Window](musicalgestures/_show_window.md#show-window)
- [Sonification](musicalgestures/_sonification.md#sonification)
+ - [Soundscape](musicalgestures/_soundscape.md#soundscape)
- [Spacetime](musicalgestures/_spacetime.md#spacetime)
- [Ssm](musicalgestures/_ssm.md#ssm)
- [Stream](musicalgestures/_stream.md#stream)
- [Subtract](musicalgestures/_subtract.md#subtract)
+ - [Sync](musicalgestures/_sync.md#sync)
+ - [Timecode](musicalgestures/_timecode.md#timecode)
- [Utils](musicalgestures/_utils.md#utils)
- [Video](musicalgestures/_video.md#video)
- [Videoadjust](musicalgestures/_videoadjust.md#videoadjust)
diff --git a/docs/musicalgestures/_360video.md b/docs/musicalgestures/_360video.md
index 579c30c..e4257c3 100644
--- a/docs/musicalgestures/_360video.md
+++ b/docs/musicalgestures/_360video.md
@@ -5,11 +5,15 @@
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / 360video
- [Mg360Video](#mg360video)
- [Mg360Video().convert_projection](#mg360videoconvert_projection)
+ - [Mg360Video.from_dual_fisheye](#mg360videofrom_dual_fisheye)
- [Projection](#projection)
+ - [calibrate_dual_fisheye_fov](#calibrate_dual_fisheye_fov)
+ - [make_seam_mask](#make_seam_mask)
+ - [stitch_dual_fisheye](#stitch_dual_fisheye)
## Mg360Video
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L94)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L255)
```python
class Mg360Video(MgVideo):
@@ -30,7 +34,7 @@ Class for 360 videos.
### Mg360Video().convert_projection
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L126)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L299)
```python
def convert_projection(
@@ -53,12 +57,128 @@ options (Dict[str, str], optional): Options for the conversion. Defaults to None
- [Projection](#projection)
+### Mg360Video.from_dual_fisheye
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L287)
+
+```python
+@classmethod
+def from_dual_fisheye(
+ front_file,
+ back_file,
+ camera: str = None,
+ **stitch_kwargs,
+):
+```
+
+Stitch a dual-fisheye pair (e.g. the `_00_`/`_10_` .insv files of an
+Insta360 camera) into an equirectangular video and open it as an
+Mg360Video. See [stitch_dual_fisheye](#stitch_dual_fisheye) for the stitching options
+(`fov=None` auto-calibrates the lens FOV on a probe frame).
+
## Projection
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L11)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L13)
```python
class Projection(Enum):
```
same as https://ffmpeg.org/ffmpeg-filters.html#v360.
+
+## calibrate_dual_fisheye_fov
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L131)
+
+```python
+def calibrate_dual_fisheye_fov(
+ front_file,
+ back_file,
+ time_s: float = 1.0,
+ candidates=None,
+ print_result: bool = False,
+):
+```
+
+Estimate the effective lens field of view of a dual-fisheye pair
+(e.g. the two .insv files of an Insta360 camera) by projecting one frame
+of each lens to equirectangular at candidate FOVs and measuring the
+photometric mismatch in the seam bands at longitude ±90°.
+
+#### Arguments
+
+- `front_file` *str* - Video of the front lens.
+- `back_file` *str* - Video of the back lens.
+- `time_s` *float* - Timestamp of the probe frame.
+- `candidates` *list* - FOVs (degrees) to try. Default 185–205.
+
+#### Returns
+
+- `float` - The FOV with the smallest seam mismatch.
+
+## make_seam_mask
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L106)
+
+```python
+def make_seam_mask(width: int, height: int, feather_deg: float = 8.0):
+```
+
+Column mask for feather-blending two hemispheres on an equirectangular
+canvas: 0 where the front lens (yaw 0) should be used, 255 for the back
+lens (yaw 180), with a linear ramp of ±feather_deg around the seams at
+longitude ±90°.
+
+#### Arguments
+
+- `width` *int* - Mask width in pixels (full 360° canvas).
+- `height` *int* - Mask height in pixels.
+- `feather_deg` *float* - Half-width of the blend ramp in degrees.
+
+#### Returns
+
+- `np.ndarray` - uint8 mask of shape (height, width).
+
+## stitch_dual_fisheye
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_360video.py#L188)
+
+```python
+def stitch_dual_fisheye(
+ front_file,
+ back_file,
+ target_name: str = None,
+ fov: float = None,
+ feather_deg: float = 8.0,
+ width: int = None,
+ height: int = None,
+ crf: int = 21,
+ preset: str = 'fast',
+ print_cmd: bool = False,
+):
+```
+
+Stitch a dual-fisheye pair (two single-lens files, e.g. Insta360
+`_00_`/`_10_` .insv) into one equirectangular video with a feathered
+seam blend. Each lens is projected to equirectangular separately
+(back lens at yaw 180) and the two are merged with a soft column mask,
+which avoids the hard seams of a plain `v360=dfisheye` conversion.
+Audio is taken from the front-lens file when present.
+Also fits Garmin VIRB 360 RAW-mode recordings, which store the two
+~200-degree hemispheres as separate files.
+
+#### Arguments
+
+- `front_file` *str* - Video of the front lens.
+- `back_file` *str* - Video of the back lens.
+- `target_name` *str* - Output path. Defaults to `_equirect.mp4`.
+- `fov` *float* - Lens FOV in degrees. None runs
+ [calibrate_dual_fisheye_fov](#calibrate_dual_fisheye_fov) on a probe frame first.
+- `feather_deg` *float* - Half-width of the seam blend in degrees.
+width, height (int): Output size. Defaults to lens height × 2 by
+ lens height (2:1 equirectangular).
+crf (int), preset (str): x264 rate control.
+
+#### Returns
+
+- `str` - Path of the stitched video.
diff --git a/docs/musicalgestures/_alignment.md b/docs/musicalgestures/_alignment.md
index 6fa8aa3..2610848 100644
--- a/docs/musicalgestures/_alignment.md
+++ b/docs/musicalgestures/_alignment.md
@@ -126,7 +126,7 @@ five camera angles' motion envelopes.
def envelope_lag(x, y, rate, max_lag_s=1.5):
```
-Lag (s) of `y` relative to `x` maximising their correlation. Positive
+Lag (s) of `y` relative to `x` maximizing their correlation. Positive
lag = `y` happens after `x`. Thin wrapper around [xcorr_lag](#xcorr_lag), kept as
the ro study's interface for envelope-to-envelope lags (e.g. voice
envelope vs motion envelope).
@@ -254,11 +254,11 @@ def xcorr_lag(x, y, fs, max_lag=1.5):
```
Canonical lead/lag estimate between two signals by vectorized
-cross-correlation: the lag of `y` relative to `x` that maximises their
+cross-correlation: the lag of `y` relative to `x` that maximizes their
correlation, searched within +/- `max_lag` seconds. Positive lag means
`y` happens after `x`.
-Both signals are mean-removed and the correlation is normalised to a
+Both signals are mean-removed and the correlation is normalized to a
Pearson-like coefficient over the full window. Among near-tied maxima
(common for periodic envelopes, where peaks recur at +/- one period),
the smallest-magnitude lag is returned rather than an arbitrary aliased
diff --git a/docs/musicalgestures/_audio.md b/docs/musicalgestures/_audio.md
index 4ac1af5..5754d7c 100644
--- a/docs/musicalgestures/_audio.md
+++ b/docs/musicalgestures/_audio.md
@@ -156,7 +156,7 @@ Renders a figure of plots showing spectral/loudness descriptors, including RMS e
- `n_mels` *int, optional* - The number of mel filters to use for filtering the frequency domain. Affects the vertical resolution (sharpness) of the spectrogram. NB: Too high values with relatively small window sizes can result in artifacts (typically black lines) in the resulting image. Defaults to 128.
- `fmin` *float, optional* - Lowest frequency (in Hz). Defaults to 0.0.
- `fmax` *float, optional* - Highest frequency (in Hz). Defaults to None, use fmax = sr / 2.0
-- `power` *float, optional* - The steepness of the curve for the colour mapping. Defaults to 2.
+- `power` *float, optional* - The steepness of the curve for the color mapping. Defaults to 2.
- `dpi` *int, optional* - Image quality of the rendered figure in DPI. Defaults to 300.
- `autoshow` *bool, optional* - Whether to show the resulting figure automatically. Defaults to True.
- `original_time` *bool, optional* - Whether to plot original time or not. This parameter can be useful if the file has been shortened beforehand (e.g. skip). Defaults to False.
@@ -337,7 +337,7 @@ Renders a figure showing the mel-scaled spectrogram of the video/audio file.
- `n_mels` *int, optional* - The number of filters to use for filtering the frequency domain. Affects the vertical resolution (sharpness) of the spectrogram. NB: Too high values with relatively small window sizes can result in artifacts (typically black lines) in the resulting image. Defaults to 128.
- `fmin` *float, optional* - Lowest frequency (in Hz). Defaults to 0.0.
- `fmax` *float, optional* - Highest frequency (in Hz). Defaults to None, use fmax = sr / 2.0.
-- `power` *float, optional* - The steepness of the curve for the colour mapping. Defaults to 2.
+- `power` *float, optional* - The steepness of the curve for the color mapping. Defaults to 2.
- `top_db` *float, optional* - threshold the output at top_db below the peak: max(20 * log10(S/ref)) - top_db. Defaults to 80.0.
- `dpi` *int, optional* - Image quality of the rendered figure in DPI. Defaults to 300.
- `autoshow` *bool, optional* - Whether to show the resulting figure automatically. Defaults to True.
@@ -468,12 +468,12 @@ Renders a figure showing the waveform of the video/audio file.
- `dpi` *int, optional* - Image quality of the rendered figure in DPI. Defaults to 300.
- `autoshow` *bool, optional* - Whether to show the resulting figure automatically. Defaults to True.
- `raw` *bool, optional* - Whether to show labels and ticks on the plot. Defaults to False.
-- `colored` *bool, optional* - Whether to create a coloured waveform image (freesound-style) from an audio input file. Defauts to False.
-- `image_width` *int, optional* - Number of pixels for the coloured waveform image width. Defaults to 2500.
-- `image_height` *int, optional* - Number of pixels for the coloured waveform image height. Defaults to 500.
-- `fmin` *int, optional* - Minimum frequency for computing spectral centroid for the coloured waveform image. Defaults to 500.
-- `fmax` *int, optional* - Maximum frequency for computing spectral centroid for the coloured waveform image. Defaults to None (i.e. Nyquist frequency).
-- `cmap` *str, optional* - Colormap used for colouring the waveform, all colormaps included with matplotlib can be used. Defaults to 'freesound'.
+- `colored` *bool, optional* - Whether to create a colored waveform image (freesound-style) from an audio input file. Defauts to False.
+- `image_width` *int, optional* - Number of pixels for the colored waveform image width. Defaults to 2500.
+- `image_height` *int, optional* - Number of pixels for the colored waveform image height. Defaults to 500.
+- `fmin` *int, optional* - Minimum frequency for computing spectral centroid for the colored waveform image. Defaults to 500.
+- `fmax` *int, optional* - Maximum frequency for computing spectral centroid for the colored waveform image. Defaults to None (i.e. Nyquist frequency).
+- `cmap` *str, optional* - Colormap used for coloring the waveform, all colormaps included with matplotlib can be used. Defaults to 'freesound'.
- `original_time` *bool, optional* - Whether to plot original time or not. This parameter can be useful if the video file has been shortened beforehand (e.g. skip). Defaults to True.
- `title` *str, optional* - Optionally add title to the figure. Possible to set the filename as the title using the string 'filename'. Defaults to None.
- `target_name` *str, optional* - The name of the output image. Defaults to None (which assumes that the input filename with the suffix "_waveform.png" should be used).
diff --git a/docs/musicalgestures/_audio_video.md b/docs/musicalgestures/_audio_video.md
index 2978d82..3305836 100644
--- a/docs/musicalgestures/_audio_video.md
+++ b/docs/musicalgestures/_audio_video.md
@@ -3,7 +3,7 @@
> Auto-generated documentation for [musicalgestures._audio_video](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_audio_video.py) module.
Audio–movement comparison reports for a single performer: tools to reveal how a dancer's
-movement relates to the sound, through phase synchrony, structural similarity, per-body-part coupling,
+movement relates to the sound — phase synchrony, structural similarity, per-body-part coupling,
and energy/dynamics coupling.
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Audio Video
@@ -36,8 +36,8 @@ def mg_body_audio_coupling(
Map which body parts are most rhythmically coupled to the music.
For every pose marker the per-frame speed is correlated with the audio onset-strength
-envelope (sampled at the video frame rate). The result is shown as a body map—the average
-pose with each marker coloured by its correlation—plus a sorted bar chart, and a CSV of the
+envelope (sampled at the video frame rate). The result is shown as a body map — the average
+pose with each marker coloured by its correlation — plus a sorted bar chart, and a CSV of the
per-marker correlations. Uses cached pose keypoints when available, otherwise runs ``pose()``
first (``**pose_kwargs`` are forwarded).
@@ -60,7 +60,7 @@ def mg_dynamics_coupling(
) -> 'MgFigure':
```
-Compare audio **loudness** with movement **quantity**. Does the dancer move more when the
+Compare audio **loudness** with movement **quantity** — does the dancer move more when the
music is louder?
Aligns the audio RMS-loudness envelope with the quantity-of-motion envelope and reports their
@@ -118,7 +118,7 @@ Compare the temporal **structure** of the audio with that of the movement.
Builds a self-similarity matrix (SSM) of the audio (from MFCC frames) and of the video
(from low-resolution frame appearance), resampled to the same ``n`` time points, and shows
-them side by side with their absolute **difference map**. Bright regions in the difference
+them side by side with their absolute **difference map** — bright regions in the difference
are where the musical structure and the movement structure diverge.
Returns an MgFigure (mean structural agreement in ``.data``), or None if the video has no audio.
diff --git a/docs/musicalgestures/_audiofeatures.md b/docs/musicalgestures/_audiofeatures.md
index 964b5a7..804e717 100644
--- a/docs/musicalgestures/_audiofeatures.md
+++ b/docs/musicalgestures/_audiofeatures.md
@@ -25,7 +25,7 @@ Sources: cymbal-comparison study and Westney-comparisons study (Jensenius).
## attack_spectral_centroid
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_audiofeatures.py#L207)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_audiofeatures.py#L209)
```python
def attack_spectral_centroid(
@@ -43,8 +43,10 @@ centroid over the first `attack` seconds after the RMS-envelope peak.
Discriminates, e.g., strike placement and damping on a cymbal.
The constants (120 ms attack, 2048-sample Hann window, 512 hop) are
-PROVISIONAL defaults reimplemented from the cymbal-comparison paper's
-method description.
+validated against the original cymbal dataset (Zenodo 21360429, 2026
+revalidation); revalidation found centroids ~15-25% lower than archived
+results with ordering preserved (implementation detail differences,
+ordering-safe); tune per dataset as needed.
Source: cymbal-comparison study (Jensenius).
@@ -78,8 +80,8 @@ relative to the per-file peak, with a minimum inter-onset interval.
Reliable for discrete strokes; over-fragments sustained rolls/tremolo
and can trigger on near-noise material. The default constants
-(0.15 x peak, 0.06 s) are PROVISIONAL defaults reimplemented from the
-cymbal-comparison paper's method description.
+(0.15 x peak, 0.06 s) are validated against the original cymbal dataset
+(Zenodo 21360429, 2026 revalidation); tune per dataset as needed.
Source: cymbal-comparison study (Jensenius).
@@ -130,7 +132,7 @@ def spectral_flux(y, sr, nperseg=2048, noverlap=1536):
```
Spectral-flux onset-detection function: the positive first difference of
-the STFT magnitude, summed over frequency and normalised to a maximum of
+the STFT magnitude, summed over frequency and normalized to a maximum of
1. Rises sharply at note/percussion onsets.
Source: Westney-comparisons study (Jensenius).
@@ -210,8 +212,10 @@ falling back to -5 to -25 dB (T20, x3) when the deeper level is not
reached.
The constants (20 ms window, -5/-35 with -5/-25 fallback, 6 dB re-rise
-stop) are PROVISIONAL defaults reimplemented from the cymbal-comparison
-paper's method description.
+stop) are validated against the original cymbal dataset (Zenodo 21360429,
+2026 revalidation); revalidation found 1-6% agreement overall (one damped
+exemplar +77% difference, likely an implementation detail); tune per
+dataset as needed.
Source: cymbal-comparison study (Jensenius) -- instrument decay of
damped vs undamped cymbal strokes.
diff --git a/docs/musicalgestures/_blurfaces.md b/docs/musicalgestures/_blurfaces.md
index 01cfa82..aac7c88 100644
--- a/docs/musicalgestures/_blurfaces.md
+++ b/docs/musicalgestures/_blurfaces.md
@@ -63,11 +63,11 @@ Credits: `centerface.onnx` (original) and `centerface.py` are based on https://g
- `ellipse` *bool, optional* - Mask faces with blurred ellipses. Defaults to True.
- `draw_heatmap` *bool, optional* - Draw heatmap of the detected faces using the centroid of the face mask. Defaults to False.
- `neighbours` *int, optional* - Number of neighbours for smoothing the heatmap image. Defaults to 32.
-- `resolution` *int, optional* - Number of pixel resolution for the heatmap visualisation. Defaults to 250.
+- `resolution` *int, optional* - Number of pixel resolution for the heatmap visualization. Defaults to 250.
- `draw_scores` *bool, optional* - Draw detection faceness scores onto outputs (a score between 0 and 1 that roughly corresponds to the detector's confidence that something is a face). Defaults to False.
- `save_data` *bool, optional* - Whether to save the scaled coordinates of the face mask (time (ms), x1, y1, x2, y2) for each frame to a file. Defaults to True.
- `data_format` *str, optional* - Specifies format of blur_faces-data. Accepted values are 'csv', 'tsv' and 'txt'. For multiple output formats, use list, e.g. ['csv', 'txt']. Defaults to 'csv'.
-- `color` *tuple, optional* - Customized colour of the rectangle boxes. Defaults to black (0, 0, 0).
+- `color` *tuple, optional* - Customized color of the rectangle boxes. Defaults to black (0, 0, 0).
- `use_gpu` *bool, optional* - Whether to attempt GPU (CUDA) acceleration for face detection. Falls back to CPU automatically if CUDA is unavailable. Defaults to False.
- `target_name` *str, optional* - Target output name. Defaults to None (which assumes that the input filename with the suffix "_blurred" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
diff --git a/docs/musicalgestures/_eulerian.md b/docs/musicalgestures/_eulerian.md
index 8b8b9b7..28a48f0 100644
--- a/docs/musicalgestures/_eulerian.md
+++ b/docs/musicalgestures/_eulerian.md
@@ -29,7 +29,7 @@ Applies Eulerian Video Magnification (EVM) to reveal subtle changes in a video.
EVM amplifies small temporal variations that are normally invisible. Two modes are
available:
-* ``mode='colour'`` — amplifies subtle **colour** changes (e.g. blood flow / pulse,
+* ``mode='color'`` — amplifies subtle **colour** changes (e.g. blood flow / pulse,
breathing). Uses a Gaussian pyramid and an ideal (FFT) temporal band-pass filter.
Processed in two passes so only a small down-sampled stack is held in memory.
* ``mode='motion'`` — amplifies subtle **motion**. Uses a Laplacian pyramid with a
@@ -41,12 +41,12 @@ World" (SIGGRAPH 2012).
#### Arguments
-- `mode` *str, optional* - 'colour' or 'motion'. Defaults to 'colour'.
+- `mode` *str, optional* - 'color' or 'motion'. Defaults to 'color'.
- `freq_low` *float, optional* - Lower temporal cutoff in Hz. Defaults to 0.83 (~50 bpm).
- `freq_high` *float, optional* - Upper temporal cutoff in Hz. Defaults to 1.0 (~60 bpm).
- `amplification` *float, optional* - Amplification factor (alpha). Defaults to 50.
- `levels` *int, optional* - Number of spatial pyramid levels. Defaults to 4.
-- `chroma_attenuation` *float, optional* - Chrominance attenuation in [0, 1] (colour mode).
+- `chroma_attenuation` *float, optional* - Chrominance attenuation in [0, 1] (color mode).
Lower values reduce colour artefacts. Defaults to 1.0.
- `lambda_cutoff` *float, optional* - Spatial wavelength cutoff for amplitude attenuation
(motion mode). Defaults to 16.
diff --git a/docs/musicalgestures/_features.md b/docs/musicalgestures/_features.md
index 8da93fb..4815540 100644
--- a/docs/musicalgestures/_features.md
+++ b/docs/musicalgestures/_features.md
@@ -10,6 +10,7 @@ MgFeatures – a named time-series container for motion and audio descriptors.
- [MgFeatures().\_\_getitem\_\_](#mgfeatures__getitem__)
- [MgFeatures().\_\_iter\_\_](#mgfeatures__iter__)
- [MgFeatures().\_\_len\_\_](#mgfeatures__len__)
+ - [MgFeatures().absolute_times](#mgfeaturesabsolute_times)
- [MgFeatures().feature_names](#mgfeaturesfeature_names)
- [MgFeatures.from_dataframe](#mgfeaturesfrom_dataframe)
- [MgFeatures.from_json](#mgfeaturesfrom_json)
@@ -95,7 +96,7 @@ shape : tuple[int, int]
### MgFeatures().\_\_array\_\_
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L151)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L169)
```python
def __array__(dtype=None, copy=None) -> np.ndarray:
@@ -105,7 +106,7 @@ Return a 2-D array of shape ``(n_features, n_samples)``.
### MgFeatures().\_\_getitem\_\_
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L140)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L158)
```python
def __getitem__(key: str) -> np.ndarray:
@@ -115,7 +116,7 @@ Return a single feature array by name.
### MgFeatures().\_\_iter\_\_
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L147)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L165)
```python
def __iter__():
@@ -125,7 +126,7 @@ Iterate over feature names.
### MgFeatures().\_\_len\_\_
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L136)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L154)
```python
def __len__() -> int:
@@ -133,6 +134,19 @@ def __len__() -> int:
Return the number of feature channels.
+### MgFeatures().absolute_times
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L132)
+
+```python
+def absolute_times() -> np.ndarray:
+```
+
+Wall-clock time stamps (epoch seconds) for each sample.
+
+Requires ``metadata["start_datetime"]`` — a ``datetime`` or ISO
+string, e.g. from [media_start_datetime](_timecode.md#media_start_datetime).
+
### MgFeatures().feature_names
[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L107)
@@ -146,7 +160,7 @@ Names of the feature channels.
### MgFeatures.from_dataframe
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L237)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L255)
```python
@classmethod
@@ -178,7 +192,7 @@ MgFeatures
### MgFeatures.from_json
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L213)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L231)
```python
@classmethod
@@ -242,7 +256,7 @@ Time axis in seconds.
### MgFeatures().to_dataframe
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L171)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L189)
```python
def to_dataframe() -> pd.DataFrame:
@@ -257,7 +271,7 @@ pd.DataFrame
### MgFeatures().to_json
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L183)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L201)
```python
def to_json(path: str | Path | None = None) -> str:
@@ -278,7 +292,7 @@ str
### MgFeatures().to_numpy
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L160)
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_features.py#L178)
```python
def to_numpy() -> np.ndarray:
diff --git a/docs/musicalgestures/_flow.md b/docs/musicalgestures/_flow.md
index 9171078..718d1aa 100644
--- a/docs/musicalgestures/_flow.md
+++ b/docs/musicalgestures/_flow.md
@@ -127,7 +127,7 @@ Renders a sparse optical flow video of the input video file using `cv2.calcOptic
- `filename` *str, optional* - Path to the input video file. If None, the video file of the MgVideo is used. Defaults to None.
- `corner_max_corners` *int, optional* - Maximum number of corners to return. If there are more corners than are found, the strongest of them is returned. `maxCorners <= 0` implies that no limit on the maximum is set and all detected corners are returned. Defaults to 100.
-- `corner_quality_level` *float, optional* - Parameter characterising the minimal accepted quality of image corners. The parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue (see cornerMinEigenVal in cv2 docs) or the Harris function response (see cornerHarris in cv2 docs). The corners with the quality measure less than the product are rejected. For example, if the best corner has the quality measure = 1500, and the qualityLevel=0.01, then all the corners with the quality measure less than 15 are rejected. Defaults to 0.3.
+- `corner_quality_level` *float, optional* - Parameter characterizing the minimal accepted quality of image corners. The parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue (see cornerMinEigenVal in cv2 docs) or the Harris function response (see cornerHarris in cv2 docs). The corners with the quality measure less than the product are rejected. For example, if the best corner has the quality measure = 1500, and the qualityLevel=0.01, then all the corners with the quality measure less than 15 are rejected. Defaults to 0.3.
- `corner_min_distance` *int, optional* - Minimum possible Euclidean distance between the returned corners. Defaults to 7.
- `corner_block_size` *int, optional* - Size of an average block for computing a derivative covariation matrix over each pixel neighborhood. See cornerEigenValsAndVecs in cv2 docs. Defaults to 7.
- `of_win_size` *tuple, optional* - Size of the search window at each pyramid level. Defaults to (15, 15).
diff --git a/docs/musicalgestures/_frameaverage.md b/docs/musicalgestures/_frameaverage.md
index f805d36..55b2494 100644
--- a/docs/musicalgestures/_frameaverage.md
+++ b/docs/musicalgestures/_frameaverage.md
@@ -20,7 +20,7 @@ and arranging all frames into a single image. This is equivalent to the bash scr
scales each frame to 1x1 pixel and then tiles them into a grid.
Based on the original bash script concept:
-- Each frame is reduced to a single pixel (average colour of the frame)
+- Each frame is reduced to a single pixel (average color of the frame)
- All pixel values are arranged in a grid with specified width
- Height is calculated automatically based on total frames and width
@@ -46,8 +46,8 @@ def mg_pixelarray_cv2(self, width=640, target_name=None, overwrite=True):
```
Alternative implementation using OpenCV for more control over the process.
-Creates a 'Frame-Averaged Pixel Array' by reading each frame, calculating its average colour,
-and arranging these average colours in a grid.
+Creates a 'Frame-Averaged Pixel Array' by reading each frame, calculating its average color,
+and arranging these average colors in a grid.
#### Arguments
diff --git a/docs/musicalgestures/_history.md b/docs/musicalgestures/_history.md
index 954546c..f6e148f 100644
--- a/docs/musicalgestures/_history.md
+++ b/docs/musicalgestures/_history.md
@@ -22,7 +22,7 @@ def history_cv2(
):
```
-This function creates a video where each frame is the average of the N previous frames, where n is determined by `history_length`. The history frames are summed up and normalised, and added to the current frame to show the history. Uses cv2.
+This function creates a video where each frame is the average of the N previous frames, where n is determined by `history_length`. The history frames are summed up and normalized, and added to the current frame to show the history. Uses cv2.
#### Arguments
@@ -55,15 +55,15 @@ def history_ffmpeg(
):
```
-This function creates a video where each frame is the average of the N previous frames, where n is determined by `history_length`. The history frames are summed up and normalised, and added to the current frame to show the history. Uses ffmpeg.
+This function creates a video where each frame is the average of the N previous frames, where n is determined by `history_length`. The history frames are summed up and normalized, and added to the current frame to show the history. Uses ffmpeg.
#### Arguments
- `filename` *str, optional* - Path to the input video file. If None, the video file of the MgVideo is used. Defaults to None.
- `history_length` *int, optional* - Number of frames to be saved in the history tail. Defaults to 10.
- `weights` *int/float/list/str, optional* - Defines the weight or weights applied to the frames in the history tail. If given as list the first element in the list will correspond to the weight of the newest frame in the tail. If given as a str - like "3 1.2 1" - it will be automatically converted to a list - like [3, 1.2, 1]. Defaults to 1.
-- `normalize` *bool, optional* - If True, the history video will be normalised. This can be useful when processing motion (frame difference) videos. Defaults to False.
-- `norm_strength` *int/float, optional* - Defines the strength of the normalisation where 1 represents full strength. Defaults to 1.
+- `normalize` *bool, optional* - If True, the history video will be normalized. This can be useful when processing motion (frame difference) videos. Defaults to False.
+- `norm_strength` *int/float, optional* - Defines the strength of the normalization where 1 represents full strength. Defaults to 1.
- `norm_smooth` *int, optional* - Defines the number of previous frames to use for temporal smoothing. The input range of each channel is smoothed using a rolling average over the current frame and the `norm_smooth` previous frames. Defaults to 0.
- `target_name` *str, optional* - Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_history" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
diff --git a/docs/musicalgestures/_impacts.md b/docs/musicalgestures/_impacts.md
index d150559..0323a29 100644
--- a/docs/musicalgestures/_impacts.md
+++ b/docs/musicalgestures/_impacts.md
@@ -46,7 +46,7 @@ def mg_impacts(
Compute a visual analogue of an onset envelope, aslo known as an impact envelope (Abe Davis).
This is computed by summing over positive entries in the columns of the directogram. This gives an impact envelope with precisely the same
form as an onset envelope. To account for large outlying spikes that sometimes happen at shot boundaries (i.e., cuts), the 99th percentile
-of the impact envelope values are clipped to the 98th percentile. Then, the impact envelopes are normalised by their maximum to make calculations
+of the impact envelope values are clipped to the 98th percentile. Then, the impact envelopes are normalized by their maximum to make calculations
more consistent across video resolutions. Fianlly, the local mean of the impact envelopes are calculated using a 0.1-second window, and local maxima
using a 0.15-second window. Impacts are defined as local maxima that are above their local mean by at least 10% of the envelope’s global maximum.
diff --git a/docs/musicalgestures/_input_test.md b/docs/musicalgestures/_input_test.md
index 426b599..dc27846 100644
--- a/docs/musicalgestures/_input_test.md
+++ b/docs/musicalgestures/_input_test.md
@@ -55,7 +55,7 @@ def mg_input_test(
):
```
-Gives feedback to user if initialisation from input went wrong.
+Gives feedback to user if initialization from input went wrong.
#### Arguments
diff --git a/docs/musicalgestures/_mglist.md b/docs/musicalgestures/_mglist.md
index 474e379..bbffd29 100644
--- a/docs/musicalgestures/_mglist.md
+++ b/docs/musicalgestures/_mglist.md
@@ -184,4 +184,4 @@ By default every item is shown. The keys ``'horizontal'`` and ``'vertical'`` sel
single panel, e.g. ``mv.motiongrams().show(key='horizontal')``. The aliases 'mgh'/'vgh'
(horizontal) and 'mgv'/'vgv' (vertical) work too, as do the legacy 'mgx'/'vgx' and
'mgy'/'vgy' (the literal x/y files). (The key identifies *which item in the list*
-to show. It is not forwarded to the individual images.)
+to show — it is not forwarded to the individual images.)
diff --git a/docs/musicalgestures/_mocap.md b/docs/musicalgestures/_mocap.md
index b191add..6a2e6e8 100644
--- a/docs/musicalgestures/_mocap.md
+++ b/docs/musicalgestures/_mocap.md
@@ -2,132 +2,20 @@
> Auto-generated documentation for [musicalgestures._mocap](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_mocap.py) module.
-Motion-capture I/O and cross-modality utilities.
+Re-exported from the ``micromotion`` package.
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Mocap
- - [compare_modality_envelopes](#compare_modality_envelopes)
- - [dominant_frequency](#dominant_frequency)
- - [read_qtm_tsv](#read_qtm_tsv)
-Pure numpy/scipy helpers ported from the "still standing" and
-Westney-comparisons studies:
+These functions used to live here. They were moved to ``micromotion`` on 2026-07-29 so that
+one implementation of quantity of motion exists rather than two, and MGT now depends on that
+package instead of carrying its own copy. Behaviour is unchanged: this module's tests pass
+against ``micromotion`` unmodified.
-* :func:`read_qtm_tsv` -- a single robust reader for Qualisys Track Manager
- (QTM) TSV exports, consolidating the four/five near-duplicate loaders that
- were copy-pasted across the study scripts.
-* :func:`compare_modality_envelopes` -- resample two motion envelopes onto a
- common per-second grid and correlate them (e.g. video-pose vs mocap
- validation).
-* :func:`dominant_frequency` -- the dominant spectral peak of a signal within
- a band, via Welch.
+The dependency points this way round on purpose. ``micromotion`` needs only numpy, scipy and
+pandas, so someone analysing accelerometer data does not have to install a computer-vision
+stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
+imports MGT.
-#### Notes
-
-:func:`compare_modality_envelopes` deliberately takes *precomputed* 1-D
-motion envelopes rather than computing quantity-of-motion internally, so
-this module stays independent of the QoM machinery. The natural producer
-of such envelopes is [band_limited_qom](_qom.md#band_limited_qom) followed by
-a per-second binning (``envelope`` / ``bin_series``), arriving in a
-sibling PR.
-
-Source: still standing study and Westney-comparisons study (Jensenius).
-
-## compare_modality_envelopes
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_mocap.py#L164)
-
-```python
-def compare_modality_envelopes(env_a, env_b, fs_a, fs_b):
-```
-
-Correlate two motion envelopes after resampling to a common grid.
-
-Resamples both 1-D envelopes onto a shared one-sample-per-second grid
-(by averaging within each second), truncates to the common length, and
-returns the Pearson correlation -- the video-vs-mocap (or view-vs-view)
-agreement measure. Both inputs are treated as already-computed motion
-envelopes (e.g. per-frame band-limited quantity-of-motion), keeping this
-function decoupled from the QoM computation itself. The per-second binning
-uses an integer-rounded step, so non-integer frame rates (e.g. 29.97 fps)
-drift slightly over long signals; this function is intended for validation
-rather than precise alignment.
-
-Source: still standing / Westney-comparisons study (Jensenius),
-MediaPipe-vs-mocap validation (``compare_mp_mocap``).
-
-#### Arguments
-
-- `env_a` *np.ndarray* - First 1-D motion envelope.
-- `env_b` *np.ndarray* - Second 1-D motion envelope.
-- `fs_a` *float* - Sampling rate of ``env_a`` in Hz.
-- `fs_b` *float* - Sampling rate of ``env_b`` in Hz.
-
-#### Returns
-
-- `dict` - ``{"r", "n"}`` where ``r`` is the Pearson correlation of the
- two per-second envelopes and ``n`` the number of common seconds
- (``r`` is ``nan`` if fewer than three overlapping seconds or if
- either resampled envelope is constant).
-
-## dominant_frequency
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_mocap.py#L130)
-
-```python
-def dominant_frequency(x, fs, band=(0.3, 4.0)):
-```
-
-Dominant frequency of a signal within a band, via a Welch spectrum.
-
-Returns the frequency of the largest Welch power-spectral-density peak
-inside ``band`` -- e.g. the dominant oscillation rate of a body-part
-speed or vertical-position signal.
-
-Source: Westney-comparisons study (Jensenius), extended motion-feature
-analysis (motion dominant frequency of vertical trunk position).
-
-#### Arguments
-
-- `x` *np.ndarray* - 1-D input signal.
-- `fs` *float* - Sampling rate in Hz.
-- `band` *tuple, optional* - ``(low, high)`` search band in Hz. Defaults
- to ``(0.3, 4.0)``.
-
-#### Returns
-
-- `float` - The dominant frequency in Hz, or ``nan`` if the band is empty.
-
-## read_qtm_tsv
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_mocap.py#L30)
-
-```python
-def read_qtm_tsv(path):
-```
-
-Read a Qualisys Track Manager (QTM) TSV motion-capture export.
-
-Consolidates the several near-duplicate loaders used across the studies
-into one robust reader. It locates the ``MARKER_NAMES`` header row to
-recover marker labels, autodetects where the numeric data block starts
-(the first row whose first field parses as a float), drops a trailing
-all-empty column produced by a trailing tab, converts exact-zero XYZ
-triples (Qualisys gap fills) to ``NaN``, and falls back from UTF-8 to
-latin-1 encoding. When a ``FREQUENCY`` header field is present the frame
-rate is returned as well.
-
-Source: still standing study and Westney-comparisons study (Jensenius) --
-unifies the ``load_qtm`` variants in the balance/dynamics/circular/
-spatial-range reports and the latin-1 variant in ``compare_mp_mocap``.
-
-#### Arguments
-
-- `path` *str* - Path to the ``.tsv`` file.
-
-#### Returns
-
-- `tuple` - ``(marker_names, data, fs)`` where ``marker_names`` is a list
- of ``M`` strings (empty if no header was found), ``data`` is a
- float array of shape ``(T, M, 3)`` with gaps as ``NaN``, and
- ``fs`` is the frame rate in Hz or ``None`` if not derivable from
- the header.
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
diff --git a/docs/musicalgestures/_motiondescriptors.md b/docs/musicalgestures/_motiondescriptors.md
index 328db63..eecba66 100644
--- a/docs/musicalgestures/_motiondescriptors.md
+++ b/docs/musicalgestures/_motiondescriptors.md
@@ -29,10 +29,10 @@ Scalar movement descriptors derived from the quantity-of-motion (QoM) signal.
Computes a compact set of higher-level descriptors that summarise *how* something moves,
complementing the per-frame motion data from :func:`motion`:
-- **motion_energy**—mean squared QoM; the overall amount of movement.
-- **motion_smoothness**—SPARC (spectral arc length) of the QoM profile; a dimensionless,
+- **motion_energy** — mean squared QoM; the overall amount of movement.
+- **motion_smoothness** — SPARC (spectral arc length) of the QoM profile; a dimensionless,
validated smoothness metric (less negative = smoother, more negative = jerkier).
-- **motion_entropy**—normalised (0–1) Shannon entropy of the QoM magnitude distribution;
+- **motion_entropy** — normalised (0–1) Shannon entropy of the QoM magnitude distribution;
the complexity/variedness of the motion.
- **spectral descriptors** of the QoM signal (Hann-windowed by default): the **dominant
frequency** (Hz, the main movement-rhythm rate) and the **spectral centroid** (Hz, the
@@ -40,7 +40,7 @@ complementing the per-frame motion data from :func:`motion`:
#### Arguments
-- `window` *str, optional* - FFT window for the spectral descriptors—'hann' (default,
+- `window` *str, optional* - FFT window for the spectral descriptors — 'hann' (default,
recommended to reduce leakage) or 'none' for a rectangular window.
- `entropy_bins` *int, optional* - Number of histogram bins for the entropy estimate. Defaults to 50.
- `fmin` *float, optional* - Lowest frequency (Hz) considered for the dominant frequency and
diff --git a/docs/musicalgestures/_motionvectors.md b/docs/musicalgestures/_motionvectors.md
index face307..01a4fac 100644
--- a/docs/musicalgestures/_motionvectors.md
+++ b/docs/musicalgestures/_motionvectors.md
@@ -25,7 +25,7 @@ how macroblocks move between frames. This method uses FFmpeg's ``codecview`` fil
giving a quick, decoder-level view of motion without any re-computation.
NB: Only codecs that actually carry motion vectors will show arrows. Intra-only
-formats (e.g. MJPEG, common in ``.avi`` files) have none. Convert to an inter-frame
+formats (e.g. MJPEG, common in ``.avi`` files) have none — convert to an inter-frame
codec first (e.g. via ``show(mode='notebook')`` which makes an mp4, or any mp4/h264
source) to see motion vectors.
diff --git a/docs/musicalgestures/_motionvideo.md b/docs/musicalgestures/_motionvideo.md
index 48d643f..94ce467 100644
--- a/docs/musicalgestures/_motionvideo.md
+++ b/docs/musicalgestures/_motionvideo.md
@@ -62,9 +62,9 @@ centroid of motion for each frame with timecodes in milliseconds.
- `unit` *str, optional* - Unit in QoM plot. Accepted values are 'seconds' or 'samples'. Defaults to 'seconds'.
- `atadenoise` *bool, optional* - If True, applies an adaptive temporal averaging denoiser every 129 frames. Defaults to False.
- `motion_analysis` *str, optional* - Specify which motion analysis to process or all. 'AoM' renders the Area of Motion. 'CoM' renders the Centroid of Motion. 'QoM' renders the Quantity of Motion. 'all' renders all the motion analysis available. Defaults to 'all'.
-- `inverted_motionvideo` *bool, optional* - If True, inverts colours of the motion video. Defaults to False.
-- `inverted_motiongram` *bool, optional* - If True, inverts colours of the motiongrams. Defaults to False.
-- `equalize_motiongram` *bool, optional* - If True, converts the motiongrams to hsv-colour space and flattens the value channel (v). Defaults to True.
+- `inverted_motionvideo` *bool, optional* - If True, inverts colors of the motion video. Defaults to False.
+- `inverted_motiongram` *bool, optional* - If True, inverts colors of the motiongrams. Defaults to False.
+- `equalize_motiongram` *bool, optional* - If True, converts the motiongrams to hsv-color space and flattens the value channel (v). Defaults to True.
- `save_plot` *bool, optional* - If True, outputs motion-plot. Defaults to True.
- `title` *str, optional* - Optionally add title to the plot. Defaults to None, which uses the file name as a title.
- `save_data` *bool, optional* - If True, outputs motion-data. Defaults to True.
@@ -153,8 +153,8 @@ Shortcut for [mg_motion](#mg_motion) to only render motiongrams.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `atadenoise` *bool, optional* - If True, applies an adaptive temporal averaging denoiser every 129 frames. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
-- `inverted_motiongram` *bool, optional* - If True, inverts colours of the motiongrams. Defaults to False.
-- `equalize_motiongram` *bool, optional* - If True, converts the motiongrams to hsv-colour space and flattens the value channel (v). Defaults to True.
+- `inverted_motiongram` *bool, optional* - If True, inverts colors of the motiongrams. Defaults to False.
+- `equalize_motiongram` *bool, optional* - If True, converts the motiongrams to hsv-color space and flattens the value channel (v). Defaults to True.
- `target_name_mgx` *str, optional* - Target output name for the vertical motiongram. Defaults to None (which assumes that the input filename with the suffix "_mgv" should be used).
- `target_name_mgy` *str, optional* - Target output name for the horizontal motiongram. Defaults to None (which assumes that the input filename with the suffix "_mgh" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
@@ -247,7 +247,7 @@ Shortcut to only render the motion video. Uses musicalgestures._utils.motionvide
- `blur` *str, optional* - 'Average' to apply a 10px * 10px blurring filter, 'None' otherwise. Defaults to 'None'.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
-- `inverted_motionvideo` *bool, optional* - If True, inverts colours of the motion video. Defaults to False.
+- `inverted_motionvideo` *bool, optional* - If True, inverts colors of the motion video. Defaults to False.
- `target_name` *str, optional* - Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_motion" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
diff --git a/docs/musicalgestures/_peaks.md b/docs/musicalgestures/_peaks.md
index 47f2519..8fbaa88 100644
--- a/docs/musicalgestures/_peaks.md
+++ b/docs/musicalgestures/_peaks.md
@@ -49,14 +49,14 @@ a prominence, again expressed as a fraction of the signal's peak
The default constants (3-tap smoothing, 0.50 x peak threshold, 0.30 s
minimum interval, 0.20 x peak prominence) are the "selective" video
-quantity-of-motion settings from the cymbal-comparison study and are
-PROVISIONAL defaults: that study's prose and deposited JSON summaries
-disagree on some values (e.g. 0.25 x peak with a 0.10 s interval in one
-deposit), so tune the parameters to the signal at hand rather than
-relying on the defaults. For reference, the same study used
+quantity-of-motion settings from the cymbal-comparison study. A 2026
+revalidation on the original dataset (Zenodo 21360429) confirmed these
+prose constants as accurate; the deposited JSON summary's conflicting
+method string (0.25 x peak / 0.10 s) was found to be inconsistent with
+its own archived results. For reference, the same study used
0.12 x peak / 0.10 s for hand-acceleration impacts, 0.15 x peak /
0.06 s for audio energy onsets, and 0.40 x peak / 0.20 s for
-wrist-speed peaks.
+wrist-speed peaks. Tune the parameters to your signal at hand.
Source: cymbal-comparison study (Jensenius), reimplemented from the
paper's method description; also subsumes the peak-picking conventions
diff --git a/docs/musicalgestures/_physio.md b/docs/musicalgestures/_physio.md
index fb7f4c5..1a6eeef 100644
--- a/docs/musicalgestures/_physio.md
+++ b/docs/musicalgestures/_physio.md
@@ -2,104 +2,18 @@
> Auto-generated documentation for [musicalgestures._physio](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_physio.py) module.
-Physiology signal features for standstill / micromotion studies.
+Re-exported from the ``micromotion`` package.
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Physio
- - [respiration_rate](#respiration_rate)
- - [spectral_band_fractions](#spectral_band_fractions)
-Two pure numpy/scipy surfaces ported from the "still standing" study:
+These functions used to live here. They were moved to ``micromotion`` on 2026-07-29 so that
+one implementation of quantity of motion exists rather than two, and MGT now depends on that
+package instead of carrying its own copy. Behaviour is unchanged: this module's tests pass
+against ``micromotion`` unmodified.
-* :func:`respiration_rate` -- windowed breathing rate (breaths per minute)
- from a respiration waveform, via band-pass filtering and a Welch spectral
- peak per window.
-* :func:`spectral_band_fractions` -- the fraction of a signal's Welch power
- falling in each of a set of caller-supplied named frequency bands. This is
- the generic "cardiorespiratory QoM" spectral-composition diagnostic with
- the heart-rate/respiration bands supplied by the caller, so the function
- carries no dependency on any particular physiological sensor.
+The dependency points this way round on purpose. ``micromotion`` needs only numpy, scipy and
+pandas, so someone analysing accelerometer data does not have to install a computer-vision
+stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
+imports MGT.
-Source: still standing study (Jensenius) -- Deichman / Equivital physiology
-analyses.
-
-## respiration_rate
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_physio.py#L22)
-
-```python
-def respiration_rate(waveform, fs, band=(0.1, 0.6), window_s=30, step_s=30):
-```
-
-Windowed respiration rate (breaths per minute) from a breathing waveform.
-
-Each analysis window is band-pass filtered to the respiration band and
-its dominant frequency is taken as the Welch spectral peak inside that
-band; the rate is that frequency times 60. Windows advance by ``step_s``
-seconds. The default band ``(0.1, 0.6)`` Hz corresponds to about
-6-36 breaths/min. Each window must contain at least 15 seconds of valid
-samples for spectral estimation.
-
-Source: still standing study (Jensenius), Deichman respiration analysis
-(``compute_qom_resp``).
-
-#### Arguments
-
-- `waveform` *np.ndarray* - 1-D respiration/breathing waveform.
-- `fs` *float* - Sampling rate in Hz.
-- `band` *tuple, optional* - ``(low, high)`` respiration band in Hz.
- Defaults to ``(0.1, 0.6)``.
-- `window_s` *float, optional* - Window length in seconds. Defaults to 30.
-- `step_s` *float, optional* - Hop between windows in seconds. Defaults to
- 30.
-
-#### Returns
-
-- `dict` - ``{"rate_bpm", "times_s", "median_bpm"}`` where ``rate_bpm`` is
- the per-window rate (breaths/min, ``nan`` for windows without a
- clear peak), ``times_s`` the window centre times in seconds, and
- ``median_bpm`` the median across valid windows.
-
-## spectral_band_fractions
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_physio.py#L94)
-
-```python
-def spectral_band_fractions(
- signal,
- fs,
- bands,
- total_band=(0.1, 8.0),
- nperseg_s=20,
-):
-```
-
-Fraction of a signal's power in each of a set of named frequency bands.
-
-Estimates the Welch power spectrum and, for each named band in ``bands``,
-returns that band's summed power divided by the summed power in
-``total_band``. This is the generic spectral-composition diagnostic used
-for the "cardiorespiratory QoM artifact" analysis (e.g. how much of a
-chest-accelerometer QoM signal sits in a cardiac vs a respiration band),
-with the bands supplied by the caller so there is no built-in dependence
-on a heart-rate or respiration sensor. Power is bin-summed on the Welch
-grid; the study source integrated with trapz, which yields nearly
-identical results on the uniform frequency spacing of Welch.
-
-Source: still standing study (Jensenius), Deichman chest-QoM
-cardiorespiratory spectral-composition analysis (``deichman_full``).
-
-#### Arguments
-
-- `signal` *np.ndarray* - 1-D input signal.
-- `fs` *float* - Sampling rate in Hz.
-- `bands` *dict* - Mapping of band name to ``(low, high)`` in Hz, e.g.
- - ```{"cardiac"` - (0.9, 1.3), "resp": (0.12, 0.5)}``.
-- `total_band` *tuple, optional* - ``(low, high)`` reference band whose
- power is the denominator. Defaults to ``(0.1, 8.0)``.
-- `nperseg_s` *float, optional* - Welch segment length in seconds.
- Defaults to 20.
-
-#### Returns
-
-- `dict` - Mapping of each band name to its power fraction in ``[0, 1]``
- (``nan`` if the total band contains no power).
+Import from ``micromotion`` directly in new code.
diff --git a/docs/musicalgestures/_pose.md b/docs/musicalgestures/_pose.md
index c146bf2..9b583c3 100644
--- a/docs/musicalgestures/_pose.md
+++ b/docs/musicalgestures/_pose.md
@@ -30,6 +30,8 @@ Uses Python's ``urllib`` directly (cross-platform, no external ``wget`` / shell
bundled binary). The ``.prototxt`` configs ship with the package; only the large weights file
is fetched.
+Returns the downloaded file path on success, or ``None`` if download attempts fail.
+
## mg_pose_center
[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_pose.py#L962)
@@ -45,12 +47,12 @@ def mg_pose_center(
) -> 'MgFigure':
```
-Centre the pose data on its global centroid, a 2D port of the MoCap Toolbox ``mccenter``.
+Centre the pose data on its global centroid — a 2D port of the MoCap Toolbox ``mccenter``.
A single offset per coordinate (the mean of the per-marker temporal means, missing detections
ignored) is subtracted from every marker so the overall spatiotemporal centroid sits at the
origin (0, 0). This removes the performer's absolute position in the frame, leaving relative
-posture/movement, useful before comparing or further analysing trajectories. Plots the centred
+posture/movement — useful before comparing or further analysing trajectories. Plots the centred
marker trajectories and (by default) saves a CSV of the centred coordinates. Uses cached pose
keypoints when available, otherwise runs ``pose()`` first (``**pose_kwargs`` are forwarded).
@@ -81,7 +83,7 @@ def mg_pose_distance(
) -> 'MgFigure':
```
-Per-marker distance travelled and the average across markers, a 2D port of the MoCap Toolbox
+Per-marker distance travelled and the average across markers — a 2D port of the MoCap Toolbox
``mccumdist``.
Sums each marker's frame-to-frame Euclidean displacement (in pixels) and accumulates it over
@@ -125,8 +127,8 @@ Circular (polar) motion plots and statistics for each body segment.
A *segment* is the bone between two connected joints (e.g. shoulder–elbow). For every segment
this computes its per-frame orientation angle and draws a polar rose histogram of the angle
distribution with the mean-direction resultant vector, annotated with circular statistics
-(mean angle, resultant length R, and range of motion). A CSV of the per-segment statistics—mean
- angle, R, circular std, range of motion, and mean angular speed—is saved alongside the
+(mean angle, resultant length R, and range of motion). A CSV of the per-segment statistics —
+mean angle, R, circular std, range of motion, and mean angular speed — is saved alongside the
image. Uses cached pose keypoints from a previous ``pose()`` call when available; otherwise it
runs pose estimation first (``model``/``device``/… are forwarded to ``pose()``).
@@ -170,8 +172,8 @@ def mg_pose_waterfall(
) -> 'MgFigure':
```
-Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis, a
- pose-based counterpart to ``silhouette_waterfall()``. Uses cached pose keypoints from a
+Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis —
+a pose-based counterpart to ``silhouette_waterfall()``. Uses cached pose keypoints from a
previous ``pose()`` call when available; otherwise it runs pose estimation first (extra
keyword arguments such as ``model``/``device``/``downsampling_factor`` are forwarded to
``pose()``).
@@ -239,7 +241,7 @@ def pose(
```
Renders a video with the pose estimation (aka. "keypoint detection" or "skeleton tracking") overlaid on it.
-Outputs the predictions in a text file containing the normalised x and y coordinates of each keypoint
+Outputs the predictions in a text file containing the normalized x and y coordinates of each keypoint
(default format is csv).
Supports two backends:
@@ -265,14 +267,14 @@ Supports two backends:
- `device` *str, optional* - Compute backend ('cpu' or 'gpu'). For OpenPose models this
selects the OpenCV DNN backend (GPU needs a CUDA-enabled OpenCV). For MediaPipe
it selects the inference delegate (GPU delegate with CPU fallback). Defaults to 'gpu'.
-- `threshold` *float, optional* - The normalised confidence threshold that decides whether we
+- `threshold` *float, optional* - The normalized confidence threshold that decides whether we
keep or discard a predicted point. Discarded points get substituted with (0, 0) in the
output data. Defaults to 0.1.
- `downsampling_factor` *int, optional* - Decides how much we downsample the video before we
pass it to the neural network. Ignored when ``model='mediapipe'``. Defaults to 2.
- `use_cache` *bool, optional* - If True (default), reuse keypoints from a previous pose() run on
this object (same model/threshold) to re-render a different `style`/`overlay`/`background`
- without re-running the network, e.g. run `style='markers'` then `style='skeleton'` fast.
+ without re-running the network — e.g. run `style='markers'` then `style='skeleton'` fast.
Defaults to True.
- `save_data` *bool, optional* - Whether we save the predicted pose data to a file. Defaults to True.
- `data_format` *str, optional* - Specifies format of pose-data. Accepted values are 'csv', 'tsv',
diff --git a/docs/musicalgestures/_pose_visualize.md b/docs/musicalgestures/_pose_visualize.md
index 116efb4..4f62430 100644
--- a/docs/musicalgestures/_pose_visualize.md
+++ b/docs/musicalgestures/_pose_visualize.md
@@ -1,8 +1,8 @@
-# Pose Visualise
+# Pose Visualize
> Auto-generated documentation for [musicalgestures._pose_visualize](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_pose_visualize.py) module.
-- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Pose Visualise
+- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Pose Visualize
- [pose_center](#pose_center)
- [pose_distance](#pose_distance)
- [render_average_pose](#render_average_pose)
@@ -22,8 +22,8 @@ def pose_center(data, names):
Centre pose data on its global centroid (a 2D port of the MoCap Toolbox ``mccenter``).
-Computes a single offset per coordinate dimension—the mean of the per-marker temporal means
-(missing detections ignored)—and subtracts it from every marker so the overall
+Computes a single offset per coordinate dimension — the mean of the per-marker temporal means
+(missing detections ignored) — and subtracts it from every marker so the overall
spatiotemporal centroid sits at the origin (0, 0).
#### Arguments
@@ -165,8 +165,8 @@ def render_pose_waterfall(
):
```
-Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis, a
- pose-based counterpart to ``silhouette_waterfall()``.
+Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis —
+a pose-based counterpart to ``silhouette_waterfall()``.
``style`` selects what is drawn:
diff --git a/docs/musicalgestures/_posetools.md b/docs/musicalgestures/_posetools.md
index 07930e6..85954fa 100644
--- a/docs/musicalgestures/_posetools.md
+++ b/docs/musicalgestures/_posetools.md
@@ -15,7 +15,7 @@ fourMs sound--motion studies: video file -> tidy per-landmark trajectory
arrays (and optionally CSV) -> derived motion signals (limb speed, impact
events).
-It complements—and does not replace—the rendering-oriented
+It complements — and does not replace — the rendering-oriented
``MgVideo.pose()`` pipeline in :mod:[Pose](_pose.md#pose) (overlaid skeleton
video, average-pose image, trajectory image, keypoint CSV) and the per-frame
:class:[PoseEstimator](_pose_estimator.md#poseestimator) interface. Use this
@@ -158,14 +158,15 @@ interval of ``min_interval_s``.
The threshold parameters are taken directly (the small relative-threshold
peak picker is implemented inline here); a general adaptive peak-picker,
``pick_peaks``, is provided by the sibling core-signal-methods PR in
-[Peaks](_peaks.md#peaks). The defaults (0.12 x peak, 100 ms) are the
-cymbal study's provisional values for 120 Hz mocap hand data and should be
-tuned per dataset. Note the study's caveat: double-differentiating
-(model-reconstructed) positions is noisy and also responds to the
-backswing, not only the collision. Treat the detected peaks as *candidate*
-impacts and validate against another modality (e.g. audio onsets) where
-possible. For whole-image visual impact detection from video (no
-landmarks), see ``MgVideo.impacts()`` instead.
+[Peaks](_peaks.md#peaks). The defaults (0.12 x peak, 100 ms) are
+validated against the original cymbal dataset (Zenodo 21360429, 2026
+revalidation) for 120 Hz mocap hand data and should be tuned per dataset.
+Note the study's caveat: double-differentiating (model-reconstructed)
+positions is noisy and also responds to the backswing, not only the
+collision — treat the detected peaks as *candidate* impacts and validate
+against another modality (e.g. audio onsets) where possible. For
+whole-image visual impact detection from video (no landmarks), see
+``MgVideo.impacts()`` instead.
#### Arguments
@@ -217,13 +218,13 @@ For each candidate limb (e.g. the left and right wrist), frames whose
landmark confidence/visibility falls below ``conf_gate`` are masked out
(NaN), and the limb speed is formed as the central-difference magnitude of
the pixel path (px/s). Candidate limbs are then merged by element-wise
-maximum—so that motion of *either* limb registers, mirroring the
-bilateral merge used for inertial hand data—and lightly smoothed with a
+maximum — so that motion of *either* limb registers, mirroring the
+bilateral merge used for inertial hand data — and lightly smoothed with a
short NaN-aware moving average. Peaks of the resulting signal mark, e.g.,
strike downstrokes of the striking wrist.
Caveats (from the cymbal study): these are 2D apparent kinematics from a
-single camera, so motion toward/away from the lens is foreshortened and pixel
+single camera — motion toward/away from the lens is foreshortened and pixel
speed is not metric speed. Moreover, a limb-speed peak marks *maximum
downstroke speed*, which systematically precedes the contact/arrest that an
audio onset or an acceleration peak registers; account for this bias when
diff --git a/docs/musicalgestures/_posture.md b/docs/musicalgestures/_posture.md
index 483c1bc..43dc22a 100644
--- a/docs/musicalgestures/_posture.md
+++ b/docs/musicalgestures/_posture.md
@@ -2,438 +2,20 @@
> Auto-generated documentation for [musicalgestures._posture](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py) module.
-Posturography and standstill-sway metrics for centre-of-pressure (CoP) and
-head/marker position signals.
+Re-exported from the ``micromotion`` package.
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Posture
- - [axial_rayleigh](#axial_rayleigh)
- - [confidence_ellipse_area](#confidence_ellipse_area)
- - [convex_hull_area](#convex_hull_area)
- - [cop_sway_metrics](#cop_sway_metrics)
- - [dfa](#dfa)
- - [principal_axis_projection](#principal_axis_projection)
- - [sample_entropy](#sample_entropy)
- - [spatial_extent](#spatial_extent)
- - [spectral_edges](#spectral_edges)
- - [stabilogram_diffusion](#stabilogram_diffusion)
- - [sway_orientation](#sway_orientation)
- - [sway_texture](#sway_texture)
-This module ports the "still standing" study's posturography stack into
-pure numpy/scipy surfaces that operate on plain arrays -- no study-specific
-loaders, axis conventions, or marker loops. Three families of measures are
-provided:
+These functions used to live here. They were moved to ``micromotion`` on 2026-07-29 so that
+one implementation of quantity of motion exists rather than two, and MGT now depends on that
+package instead of carrying its own copy. Behaviour is unchanged: this module's tests pass
+against ``micromotion`` unmodified.
-* **Sway amount / geometry** -- :func:`cop_sway_metrics`,
- :func:`confidence_ellipse_area`, :func:`convex_hull_area`.
-* **Control dynamics / complexity** -- :func:`stabilogram_diffusion`
- (Collins-De Luca SDA), :func:`dfa` (detrended fluctuation analysis),
- :func:`sample_entropy`, :func:`spectral_edges`, :func:`sway_texture`,
- :func:`principal_axis_projection`.
-* **Direction / extent** -- :func:`sway_orientation`, :func:`axial_rayleigh`,
- :func:`spatial_extent`.
+The dependency points this way round on purpose. ``micromotion`` needs only numpy, scipy and
+pandas, so someone analysing accelerometer data does not have to install a computer-vision
+stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
+imports MGT.
-The from-scratch SDA / DFA / sample-entropy implementations are validated in
-the test-suite against known-answer synthetic signals (white noise ->
-DFA alpha ~= 0.5 and SDA Hurst ~= 0.5; a sine -> low sample entropy relative
-to its shuffle).
-
-Source: still standing study (Jensenius) -- posturography and micromotion
-analyses of the international "standstill" championships and related datasets.
-
-## axial_rayleigh
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L499)
-
-```python
-def axial_rayleigh(angles_deg):
-```
-
-Axial Rayleigh test for a preferred orientation among axial angles.
-
-Tests whether a sample of axial (undirected, ``[0, 180)`` deg) angles --
-e.g. per-session principal sway axes -- clusters around a common
-orientation. Angles are doubled to map the axial circle onto the full
-circle before computing the mean resultant length ``R`` and the Rayleigh
-p-value (small ``p`` with large ``R`` means a shared preferred axis).
-
-Source: still standing study (Jensenius), sway-direction analysis.
-
-#### Arguments
-
-- `angles_deg` *np.ndarray* - Axial angles in degrees.
-
-#### Returns
-
-- `dict` - ``{"R", "p", "mean_axis_deg", "n"}``.
-
-## confidence_ellipse_area
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L34)
-
-```python
-def confidence_ellipse_area(xy, conf=0.95):
-```
-
-Area of the confidence ellipse of a 2-D point cloud (e.g. a
-centre-of-pressure trace).
-
-The ellipse is the standard bivariate-Gaussian confidence region
-``area = pi * chi2_conf,2df * sqrt(det Cov)`` where ``Cov`` is the
-2x2 covariance of the (mean-removed) points. For a CoP sway path this
-is the classic 95% "sway-ellipse area".
-
-Source: still standing study (Jensenius), HpSp balance analysis.
-
-#### Arguments
-
-- `xy` *np.ndarray* - Point cloud of shape ``(T, 2)``.
-- `conf` *float, optional* - Confidence level in ``(0, 1)``. Defaults to
- 0.95.
-
-#### Returns
-
-- `float` - Ellipse area in squared position units (e.g. mm^2), or
- ``nan`` if fewer than three finite points are available.
-
-## convex_hull_area
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L65)
-
-```python
-def convex_hull_area(xy):
-```
-
-Area of the 2-D convex hull of a point cloud.
-
-A non-parametric alternative to :func:[confidence_ellipse_area](#confidence_ellipse_area) for the
-region occupied by a sway path: it makes no Gaussian assumption and is
-driven by the outermost excursions.
-
-Source: still standing study (Jensenius); complements the confidence
-ellipse used in the balance reports.
-
-#### Arguments
-
-- `xy` *np.ndarray* - Point cloud of shape ``(T, 2)``.
-
-#### Returns
-
-- `float` - Convex-hull area in squared position units, or ``nan`` if
- fewer than three non-collinear finite points are available.
-
-## cop_sway_metrics
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L95)
-
-```python
-def cop_sway_metrics(
- xy,
- t=None,
- fs=None,
- freq_band=(0.1, 5.0),
- resample_fs=50.0,
-):
-```
-
-Standard centre-of-pressure (CoP) sway metrics from a 2-D sway path.
-
-Computes the classic posturographic descriptors: CoP path length and
-path rate, the 95% confidence-ellipse area, medio-lateral (ML) and
-antero-posterior (AP) ranges and standard deviations, the AP/ML range
-and SD ratios, and the mean sway frequency of each axis (the
-power-weighted mean frequency of a Welch spectrum inside ``freq_band``,
-computed on a uniform grid at ``resample_fs``).
-
-The first column of ``xy`` is treated as ML and the second as AP,
-matching the study convention. Sampling time may be given either as an
-explicit time vector ``t`` (seconds; may be irregular) or a constant
-rate ``fs`` (Hz); if neither is supplied a rate of 1 Hz is assumed.
-
-Source: still standing study (Jensenius), HpSp balance analysis
-(``analyze_balance``).
-
-#### Arguments
-
-- `xy` *np.ndarray* - CoP path of shape ``(T, 2)`` as ``[ML, AP]`` in
- position units (e.g. mm).
-- `t` *np.ndarray, optional* - Per-sample timestamps in seconds. May be
- irregular. Defaults to None.
-- `fs` *float, optional* - Constant sampling rate in Hz, used when ``t``
- is not given. Defaults to None (interpreted as 1 Hz).
-- `freq_band` *tuple, optional* - ``(low, high)`` band in Hz for the mean
- sway frequency. Defaults to ``(0.1, 5.0)``.
-- `resample_fs` *float, optional* - Uniform rate in Hz onto which the
- path is interpolated before the spectral estimate. Defaults to
- 50.0.
-
-#### Returns
-
-- `dict` - Metrics with keys ``n``, ``dur``, ``fs_mean``, ``path_len``,
- ``path_rate``, ``area95``, ``ml_range``, ``ap_range``,
- ``ml_sd``, ``ap_sd``, ``ap_ml_range_ratio``,
- ``ap_ml_sd_ratio``, ``mf_ml``, ``mf_ap`` and ``mf_mean``.
-
-## dfa
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L293)
-
-```python
-def dfa(x, n_scales=18, min_scale=10):
-```
-
-Detrended fluctuation analysis (DFA) scaling exponent.
-
-Integrates the mean-removed signal, then measures the RMS of the
-linearly-detrended integrated profile within non-overlapping windows of
-increasing size; the slope of ``log F(n)`` versus ``log n`` is the DFA
-exponent ``alpha``. White noise gives ``alpha ~= 0.5``; a random walk
-(Brownian) gives ``alpha ~= 1.5``; ``alpha == 1`` is 1/f noise.
-
-Source: still standing study (Jensenius), sway-complexity analysis;
-method of Peng et al. (1994).
-
-#### Arguments
-
-- `x` *np.ndarray* - 1-D input signal.
-- `n_scales` *int, optional* - Number of log-spaced window sizes.
- Defaults to 18.
-- `min_scale` *int, optional* - Smallest window size in samples. Defaults
- to 10.
-
-#### Returns
-
-- `float` - The DFA exponent ``alpha`` (``nan`` if too short).
-
-## principal_axis_projection
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L200)
-
-```python
-def principal_axis_projection(xy):
-```
-
-Project a 2-D (or N-D) point cloud onto its principal axis.
-
-Runs a PCA on the mean-removed points and returns the 1-D coordinate
-along the direction of greatest variance -- the natural 1-D reduction of
-a sway path used by the dynamics/complexity measures. The PCA eigenvector
-sign is arbitrary; the projection may be globally flipped across calls or
-datasets.
-
-Source: still standing study (Jensenius), sway-dynamics analysis.
-
-#### Arguments
-
-- `xy` *np.ndarray* - Point cloud of shape ``(T, D)`` (typically
- ``D == 2``).
-
-#### Returns
-
-- `np.ndarray` - 1-D projection of shape ``(T,)``.
-
-## sample_entropy
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L344)
-
-```python
-def sample_entropy(x, m=2, r=0.2):
-```
-
-Sample entropy (SampEn) of a 1-D signal.
-
-Measures regularity/predictability: the negative log conditional
-probability that sequences close (within tolerance ``r``) for ``m``
-samples remain close for ``m + 1`` samples, self-matches excluded.
-Lower values mean more repetitive/predictable signals. The signal is
-z-scored internally so ``r`` is expressed as a fraction of its standard
-deviation. Neighbour counts use a Chebyshev (max-norm) KD-tree.
-
-Source: still standing study (Jensenius), sway-complexity analysis;
-method of Richman & Moorman (2000).
-
-#### Arguments
-
-- `x` *np.ndarray* - 1-D input signal.
-- `m` *int, optional* - Embedding (template) length. Defaults to 2.
-- `r` *float, optional* - Tolerance as a fraction of the signal SD.
- Defaults to 0.2.
-
-#### Returns
-
-- `float` - Sample entropy (``nan`` if undefined, e.g. no matches).
-
-## spatial_extent
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L531)
-
-```python
-def spatial_extent(
- pos,
- fs,
- ellipse_conf=0.95,
- window_s=20.0,
- vertical_axis=None,
-):
-```
-
-Spatial extent / occupied volume of a 3-D (or 2-D) position trace.
-
-Complements sway magnitude (QoM) by describing *how large a region* a
-marker occupies. Reports the RMS dispersion radius about the session
-centroid, the Gaussian confidence-ellipsoid volume and its cube-root
-radius, the mean within-window dispersion (which removes slow drift),
-and a drift ratio ``full_dispersion / within_window_dispersion`` (``> 1``
-when slow drift enlarges the occupied region over the session). When a
-``vertical_axis`` is given the drift is additionally split into
-horizontal and vertical components.
-
-Source: still standing study (Jensenius), spatial-range analysis
-(``session_metrics``).
-
-#### Arguments
-
-- `pos` *np.ndarray* - Position trace of shape ``(T, D)`` with ``D`` 2 or
- 3, in position units (e.g. mm).
-- `fs` *float* - Sampling rate in Hz.
-- `ellipse_conf` *float, optional* - Confidence level for the ellipsoid
- volume. Defaults to 0.95.
-- `window_s` *float, optional* - Window length in seconds for the
- within-window dispersion. Defaults to 20.0.
-- `vertical_axis` *int, optional* - Index of the vertical axis (``0``,
- ``1`` or ``2``); when given, drift is decomposed into horizontal
- and vertical parts. Defaults to None.
-
-#### Returns
-
-- `dict` - ``dispersion``, ``ellipsoid_volume``, ``ellipsoid_radius``,
- ``within_window_dispersion``, ``drift_ratio`` and (when
- ``vertical_axis`` is set) ``drift_horizontal`` and
- ``drift_vertical``. Returns ``None`` if fewer than one window of
- finite samples is available.
-
-## spectral_edges
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L389)
-
-```python
-def spectral_edges(x, fs, edges=(0.5, 0.95), nperseg=None):
-```
-
-Spectral-edge frequencies of a signal.
-
-Returns the frequencies below which a given cumulative fraction of the
-Welch power spectrum lies. With the default ``edges`` the first value is
-the median frequency (50% edge) and the second the 95% spectral-edge
-frequency, two standard descriptors of sway spectral shape.
-
-Source: still standing study (Jensenius), sway-dynamics analysis.
-
-#### Arguments
-
-- `x` *np.ndarray* - 1-D input signal.
-- `fs` *float* - Sampling rate in Hz.
-- `edges` *tuple, optional* - Cumulative-power fractions in ``(0, 1)``.
- Defaults to ``(0.5, 0.95)``.
-- `nperseg` *int, optional* - Welch segment length in samples. Defaults
- to ``min(2048, len(x))``.
-
-#### Returns
-
-- `dict` - Mapping ``"f"`` (e.g. ``"f50"``, ``"f95"``) to the edge
- frequency in Hz.
-
-## stabilogram_diffusion
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L226)
-
-```python
-def stabilogram_diffusion(xy, fs, short_max_s=0.6, long_min_s=1.5, n_lags=40):
-```
-
-Collins-De Luca stabilogram-diffusion analysis (SDA) of a sway path.
-
-Fits the mean-square-displacement (MSD) curve
-``<[r(t+dt) - r(t)]^2>`` versus time-lag ``dt`` in log-log space and
-reports a short-term and a long-term Hurst exponent (each ``slope / 2``)
-plus the critical crossover time where the two regression lines
-intersect. Persistent (open-loop) drift gives a short-term Hurst above
-0.5; anti-persistent (closed-loop correction) gives a long-term Hurst
-below 0.5.
-
-Source: still standing study (Jensenius), sway-dynamics analysis;
-method of Collins & De Luca (1993).
-
-#### Arguments
-
-- `xy` *np.ndarray* - Sway path of shape ``(T, D)`` (``D >= 1``). A 1-D
- input of shape ``(T,)`` is accepted and treated as a single
- axis.
-- `fs` *float* - Sampling rate in Hz.
-- `short_max_s` *float, optional* - Upper bound (s) of the short-term
- fitting window. Defaults to 0.6.
-- `long_min_s` *float, optional* - Lower bound (s) of the long-term
- fitting window. Defaults to 1.5.
-- `n_lags` *int, optional* - Number of log-spaced lags at which the MSD
- is evaluated. Defaults to 40.
-
-#### Returns
-
-- `dict` - ``{"H_short", "H_long", "crossover_s"}``. Entries are ``nan``
- when a window contains fewer than three usable lags.
-
-## sway_orientation
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L465)
-
-```python
-def sway_orientation(xy):
-```
-
-Principal sway-axis orientation and anisotropy of a 2-D point cloud.
-
-A PCA of the mean-removed horizontal positions gives the orientation of
-the major axis as an axial angle in ``[0, 180)`` degrees (undirected --
-a line, not an arrow) and the anisotropy ``sqrt(lambda_max / lambda_min)``
-of the sway ellipse. Anisotropy 1.0 is isotropic/circular; values above
-~1.3 indicate clearly directional sway.
-
-Source: still standing study (Jensenius), sway-direction analysis.
-
-#### Arguments
-
-- `xy` *np.ndarray* - Point cloud of shape ``(T, 2)``.
-
-#### Returns
-
-- `dict` - ``{"angle_deg", "anisotropy"}``. Both are ``nan`` if the
- covariance is degenerate or there are too few finite points.
-
-## sway_texture
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_posture.py#L429)
-
-```python
-def sway_texture(speed, fs, frozen_threshold=2.0):
-```
-
-Micro-texture of a sway speed signal: frozen fraction and burst rate.
-
-Distinguishes a smooth wander from intermittent ballistic corrections.
-The frozen fraction is the share of time the speed is below
-``frozen_threshold``; the burst rate is the number of upward threshold
-crossings (onset of a velocity burst) per minute.
-
-Source: still standing study (Jensenius), sway-texture analysis.
-
-#### Arguments
-
-- `speed` *np.ndarray* - 1-D speed signal (e.g. mm/s).
-- `fs` *float* - Sampling rate in Hz.
-- `frozen_threshold` *float, optional* - Speed below which the signal is
- considered "frozen", in the units of ``speed``. Defaults to 2.0.
-
-#### Returns
-
-- `dict` - ``{"frozen_fraction", "burst_rate"}`` where ``burst_rate`` is
- in bursts per minute.
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
diff --git a/docs/musicalgestures/_qom.md b/docs/musicalgestures/_qom.md
index ac14b5c..4e83ab4 100644
--- a/docs/musicalgestures/_qom.md
+++ b/docs/musicalgestures/_qom.md
@@ -2,350 +2,20 @@
> Auto-generated documentation for [musicalgestures._qom](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py) module.
-Quantity-of-motion (QoM) signal cores for position, pose and accelerometer
-data.
+Re-exported from the ``micromotion`` package.
- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Qom
- - [accel_to_speed](#accel_to_speed)
- - [band_limited_qom](#band_limited_qom)
- - [bin_series](#bin_series)
- - [body_scale](#body_scale)
- - [envelope](#envelope)
- - [grid_qom](#grid_qom)
- - [group_qom](#group_qom)
- - [normalized_qom](#normalized_qom)
- - [pose_qom](#pose_qom)
-Band-limited QoM (with an automatic decimate+SOS regime for very low
-frequency bands), accelerometer-to-speed integration, per-landmark-group
-pose QoM, body-scale normalisation for framing-invariant comparisons,
-spatial grid QoM, and small envelope/binning helpers.
+These functions used to live here. They were moved to ``micromotion`` on 2026-07-29 so that
+one implementation of quantity of motion exists rather than two, and MGT now depends on that
+package instead of carrying its own copy. Behaviour is unchanged: this module's tests pass
+against ``micromotion`` unmodified.
-These functions are independent of the MgVideo/MgAudio classes and operate
-on plain numpy arrays (marker/landmark trajectories, accelerometer data,
-grayscale frame stacks, 1-D signals).
+The dependency points this way round on purpose. ``micromotion`` needs only numpy, scipy and
+pandas, so someone analysing accelerometer data does not have to install a computer-vision
+stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
+imports MGT.
-Sources: stillstanding study and Westney-comparisons study (Jensenius).
-
-## accel_to_speed
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L164)
-
-```python
-def accel_to_speed(acc, fs, highpass=0.3, order=2, normalize_gravity=False):
-```
-
-Integrated speed from a 3-axis accelerometer: each axis is high-pass
-filtered (removing gravity and DC), integrated to velocity, high-pass
-filtered again (killing integration drift), and the speed is the
-Euclidean norm of the velocity (m/s for input in m/s^2).
-
-Source: stillstanding study (Jensenius) -- the "corpus method" for
-integrated quantity of motion from chest-worn accelerometers.
-
-#### Arguments
-
-- `acc` *np.ndarray* - Acceleration of shape (N, 3) in m/s^2 (or raw counts
- with `normalize_gravity=True`).
-- `fs` *float* - Sampling rate (Hz).
-- `highpass` *float, optional* - High-pass cutoff (Hz) used both before and
- after integration. Defaults to 0.3.
-- `order` *int, optional* - Butterworth order of the high-pass filters.
- Defaults to 2.
-- `normalize_gravity` *bool, optional* - If True, rescale the raw input so
- that the median vector magnitude equals 1 g (9.80665 m/s^2) before
- - `filtering` - useful for uncalibrated sensors whose resting output
- should be gravity. Defaults to False.
-
-#### Returns
-
-- `np.ndarray` - Speed series of length N (m/s).
-
-## band_limited_qom
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L86)
-
-```python
-def band_limited_qom(pos, fs, lo=0.3, hi=15.0, order=4, auto_decimate=True):
-```
-
-Band-limited quantity of motion from a position trajectory: the position
-is band-pass filtered (zero phase) to `[lo, hi]` Hz and the QoM is the
-per-frame speed, i.e. the Euclidean norm of the first difference times
-the sampling rate (units of the input per second).
-
-For very low bands relative to the sampling rate (band edge below about
-fs/40), a direct high-order band-pass is numerically fragile; in that
-regime the trajectory is first decimated (zero phase) so the band sits
-comfortably in the new Nyquist range, then filtered with a second-order
-section (SOS) band-pass. This is the "slow sway" regime (e.g. 0.1-0.5 Hz
-postural sway from 100 Hz mocap). Set `auto_decimate=False` to force the
-direct filter.
-
-Source: stillstanding study and Westney-comparisons study (Jensenius) --
-this unifies the band-limited QoM cores used on mocap markers (mm),
-MediaPipe landmarks (px) and slow postural sway across both studies.
-
-#### Arguments
-
-- `pos` *np.ndarray* - Position trajectory of shape (N,) or (N, D) (e.g. D=2
- image coordinates or D=3 mocap coordinates). Non-finite samples are
- linearly interpolated per dimension.
-- `fs` *float* - Sampling rate of the trajectory (Hz).
-- `lo` *float, optional* - Lower band edge (Hz). Defaults to 0.3.
-- `hi` *float, optional* - Upper band edge (Hz), clipped to 0.9 x Nyquist.
- Defaults to 15.0.
-- `order` *int, optional* - Butterworth order of the direct band-pass.
- Defaults to 4.
-- `auto_decimate` *bool, optional* - Enable the decimate+SOS low-band regime.
- Defaults to True.
-
-#### Returns
-
-- `tuple` - `(speed, fs_out)` where `speed` is the per-frame speed series
- (length N-1, or shorter when decimated) and `fs_out` is its
- sampling rate (equal to `fs` unless decimated). `speed` is empty
- (and `fs_out` equals the input `fs`) when the input has fewer
- than `int(fs) + 5` samples, or when it still contains non-finite
- samples after per-dimension interpolation (i.e. a dimension had
- fewer than 3 finite samples to interpolate from). In the
- auto-decimate regime, `speed` is also empty (with `fs_out` the
- decimated rate) when decimation leaves fewer than ~30 samples --
- too few for a stable SOS band-pass.
-
-#### Raises
-
-- `ValueError` - If the band is invalid, i.e. does not satisfy
- `0 < lo < hi <= 0.45*fs` (after `hi` is clipped to 0.9 x Nyquist).
-
-## bin_series
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L60)
-
-```python
-def bin_series(x, fs, bin_s=1.0):
-```
-
-Mean of consecutive, non-overlapping bins of a signal (e.g. a per-second
-quantity-of-motion envelope from a per-frame speed series). Trailing
-samples that do not fill a whole bin are dropped.
-
-Source: stillstanding study (Jensenius); also used in the
-Westney-comparisons study as a per-second envelope.
-
-#### Arguments
-
-- `x` *np.ndarray* - Input 1-D signal.
-- `fs` *float* - Sampling rate of the signal (Hz).
-- `bin_s` *float, optional* - Bin length in seconds. Defaults to 1.0.
-
-#### Returns
-
-- `np.ndarray` - One mean value per bin (empty if the signal is shorter than
- two bins).
-
-## body_scale
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L266)
-
-```python
-def body_scale(landmarks, upper=(11, 12), lower=(23, 24)):
-```
-
-Body-size scale (in the landmarks' own units, e.g. pixels) as the median
-torso length: the distance from the midpoint of the `upper` landmarks
-(shoulders) to the midpoint of the `lower` landmarks (hips). The torso
-length is preferred over shoulder width because it stays robust in a
-profile view, where the shoulder width collapses.
-
-The default indices are MediaPipe Pose landmarks (11/12 shoulders,
-23/24 hips).
-
-Source: Westney-comparisons study (Jensenius).
-
-#### Arguments
-
-- `landmarks` *np.ndarray* - Landmark trajectories of shape (N, L, C) with
- C >= 2; only the first two coordinates are used.
-- `upper` *tuple, optional* - Indices of the two shoulder landmarks.
- Defaults to (11, 12).
-- `lower` *tuple, optional* - Indices of the two hip landmarks.
- Defaults to (23, 24).
-
-#### Returns
-
-- `float` - Median torso length (NaN if no finite frames).
-
-## envelope
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L29)
-
-```python
-def envelope(x, fs, smooth=1.0, normalize=True):
-```
-
-Smooth, optionally z-scored envelope of a signal: Savitzky-Golay
-smoothing (order 2, window `smooth` seconds) followed by
-standardisation. Used to compare motion/audio envelopes across sources
-on a common, amplitude-free scale.
-
-Source: Westney-comparisons study (Jensenius).
-
-#### Arguments
-
-- `x` *np.ndarray* - Input 1-D signal.
-- `fs` *float* - Sampling rate of the signal (Hz).
-- `smooth` *float, optional* - Smoothing window in seconds. None or 0 disables
- smoothing. Defaults to 1.0.
-- `normalize` *bool, optional* - If True, z-score the result. Defaults to True.
-
-#### Returns
-
-- `np.ndarray` - The smoothed (and optionally z-scored) envelope, same length
- as the input.
-
-## grid_qom
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L340)
-
-```python
-def grid_qom(frames, grid=(6, 4), region=(0.0, 1.0, 0.0, 1.0), threshold=8.0):
-```
-
-Spatial grid quantity of motion from a stack of grayscale frames: the
-absolute inter-frame difference is thresholded (small differences set to
-zero to suppress sensor noise) and averaged within each cell of a
-`grid[0]` x `grid[1]` grid laid over `region`, yielding one motion time
-series per cell plus a per-cell mean-motion heatmap.
-
-Source: Westney-comparisons study (Jensenius) -- audience-region motion
-mapping in a concert hall.
-
-#### Arguments
-
-- `frames` *np.ndarray* - Grayscale frames of shape (T, H, W).
-- `grid` *tuple, optional* - Grid size (columns, rows). Defaults to (6, 4).
-- `region` *tuple, optional* - Region of interest as fractions
- (x0, x1, y0, y1) of the frame. Defaults to the full frame.
-- `threshold` *float, optional* - Absolute-difference threshold below which
- pixel changes are zeroed (0-255 scale). Defaults to 8.0.
-
-#### Returns
-
-- `tuple` - `(series, heat)` where `series` has shape (T-1, rows*cols)
- (cells in row-major order) and `heat` has shape (rows, cols) with
- each cell's time-mean motion.
-
-#### Raises
-
-- `ValueError` - If `frames` is not 3-D (T, H, W), as in
- `_motionanalysis.motiongram_data`.
-
-## group_qom
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L203)
-
-```python
-def group_qom(points, fs, lo=0.3, hi=15.0, **kwargs):
-```
-
-Mean band-limited quantity of motion over a group of markers/landmarks,
-plus the group's mean speed envelope: each trajectory is passed through
-[band_limited_qom](#band_limited_qom) and the per-trajectory speeds are averaged.
-
-Source: stillstanding study and Westney-comparisons study (Jensenius) --
-per-body-part QoM (head, shoulders, arms, wrists) from mocap markers and
-pose landmarks.
-
-#### Arguments
-
-- `points` *np.ndarray* - Trajectories of shape (N, M, D): N frames, M
- markers/landmarks, D spatial dimensions.
-- `fs` *float* - Sampling rate (Hz).
-- `lo` *float, optional* - Lower band edge (Hz). Defaults to 0.3.
-- `hi` *float, optional* - Upper band edge (Hz). Defaults to 15.0.
-- `**kwargs` - Passed on to [band_limited_qom](#band_limited_qom).
-
-#### Returns
-
-- `tuple` - `(qom, speed, fs_out)` where `qom` is the mean speed across
- markers and time (NaN if no marker yields a valid series), `speed`
- is the group's mean per-frame speed series, and `fs_out` its
- sampling rate.
-
-## normalized_qom
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L298)
-
-```python
-def normalized_qom(
- landmarks,
- fs,
- scale=None,
- lo=0.3,
- hi=5.0,
- upper=(11, 12),
- lower=(23, 24),
- **kwargs,
-):
-```
-
-Body-scale-normalised quantity of motion (body-lengths per second):
-the pose QoM divided by the performer's own body scale (median torso
-length, see [body_scale](#body_scale)). Being dimensionless, this is invariant to
-camera framing/zoom and comparable across recordings.
-
-Source: Westney-comparisons study (Jensenius) -- framing-invariant
-with/without-audience comparison of a pianist's motion.
-
-#### Arguments
-
-- `landmarks` *np.ndarray* - Landmark trajectories of shape (N, L, 2).
-- `fs` *float* - Sampling rate (Hz).
-- `scale` *float, optional* - Precomputed body scale. Defaults to None (which
- computes `body_scale(landmarks, upper, lower)`).
-- `lo` *float, optional* - Lower band edge (Hz). Defaults to 0.3.
-- `hi` *float, optional* - Upper band edge (Hz). Defaults to 5.0.
-- `upper` *tuple, optional* - Shoulder landmark indices for [body_scale](#body_scale). Defaults to (11, 12).
-- `lower` *tuple, optional* - Hip landmark indices for [body_scale](#body_scale). Defaults to (23, 24).
-- `**kwargs` - Passed on to [band_limited_qom](#band_limited_qom).
-
-#### Returns
-
-- `tuple` - `(qom, speed, fs_out)` as in [group_qom](#group_qom), with both `qom` and
- `speed` divided by the body scale. When `scale` is non-finite
- (e.g. [body_scale](#body_scale) found no finite torso-length sample) or not
- strictly positive (degenerate, coincident upper/lower landmarks),
- division would otherwise silently propagate NaN/inf through
- `qom` and `speed`; instead both are explicitly returned as NaN
- (`qom` as a NaN scalar, `speed` as an all-NaN array of the same
- shape) so the invalid-scale case is unambiguous rather than
- merely inferred from the arithmetic.
-
-## pose_qom
-
-[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_qom.py#L240)
-
-```python
-def pose_qom(landmarks, fs, lo=0.3, hi=5.0, **kwargs):
-```
-
-Band-limited quantity of motion of 2-D pose landmarks (px/s): a thin
-wrapper around [group_qom](#group_qom) with the band used for image-space pose
-trajectories (0.3-5 Hz), where higher bands are dominated by landmark
-jitter rather than motion.
-
-Source: Westney-comparisons study (Jensenius).
-
-#### Arguments
-
-- `landmarks` *np.ndarray* - Landmark trajectories of shape (N, L, 2) in
- pixels (a single landmark of shape (N, 2) is also accepted).
-- `fs` *float* - Sampling rate (Hz, e.g. video frame rate).
-- `lo` *float, optional* - Lower band edge (Hz). Defaults to 0.3.
-- `hi` *float, optional* - Upper band edge (Hz). Defaults to 5.0.
-- `**kwargs` - Passed on to [band_limited_qom](#band_limited_qom).
-
-#### Returns
-
-- `tuple` - `(qom, speed, fs_out)` as in [group_qom](#group_qom).
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
diff --git a/docs/musicalgestures/_remap360.md b/docs/musicalgestures/_remap360.md
new file mode 100644
index 0000000..03f01c4
--- /dev/null
+++ b/docs/musicalgestures/_remap360.md
@@ -0,0 +1,246 @@
+# Remap360
+
+> Auto-generated documentation for [musicalgestures._remap360](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py) module.
+
+Remap-table flattening for legacy 360 formats.
+
+- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Remap360
+ - [flatten_gopro360](#flatten_gopro360)
+ - [flatten_theta360](#flatten_theta360)
+ - [gopro360_dual_fisheye_average](#gopro360_dual_fisheye_average)
+ - [gopro360_to_dual_fisheye](#gopro360_to_dual_fisheye)
+ - [gopro_maps](#gopro_maps)
+ - [probe_gopro360](#probe_gopro360)
+ - [theta_maps](#theta_maps)
+ - [write_remap_pgm](#write_remap_pgm)
+
+GoPro MAX/MAX2 .360 files store the sphere as two strips of a custom
+equi-angular cubemap (EAC) that stock ffmpeg cannot unwrap; legacy Ricoh
+Theta S files store two 90-degree-rotated fisheye circles in one 16:9
+frame. Both become plain equirectangular through the same machinery:
+numpy-generated remap tables (16-bit PGM) driving ffmpeg's `remap` filter,
+with a feathered `maskedmerge` blend across the unstitched seams — the
+same two-pass pattern as `stitch_dual_fisheye` in `_360video`.
+
+The GoPro mapping is a port of Paul Bourke's max2sphere reference
+(paulbourke.net/panorama/gopromax2sphere/). MAX2-resolution files are
+handled by proportional template scaling and are experimental until
+validated against a real recording.
+
+#### Attributes
+
+- `GOPRO_TEMPLATES` - (track_w, track_h) -> (centerwidth, sidewidth, blendwidth); the last 32
+ (resp. 16) columns of each strip are unused padding in the real files: `{(4096, 1344): (1376, 1344, 32), (2272, 736): (768, 736, 16)}`
+
+## flatten_gopro360
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L439)
+
+```python
+def flatten_gopro360(
+ path,
+ target_name=None,
+ width=None,
+ height=None,
+ crf=21,
+ preset='fast',
+ print_cmd=False,
+):
+```
+
+Flatten a GoPro MAX/MAX2 .360 (or chunk-merged .mkv) to equirect.
+
+vstacks the two EAC strips, runs two `remap` passes (left/right seam
+samples) and blends the unstitched zones with `maskedmerge`. The best
+audio stream (most channels — the ambisonic PCM track on a MAX) is
+carried over as AAC. Files that are not exact GoPro templates (e.g.
+MAX2) use proportionally scaled geometry and are experimental.
+
+Geometry is validated against synthetic fixtures and the max2sphere
+reference; strip order/orientation against real camera files is still
+unverified.
+
+## flatten_theta360
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L532)
+
+```python
+def flatten_theta360(
+ path,
+ target_name=None,
+ width=1920,
+ height=960,
+ fov_deg=191.5,
+ roll_deg=(90.0, -90.0),
+ crf=21,
+ preset='fast',
+ print_cmd=False,
+):
+```
+
+Flatten a legacy Ricoh Theta S dual-fisheye MP4 to equirectangular.
+
+Explicit invocation only: a 16:9 MP4 is not identifiable as a Theta
+file by probing. Audio (mono on the Theta S) is passed through as AAC.
+
+The 191.5-degree/±90-degree defaults are validated only against
+synthetic fixtures; a real Theta S recording may need fov_deg/roll_deg
+fine-tuning.
+
+## gopro360_dual_fisheye_average
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L283)
+
+```python
+def gopro360_dual_fisheye_average(
+ path,
+ target_name=None,
+ fov=180.0,
+ size=704,
+ fps=2.0,
+ transparent=True,
+ print_cmd=False,
+):
+```
+
+The time-average of a .360 as one dual-fisheye image, without writing a video first.
+
+For a recording of somebody standing still this is the useful still: whatever held position
+resolves, whatever moved smears, and a single frame cannot show either. Returns the path to a
+PNG, RGBA with the area outside each circle transparent when `transparent` is set.
+
+`fps` decimates before averaging. The mean of a stationary scene converges long before every
+frame is used -- a few hundred samples is plenty -- and decoding 4K equi-angular cubemap frames
+is the whole cost of this operation, so sampling at 2 Hz rather than 30 does the same job for a
+fifteenth of the work. Pass `fps=None` to average every frame.
+
+Frames are accumulated in float64 from a raw pipe rather than written out and re-read. An 8-bit
+running mean over a few hundred frames loses roughly a bit of precision at the point where the
+averaging is meant to be revealing motion smaller than a pixel.
+
+`path` may be several files. GoPro splits a recording into chapters, and averaging each chapter
+separately and combining the means weighted by frame count is arithmetically identical to
+averaging their concatenation -- while skipping the concatenation, which for a full session is
+an 8 GB lossless copy written and read back before any useful work starts.
+
+See [gopro360_to_dual_fisheye](#gopro360_to_dual_fisheye) for what `fov` means and why it has to be recorded.
+
+## gopro360_to_dual_fisheye
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L369)
+
+```python
+def gopro360_to_dual_fisheye(
+ path,
+ target_name=None,
+ fov=180.0,
+ size=704,
+ circular=True,
+ crf=21,
+ preset='fast',
+ print_cmd=False,
+):
+```
+
+Convert a GoPro MAX .360 to side-by-side fisheye circles, front then back.
+
+The output is `2*size` by `size`: two inscribed circles of `size` pixels, the layout GoPro's
+own LRV proxies use and what most dual-fisheye viewers expect.
+
+`fov` is the angular width each circle covers, and it is a parameter to set deliberately rather
+than leave at a default. At 180 degrees a circle holds exactly a hemisphere and the two together
+hold the sphere with nothing to spare. Above 180 each holds more than a hemisphere, the pair
+overlap, and a given real-world direction lands closer to the centre of the circle --- at 195
+degrees by a factor of 180/195, about eight per cent at the rim. Two renders at different `fov`
+have identical pixel dimensions and are not comparable as measurements, so anything measuring
+direction or angular size in the result must record which was used.
+
+Why this is not `v360=input=eac` on the strips. GoPro's `.360` is a custom equi-angular cubemap
+that stock ffmpeg cannot unwrap: pointing `v360` at one 4096x1344 strip, or at the two stacked,
+yields a plausible-looking frame with scrambled corners rather than an error. The sphere is
+recovered here with the same remap tables [flatten_gopro360](#flatten_gopro360) uses, and only then projected.
+
+`circular` masks everything outside the inscribed circle to black, which is the convention for
+dual-fisheye files and what GoPro's own proxies look like. Without it `v360` fills the square
+out to the corners, and those corners hold real image content at an angle wider than `fov` --
+harmless to look at, wrong to measure, and enough to make two otherwise identical renders
+disagree about where the image ends.
+
+Geometry is validated against synthetic fixtures and the max2sphere reference; strip
+order/orientation against real camera files is still unverified, as for [flatten_gopro360](#flatten_gopro360).
+
+## gopro_maps
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L98)
+
+```python
+def gopro_maps(
+ track_w,
+ track_h,
+ centerwidth,
+ sidewidth,
+ blendwidth,
+ out_w,
+ out_h,
+):
+```
+
+Equirect -> vstacked GoPro strips: dual sample maps + blend alpha.
+
+Port of max2sphere's FindFaceUV/GetColour (Paul Bourke). Returns
+(xmapL, ymapL, xmapR, ymapR, alpha): two source-coordinate maps into
+the double-height stacked frame (strip 1 on top) and the weight of the
+R sample (nonzero only in the unstitched seam zones of the four side
+faces).
+
+## probe_gopro360
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L34)
+
+```python
+def probe_gopro360(path):
+```
+
+Stream inventory + strip geometry of a GoPro two-strip container.
+
+Works on original .360 files and on chunk-merged .mkv copies. Returns
+{"video": [{index,width,height} x2], "audio": [{index,codec,channels}],
+"centerwidth", "sidewidth", "blendwidth", "experimental"}. Raises
+ValueError naming what was found when the file does not match the
+two-strip pattern.
+
+## theta_maps
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L479)
+
+```python
+def theta_maps(
+ in_w,
+ in_h,
+ out_w,
+ out_h,
+ fov_deg=191.5,
+ roll_deg=(90.0, -90.0),
+):
+```
+
+Equirect -> Ricoh Theta S rotated dual-fisheye source coordinates.
+
+Legacy Theta S videos hold two fisheye circles side by side, each
+rotated 90 degrees in plane, in a 16:9 frame whose bottom band is
+unused. Front lens = left circle (axis +y), back = right (axis -y);
+equidistant fisheye model. Returns dual maps + seam-blend alpha like
+[gopro_maps](#gopro_maps). fov_deg and roll_deg are tunable against a real file.
+
+## write_remap_pgm
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_remap360.py#L78)
+
+```python
+def write_remap_pgm(xmap, ymap, tmpdir):
+```
+
+Write x/y remap tables as 16-bit binary PGMs for ffmpeg's remap.
+
+Values are integer source-pixel coordinates; 16-bit PGM payloads are
+big-endian per the Netpbm spec.
diff --git a/docs/musicalgestures/_sonification.md b/docs/musicalgestures/_sonification.md
index 6210acd..c5cb461 100644
--- a/docs/musicalgestures/_sonification.md
+++ b/docs/musicalgestures/_sonification.md
@@ -26,7 +26,7 @@ def mg_sonomotiongram(
Creates a *sonomotiongram*: a sonification of the video's motiongram.
The motiongram (a time–space image of where motion happens) is treated as a magnitude
-spectrogram—spatial position maps to frequency, motion intensity to amplitude—and
+spectrogram — spatial position maps to frequency, motion intensity to amplitude — and
converted back to audio with an inverse STFT (Griffin–Lim phase estimation). The result
lets you *hear* the motion. Based on Jensenius, "Some video abstraction techniques for
displaying body movement in analysis and performance" / sonomotiongrams (SMC 2013).
diff --git a/docs/musicalgestures/_soundscape.md b/docs/musicalgestures/_soundscape.md
new file mode 100644
index 0000000..162181f
--- /dev/null
+++ b/docs/musicalgestures/_soundscape.md
@@ -0,0 +1,58 @@
+# Soundscape
+
+> Auto-generated documentation for [musicalgestures._soundscape](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_soundscape.py) module.
+
+Bridge to ambiscape: soundscape features on the MGT time base.
+
+- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Soundscape
+ - [merge_into_summary](#merge_into_summary)
+ - [soundscape_features](#soundscape_features)
+
+MGT owns pixels, ambiscape owns samples; this adapter is the one crossing
+point. It runs (or reuses) ambiscape's cached feature extraction for a
+session folder and returns the 1 Hz series as an MgFeatures container whose
+metadata carries the absolute start time, so motion and audio series join
+on the wall clock. Requires ``pip install "musicalgestures[soundscape]"``.
+
+## merge_into_summary
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_soundscape.py#L52)
+
+```python
+def merge_into_summary(
+ features: MgFeatures,
+ summary_json,
+ prefix: str = 'mot_',
+):
+```
+
+Fold feature medians/IQRs into an analysis summary.json.
+
+The mirror of ambiscape's ``vision --merge`` (which uses ``vis_``):
+each feature contributes ``_median`` and
+``_iqr`` so one summary file describes the whole
+audio-visual session. Existing keys are preserved.
+
+#### See also
+
+- [MgFeatures](_features.md#mgfeatures)
+
+## soundscape_features
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_soundscape.py#L17)
+
+```python
+def soundscape_features(session_folder, features_dir=None) -> MgFeatures:
+```
+
+ambiscape session features as an MgFeatures (1 Hz, wall-clocked).
+
+#### Arguments
+
+- `session_folder` - an ambiscape session folder (WAVs on one clock).
+- `features_dir` - cache directory for ambiscape's .npz features
+ - `(default` - ``/analysis/features``).
+
+#### See also
+
+- [MgFeatures](_features.md#mgfeatures)
diff --git a/docs/musicalgestures/_spacetime.md b/docs/musicalgestures/_spacetime.md
index 74ec28f..13f0d81 100644
--- a/docs/musicalgestures/_spacetime.md
+++ b/docs/musicalgestures/_spacetime.md
@@ -88,7 +88,7 @@ def mg_silhouette_waterfall(
Renders a 3D silhouette waterfall: the per-frame silhouette projected onto one spatial
axis and stacked as cascading curves along a time (depth) axis, so the body's occupancy
-profile "flows" through time, like a 3D spectrogram waterfall.
+profile "flows" through time — like a 3D spectrogram waterfall.
For a single person on a static background, raise ``threshold`` and/or set
``keep_largest=True`` for a cleaner profile.
diff --git a/docs/musicalgestures/_ssm.md b/docs/musicalgestures/_ssm.md
index a156b99..7a87b98 100644
--- a/docs/musicalgestures/_ssm.md
+++ b/docs/musicalgestures/_ssm.md
@@ -40,12 +40,12 @@ SSMs can be computed over different input features such as 'motiongrams', 'spect
- `filtertype` *str, optional* - 'Regular' turns all values below `threshold` to 0. 'Binary' turns all values below `threshold` to 0, above `threshold` to 1. 'Blob' removes individual pixels with erosion method. Defaults to 'Regular'.
- `threshold` *float, optional* - Eliminates pixel values less than given threshold. Ranges from 0 to 1. Defaults to 0.05.
- `blur` *str, optional* - 'Average' to apply a 10px * 10px blurring filter, 'None' otherwise. Defaults to 'None'.
-- `norm` *int, optional* - Normalise the columns of the feature sequence. Possible to compute Manhattan norm (1), Euclidean norm (2), Minimum norm (-np.inf), Maximum norm (np.inf), etc. Defaults to np.inf.
-- `norm_threshold` *float, optional* - Only the columns with norm at least `norm_threshold` are normalised. Defaults to 0.001.
+- `norm` *int, optional* - Normalize the columns of the feature sequence. Possible to compute Manhattan norm (1), Euclidean norm (2), Minimum norm (-np.inf), Maximum norm (np.inf), etc. Defaults to np.inf.
+- `norm_threshold` *float, optional* - Only the columns with norm at least `norm_threshold` are normalized. Defaults to 0.001.
- `combine` *bool, optional* - For 'motiongrams', compute a single SSM from the concatenated
horizontal + vertical motiongram features (both axes of motion in one display) and
return a single MgImage instead of an MgList of two. Defaults to False.
-- `cmap` *str, optional* - A Colormap instance or registered colormap name. The colormap maps the C values to colours. Defaults to 'gray_r'.
+- `cmap` *str, optional* - A Colormap instance or registered colormap name. The colormap maps the C values to colors. Defaults to 'gray_r'.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
- `invert_yaxis` *bool, optional* - Whether to invert the y axis of the SSM. Defaults to True.
diff --git a/docs/musicalgestures/_subtract.md b/docs/musicalgestures/_subtract.md
index e2b69c2..e8a3484 100644
--- a/docs/musicalgestures/_subtract.md
+++ b/docs/musicalgestures/_subtract.md
@@ -38,7 +38,7 @@ Renders background subtraction using ffmpeg.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
- `bg_img` *str, optional* - Path to a background image (.png) that needs to be subtracted from the video. If set to None, it uses an average image of all frames in the video. Defaults to None.
-- `bg_color` *str, optional* - Set the background colour in the video file in hex value. Defaults to '#000000' (black).
+- `bg_color` *str, optional* - Set the background color in the video file in hex value. Defaults to '#000000' (black).
- `target_name` *str, optional* - Target output name for the subtracted video. Defaults to None.
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
diff --git a/docs/musicalgestures/_sync.md b/docs/musicalgestures/_sync.md
new file mode 100644
index 0000000..6788676
--- /dev/null
+++ b/docs/musicalgestures/_sync.md
@@ -0,0 +1,35 @@
+# Sync
+
+> Auto-generated documentation for [musicalgestures._sync](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_sync.py) module.
+
+Align recordings from different devices by their transient envelopes.
+
+- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Sync
+ - [align_recordings](#align_recordings)
+
+One session, many gadgets, every clock slightly wrong: this estimates the
+start-time offset between two recordings of the same scene from the
+cross-correlation of their band-passed onset envelopes. Use the result to
+fill ambiscape's ``calibration.json`` ``clock_offsets_s`` or to trim video
+against a separate audio recorder.
+
+## align_recordings
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_sync.py#L40)
+
+```python
+def align_recordings(
+ file_a,
+ file_b,
+ band=(200.0, 4000.0),
+ env_fs=200,
+ max_lag_s=None,
+):
+```
+
+Offset between two recordings of the same scene.
+
+Returns ``{"lag_s": s, "peak": p}`` where ``lag_s`` is positive when
+*file_b starts after file_a*. ``peak`` is the normalized correlation
+peak; below ~0.3 the alignment is unreliable (little shared audio).
+``max_lag_s`` restricts the search when a rough offset is known.
diff --git a/docs/musicalgestures/_timecode.md b/docs/musicalgestures/_timecode.md
new file mode 100644
index 0000000..27998fe
--- /dev/null
+++ b/docs/musicalgestures/_timecode.md
@@ -0,0 +1,33 @@
+# Timecode
+
+> Auto-generated documentation for [musicalgestures._timecode](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_timecode.py) module.
+
+Absolute-clock helpers: parse recording start times from filenames.
+
+- [Mgt-python](../README.md#mgt-python) / [Modules](../MODULES.md#mgt-python-modules) / [Musicalgestures](index.md#musicalgestures) / Timecode
+ - [filename_datetime](#filename_datetime)
+ - [media_start_datetime](#media_start_datetime)
+
+The regexes are byte-identical to ambiscape's (``ambiscape/io.py``), so a
+folder of phone/recorder/360-camera files resolves to the same wall-clock
+timeline in both toolboxes.
+
+## filename_datetime
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_timecode.py#L17)
+
+```python
+def filename_datetime(path) -> dt.datetime | None:
+```
+
+Parse a ``YYYYMMDD_HHMMSS`` / ``YYMMDD_HHMMSS`` filename stamp.
+
+## media_start_datetime
+
+[[find in source code]](https://github.com/fourMs/MGT-python/blob/master/musicalgestures/_timecode.py#L37)
+
+```python
+def media_start_datetime(path) -> dt.datetime | None:
+```
+
+Start time of a recording: filename stamp, else file mtime.
diff --git a/docs/musicalgestures/_utils.md b/docs/musicalgestures/_utils.md
index 64ff281..087f987 100644
--- a/docs/musicalgestures/_utils.md
+++ b/docs/musicalgestures/_utils.md
@@ -862,7 +862,7 @@ Gets the FPS (frames per second) value of a video using FFprobe.
def get_frame_planecount(frame: np.ndarray) -> int:
```
-Gets the planecount (colour channel count) of a video frame.
+Gets the planecount (color channel count) of a video frame.
#### Arguments
@@ -886,9 +886,9 @@ Returns the number of frames in a video using FFprobe.
- `filename` *str* - Path to the video file to measure.
- `fast` *bool, optional* - If True (default), count demuxed video packets
- (``-count_packets``). This is fast (no decoding) and—unlike the container's
+ (``-count_packets``). This is fast (no decoding) and — unlike the container's
``nb_frames`` metadata, which is unreliable (e.g. off by one on many AVIs, or absent
- on WebM)—matches the true decoded frame count for normal video streams. If False,
+ on WebM) — matches the true decoded frame count for normal video streams. If False,
fully decode and count frames (``-count_frames``): the ground truth, but slower.
Defaults to True.
@@ -1055,7 +1055,7 @@ Renders horizontal and vertical motiongrams using ffmpeg.
- `blur` *str, optional* - 'Average' to apply a 10px * 10px blurring filter, 'None' otherwise. Defaults to 'None'.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
-- `invert` *bool, optional* - If True, inverts colours of the motiongrams. Defaults to False.
+- `invert` *bool, optional* - If True, inverts colors of the motiongrams. Defaults to False.
- `target_name_x` *str, optional* - Target output name for the motiongram on the X axis. Defaults to None (which assumes that the input filename with the suffix "_mgx_ffmpeg" should be used).
- `target_name_y` *str, optional* - Target output name for the motiongram on the Y axis. Defaults to None (which assumes that the input filename with the suffix "_mgy_ffmpeg" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.
@@ -1095,7 +1095,7 @@ Renders a motion video using ffmpeg.
- `blur` *str, optional* - 'Average' to apply a 10px * 10px blurring filter, 'None' otherwise. Defaults to 'None'.
- `use_median` *bool, optional* - If True the algorithm applies a median filter on the thresholded frame-difference stream. Defaults to False.
- `kernel_size` *int, optional* - Size of the median filter (if `use_median=True`) or the erosion filter (if `filtertype='blob'`). Defaults to 5.
-- `invert` *bool, optional* - If True, inverts colours of the motion video. Defaults to False.
+- `invert` *bool, optional* - If True, inverts colors of the motion video. Defaults to False.
- `target_name` *str, optional* - Defaults to None (which assumes that the input filename with the suffix "_motion" should be used).
- `overwrite` *bool, optional* - Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.
@@ -1123,7 +1123,7 @@ agree on the orientation, preventing some processes from coming out rotated.
#### Returns
-- `str` - Path to an upright video—the original if it had no rotation, otherwise a
+- `str` - Path to an upright video — the original if it had no rotation, otherwise a
new "_oriented" copy.
## pass_if_container_is
diff --git a/docs/musicalgestures/_video.md b/docs/musicalgestures/_video.md
index f823efb..8035aee 100644
--- a/docs/musicalgestures/_video.md
+++ b/docs/musicalgestures/_video.md
@@ -190,4 +190,4 @@ Read all video frames into a numpy array using FFmpeg.
def test_input():
```
-Gives feedback to user if initialisation from input went wrong.
+Gives feedback to user if initialization from input went wrong.
diff --git a/docs/musicalgestures/_videoadjust.md b/docs/musicalgestures/_videoadjust.md
index 89818be..b933a0a 100644
--- a/docs/musicalgestures/_videoadjust.md
+++ b/docs/musicalgestures/_videoadjust.md
@@ -78,11 +78,11 @@ object untouched.
Three independent, combinable operations:
-* ``fps``: retime to a target frame rate using FFmpeg's ``fps`` filter, **duration-preserving**
+* ``fps``: retime to a target frame rate using FFmpeg's ``fps`` filter — **duration-preserving**
(frames are dropped/duplicated to hit the rate), e.g. 30 → 25 fps.
* ``speed``: change playback speed by a factor (>1 faster/shorter, <1 slower/longer); the video
is retimed with ``setpts`` and the audio with ``atempo`` so they stay in sync.
-* ``skip``: integer frame decimation, discarding ``skip`` frames for every one kept (this also
+* ``skip``: integer frame decimation — discard ``skip`` frames for every one kept (this also
shortens/speeds up the clip), matching the loader's ``skip`` parameter.
When more than one is given they are applied in order: ``skip`` → ``speed``/``fps``.
diff --git a/docs/musicalgestures/index.md b/docs/musicalgestures/index.md
index ea330f4..b46d12c 100644
--- a/docs/musicalgestures/index.md
+++ b/docs/musicalgestures/index.md
@@ -53,13 +53,17 @@
- [Posture](_posture.md#posture)
- [Pulse](_pulse.md#pulse)
- [Qom](_qom.md#qom)
+ - [Remap360](_remap360.md#remap360)
- [Show](_show.md#show)
- [Show Window](_show_window.md#show-window)
- [Sonification](_sonification.md#sonification)
+ - [Soundscape](_soundscape.md#soundscape)
- [Spacetime](_spacetime.md#spacetime)
- [Ssm](_ssm.md#ssm)
- [Stream](_stream.md#stream)
- [Subtract](_subtract.md#subtract)
+ - [Sync](_sync.md#sync)
+ - [Timecode](_timecode.md#timecode)
- [Utils](_utils.md#utils)
- [Video](_video.md#video)
- [Videoadjust](_videoadjust.md#videoadjust)
diff --git a/docs/releases.md b/docs/releases.md
index 6f42fd5..05d9b36 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1,6 +1,6 @@
# Release Notes
-The current stable release is **MGT-python 1.7.0**.
+The current stable release is **MGT-python 1.7.1**.
Install or upgrade from PyPI:
@@ -16,6 +16,17 @@ which is the single source of truth for release notes.
## Recent highlights
+### 1.7.1
+
+- **The published API pages described a band the package no longer uses.** They showed
+ `group_qom(points, fs, lo=0.3, hi=15.0)` and linked into source lines that stopped existing when
+ those functions moved to `micromotion`. The generated pages are rebuilt from the current source
+ and the two hand-written user-guide pages, which the regeneration script does not touch, were
+ corrected. The band is `micromotion.BAND`, 0.2–5 Hz.
+- **The `micromotion` requirement was `>=0.3`.** No such release exists on PyPI below 0.6, and the
+ functions this package re-exports arrived much later, so the constraint allowed installations in
+ which importing them fails. It is now `>=0.15.2`.
+
### 1.7.0
- **GoPro MAX `.360` support.** `gopro360_to_dual_fisheye()` converts the two-strip equi-angular
diff --git a/docs/user-guide/pose-tracking.md b/docs/user-guide/pose-tracking.md
index dad928a..9d703f2 100644
--- a/docs/user-guide/pose-tracking.md
+++ b/docs/user-guide/pose-tracking.md
@@ -227,7 +227,7 @@ instead.
from musicalgestures import pose_qom, body_scale, normalized_qom
qom, speed, fs_out = pose_qom(traj['landmarks'][..., :2], traj['fps'])
-# qom: scalar mean speed (px/s), band-limited to 0.3-5 Hz (landmark jitter dominates above that)
+# qom: scalar mean speed (px/s), band-limited to 0.2-5 Hz (landmark jitter dominates above that)
# speed: the per-frame envelope, at fs_out Hz
scale = body_scale(traj['landmarks'][..., :2]) # median torso length (shoulders->hips), px
@@ -238,7 +238,9 @@ print(f"{qom_norm:.3f} body-lengths/s") # dimensionless -- comparable across f
```
`pose_qom()` is a thin wrapper around the general `group_qom()` (any group of marker/landmark
-trajectories, mocap included), band-limited to 0.3–5 Hz for image-space pose data.
+trajectories, mocap included), band-limited to 0.2–5 Hz for image-space pose data. That band is
+`micromotion.BAND`, and every quantity of motion in these packages uses it unless you pass your own
+edges.
`body_scale()` computes the median torso length—the distance between the shoulder midpoint
(landmarks 11/12 by default) and the hip midpoint (23/24)—preferred over shoulder width because
it stays robust in profile view. `normalized_qom()` divides the pose QoM by `body_scale()`,
diff --git a/docs/user-guide/sound-movement-toolkit.md b/docs/user-guide/sound-movement-toolkit.md
index da7171e..a0c6b1c 100644
--- a/docs/user-guide/sound-movement-toolkit.md
+++ b/docs/user-guide/sound-movement-toolkit.md
@@ -90,7 +90,7 @@ framing-invariant comparisons, spatial grid QoM, and small envelope/binning help
```python
from musicalgestures import band_limited_qom, accel_to_speed
-speed, fs_out = band_limited_qom(marker_xyz, fs=100.0, lo=0.3, hi=15.0) # px or mm per second
+speed, fs_out = band_limited_qom(marker_xyz, fs=100.0) # px or mm per second, 0.2-5 Hz
```

diff --git a/musicalgestures/_mocap.py b/musicalgestures/_mocap.py
index dcd8b36..b42f761 100644
--- a/musicalgestures/_mocap.py
+++ b/musicalgestures/_mocap.py
@@ -10,7 +10,9 @@
stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
imports MGT.
-Import from ``micromotion`` directly in new code.
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
"""
from micromotion.mocap import ( # noqa: F401
diff --git a/musicalgestures/_posture.py b/musicalgestures/_posture.py
index 88ee353..506a9e0 100644
--- a/musicalgestures/_posture.py
+++ b/musicalgestures/_posture.py
@@ -10,7 +10,9 @@
stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
imports MGT.
-Import from ``micromotion`` directly in new code.
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
"""
from micromotion.balance import ( # noqa: F401
diff --git a/musicalgestures/_qom.py b/musicalgestures/_qom.py
index 3e7f2d1..2989205 100644
--- a/musicalgestures/_qom.py
+++ b/musicalgestures/_qom.py
@@ -10,7 +10,9 @@
stack; MGT already depends on ``ambiscape`` the same way, and neither of those packages
imports MGT.
-Import from ``micromotion`` directly in new code.
+Import from ``micromotion`` directly in new code. Its API reference, including the
+band each function uses and what it returns, is at https://fourms.github.io/micromotion/
+and the functions re-exported here are documented there rather than below.
"""
from micromotion.qom import ( # noqa: F401
diff --git a/pyproject.toml b/pyproject.toml
index 3d5d551..e166f2e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "musicalgestures"
-version = "1.7.0"
+version = "1.7.1"
description = "Musical Gestures Toolbox for Python"
readme = "README.md"
license = {text = "GPL-3.0-or-later"}
@@ -24,7 +24,10 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dependencies = [
- "micromotion>=0.3",
+ # >=0.3 was wrong twice over: no such release exists on PyPI below 0.6, and the functions
+ # this package re-exports arrived much later. 0.15.2 is the floor that makes the
+ # committed API pages true, since they are generated from that package's docstrings.
+ "micromotion>=0.15.2",
"numpy",
"pandas",
"matplotlib",