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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

This is a breaking release. It corrects the surface-EMG pipeline against Farina, Mesin, Martina & Merletti (2004). The problems were found through feedback from @joaohbbittar; thank you. Both the default route and `execution=` are affected.

It also ports three fixes that never reached `main` (Gfluctdv noise, fibre conduction velocities, large fibre layouts), corrects the units of the intramuscular SFAP and adds the multilevel descending-drive optimiser used to calibrate an FDI pool.
It also ports three fixes that never reached `main` (Gfluctdv noise, fibre conduction velocities, large fibre layouts), corrects the units of the intramuscular SFAP and adds the multilevel descending-drive optimiser used to calibrate an FDI pool and an opt-in fibre model typed by motor unit.

### Breaking changes

Expand All @@ -22,6 +22,7 @@ This release changes default outputs; regenerate anything calibrated against ear
- Pools that call `insert_Gfluctdv()` now produce noisy discharge: spike trains change, and descending-drive parameters fitted while the noise was silent should be refitted.
- iEMG outputs change through the fibre conduction velocities (mean ~2.0 → ~3.45 m/s at the default diameter). Muscles with more than ~313 000 fibres get a different, now circular, fibre layout.
- iEMG amplitudes, shapes and spectra change through the SFAP unit fixes (typically 20–50× larger at a fine wire). Anything tuned against earlier iEMG amplitudes, including `add_noise` floors given in absolute units, must be redone. Values are in mV.
- Default sEMG amplitudes rise 34 % through the 57.9 µm fibre diameter; pass `fiber_diameter__um=50` to `SurfaceEMG` to keep the earlier source.
- iEMG outputs change through the narrower end-plate spreads. Pass `IntramuscularEMG(..., nmj_coefficients="reference_code")` to reproduce earlier end-plates.

### Fixed
Expand All @@ -48,19 +49,26 @@ This release changes default outputs; regenerate anything calibrated against ear
- The sum over the fibre omitted the spatial step, so amplitudes scaled with `1/spatial_resolution__mm`.
Together the bugs nearly cancelled at the default 0.01 mm step for a nearby electrode. On an FDI cylinder (bipolar fine wire, 2 mm IED, 15 units) MUAPs go from 0.03–1.8 mV to 0.9–37 mV peak-to-peak. These are point electrodes in an infinite homogeneous medium, which overestimates the near field (no electrode area, no near-field correction); see Breaking changes.
- **Large circular fibre layouts.** Fibre centres came from a 399 499-point table covering a square, of which only ~313 000 points lie inside the circle. Beyond that the outer ring was filled only at the diagonals, and beyond 399 499 the fibre count was silently capped. Layouts the table covers are unchanged (FDI defaults ≈ 54 000 fibres); larger counts now use a scrambled Halton sequence in the disc, seeded from MyoGen's generator, with a warning.
- **Innervation centres on other platforms.** The fast-marching placement has exact ties (two cells at the same distance, first at the second centre). `np.argmax` took whichever cell a platform's floating point put a last bit higher, so on another machine the centres could come out mirrored and most fibres went to other units. Ties now go to the lowest cell index, which is what `argmax` returned on exact ties, so layouts are unchanged where they were already reproducible and now agree across platforms.

### Added

- `SurfaceEMG(fiber_diameter__um=50, intracellular_conductivity__S_m=1.01)` set the source strength; amplitude scales with the fibre cross-section.
- `SurfaceEMG(fiber_diameter__um=57.9, intracellular_conductivity__S_m=1.01)` set the source strength; amplitude scales with the fibre cross-section.
- `SurfaceEMG(iap_lambda__per_mm=1.0)` sets the length scale of the Rosenfalck IAP, `Vm = 96 (λz)³ e^{−λz} − 90` mV. The default, 1 / mm, is the published value and leaves outputs unchanged. Values below 1 stretch the IAP, lower the MUAP's spectral content and raise surface amplitude. On the FDI defaults, λ = 0.7 matched the median frequency (~175 vs ~165 Hz measured) and adjacent-pin RMS of the Delsys recordings of Botelho et al. (2019) at 25 % MVC; λ = 1 gives ~200 Hz. Botelho et al. used 0.5 in their own FE model for the same reason. Any value other than 1 is a fitted modelling assumption, from one subject. Supported on the default and `execution=` routes; `use_unified=True` refuses λ ≠ 1.
- **Fibre dispersion in surface EMG.** `SurfaceEMG(fiber_dispersion=SurfaceFiberDispersion(...))` adds, each switched on independently:
- per-fibre end-plates from the two-layer nerve model of Akhmadeev et al. (2020), shared with intramuscular EMG;
- axon-branch conduction delays;
- motor-unit conduction velocity linear in recruitment threshold (Del Vecchio et al. 2017), and a spread of fibre velocities within each unit, each fibre propagating at its own velocity;
- a source scaled by fibre diameter (from the Nandedkar & Stålberg 1983 velocity–diameter line);
- motor-unit conduction velocity linear in recruitment threshold (Del Vecchio et al. 2018) [delvecchio2018 p. 2], or set per unit with `fiber_cv_per_unit__m_s` (e.g. mapped from each unit's recruitment force), and a spread of fibre velocities within each unit, each fibre propagating at its own velocity;
- a source scaled by fibre diameter squared (diameter from the Nandedkar & Stålberg 1983 velocity–diameter line), normalised to average 1 over the muscle so that `fiber_diameter__um` stays the population RMS diameter; `fiber_dispersion_report["diameter_mean__um"]` gives each unit's effective diameter and `line_diameter_mean__um` the raw line value;
- tendon-end stagger: each fibre end in a band, independently or following a smooth random field across the cross-section so that neighbouring fibres end close together.

The realised spreads are reported per motor unit in `SurfaceEMG.fiber_dispersion_report`. The default is off and leaves outputs bit-identical. `SurfaceFiberDispersion.literature_candidate()` gives a preset whose values come from the literature or are marked as assumptions. The `execution=` route and `use_unified=True` refuse an active configuration.
- **Surface EMG can use the `Muscle` fibres.** `SurfaceFiberDispersion(fiber_properties="muscle")` takes every fibre's conduction velocity and diameter from `Muscle` (the fibres intramuscular EMG uses; typed by motor unit with `fiber_model="typed"`) instead of building surface-only CVs; with `diameter_scales_source=True` each source scales with its own absolute cross-section, so `fiber_diameter__um` has no effect. Cannot be combined with the surface CV options (threshold slope, inter-unit spread, per-unit CVs, within-unit spread). Preset `SurfaceFiberDispersion.muscle_candidate()`. Opt-in; defaults unchanged. The dispersion window check now lengthens the IAP tail with `1 / iap_lambda__per_mm`. On the Botelho et al. (2019) FDI benchmark this mode, with the typed model and per-unit CVs from recruitment force, matches the median frequency at λ ≈ 0.63 (documented under `iap_lambda__per_mm`; default stays 1).
- **Fibre types by motor unit (`Muscle(fiber_model="typed")`).** Opt-in; the default, `fiber_model="independent"`, keeps the fibres of the fix above (one log-normal diameter per fibre, the CV line plus 0.2 m/s), and the typed-model arguments below raise without `fiber_model="typed"`. With the typed model `_generate_fiber_properties()` needs `assign_mfs2mns()` first (`autorun=True` already orders them).
- Every unit has one fibre type (`Muscle.motor_unit_fiber_types`, `muscle_fiber_types`): units in recruitment order are type I until they hold 57.4 % of fibres (FDI, Polgar et al. 1973; strict order is a simplification of Garnett et al. 1979). In the default FDI muscle 102 of 120 units are type I, because fibres concentrate in the large late units.
- Unit mean diameters rise with threshold along one ladder across the pool (`myogen.simulator.core.muscle.fiber_types`), with type means 50.0 / 67.0 µm (`type_mean_fiber_diameters__um`, Polgar et al. 1973). One ladder rather than one per type, because per-type ladders made conduction velocity drop at the type boundary against the linear CV–threshold relation (Del Vecchio et al. 2018).
- The spread of diameters inside a unit is set per type, `within_unit_fiber_diameter_log_sd=(0.08, 0.13)` (type I, type II; cat TA fibre-area CoV within units, Martin et al. 1988). Fibre CV follows diameter on one Nandedkar & Stålberg line; `mean_fiber_conduction_velocity__m_per_s` / `sd_fiber_conduction_velocity__m_per_s` (4.32 ± 0.71 m/s, the HD-sEMG CV of all tibialis anterior units up to 70 % MVC, Del Vecchio et al. 2018) hold over unit means, each unit once, as measured. `within_unit_fiber_diameter_log_sd=None` drops the per-type spread and rescales the line plus 0.2 m/s scatter over fibres instead.
- `motor_unit_conduction_velocities__m_per_s` sets each unit's mean CV, e.g. from recruitment force through `myogen.utils.ramp_analysis.cv_from_recruitment_force`; within a unit CV keeps the line's slope. In the default FDI muscle the 4.32 ± 0.71 m/s fallback puts ~15 % of fibres at the 6 m/s bound (large units stretched by an SD over mostly small units) and warns when more than 5 % are clipped; per-unit CVs from recruitment force put 1 % there. On the Botelho FDI benchmark with per-unit CVs, typed fibres match the median frequency at λ ≈ 0.63 with RMS inside Botelho's range.
- `IntramuscularEMG(nmj_coefficients=...)` selects the end-plate spreads (see Changed).
- `ForceModel(force_frequency_shape=(a, c))`: shape of the force-frequency saturation S(r) = 1 − exp(−a r^c) in the Fuglevand gain. The default (2, 3) is Fuglevand et al. 1993 and leaves outputs unchanged. `force_utils.HUMAN_UNIT_FORCE_FREQUENCY` (0.93, 1.29) is fitted to human single motor units stimulated through their axons (Macefield et al. 1996, Fuglevand et al. 1999, McNulty et al. 2000): units keep gaining force up to ~30–50 Hz, where Fuglevand's curve saturates by ~20 Hz for slow units. It also gives the measured twitch:tetanus ratio (0.23; Fuglevand 0.11).
- **Multilevel descending-drive optimiser.** `examples/02_finetune/06_optimize_dd_multilevel.py` fits the descending-drive network (and the Gfluctdv noise scale) to rate-coding targets over a trapezoidal ramp, sequentially or with parallel Optuna workers. Supporting modules: `myogen.utils.fr_optimization_targets` (Fuglevand, De Luca and experimental targets), `myogen.utils.ramp_analysis` and `myogen.utils.parallel_budget` (worker count from free RAM and CPUs).
Expand All @@ -79,6 +87,7 @@ This release changes default outputs; regenerate anything calibrated against ear

- **End-plate spreads in intramuscular EMG.** The two-layer end-plate model now uses the spreads printed in Akhmadeev et al. (2020, eq. 5-6) instead of the 1.5–2× wider values of their reference code: branch-point SD 1.0 + 2.5·s mm (was 1.5 + 4.0·s) and arborisation SD 0.25 + 1.0·s mm (was 0.5 + 1.5·s), with s the share of total threshold below the unit. In the sEMG limb sweep the realised within-unit end-plate SD fell from a median of 1.68 mm to 1.07 mm. iEMG MUAPs and signals change.
- Citation corrected: the two-layer end-plate and recruitment-threshold model cited as "Konstantin et al. 2020" is Akhmadeev, Yu, Le Carpentier, Aoustin & Farina (2020); Konstantin is the first author's given name. The `konstantin` threshold mode keeps its name.
- **Default surface fibre diameter (FDI).** `SurfaceEMG(fiber_diameter__um)` 50 → 57.9 µm, the root-mean-square FDI diameter from Polgar et al. (1973): type I 50.0 µm, type II 67.0 µm, 57.4 % type I. The RMS is the right single value because the source scales with d². Default surface amplitudes rise by (57.9/50)² = 1.34; see Breaking changes. `Muscle` fibres are unchanged unless `fiber_model="typed"`.
- `Muscle.var_fiber_length__mm` is documented as the half-width of a uniform fibre-length variation (the FDI value of Jacobson et al. 1992), not a variance. Behaviour is unchanged.

## [0.13.0] - 2026-10-28
Expand Down
7 changes: 5 additions & 2 deletions benchmarks/semg_dispersion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ Arms run before the published end-plate coefficients became the default pin
is a harness key for the skin layer.

Arms with `_spike_file` use NEURON spike trains of the calibrated FDI pool
(spike times per unit and the pool thresholds), stored under `spikes/`; the
path is relative to this directory.
(spike times per unit and the pool thresholds), stored under `spikes/`.
`unit_cv/` holds per-unit mean fibre CVs from recruitment force
(`myogen.utils.ramp_analysis.cv_from_recruitment_force`) for
`_fiber_cv_per_unit_file` and `_muscle_unit_cv_file`. Input paths in
`arms.json` are relative to this directory.

## Running

Expand Down
Loading
Loading