Skip to main content

plateforce

Force-plate analysis where a result carries the method that produced it. You pick a published method from the registry.

pip install plateforce

The machine that installs it needs no compiler and no Rust toolchain. One abi3 wheel per platform covers Python 3.11 and every later version, and the method registry travels inside the wheel, so the digest a result reports names the same bytes on every machine that installed the same version.

plateforce is not forceplate. The similarly named CRAN package, by Hartmann, Koger and Johannsen, analyses posturography: centre-of-pressure measures from quiet standing. This one computes jump kinetics from a vertical ground reaction force trace. Neither is a port of the other.

Analysing one jump

import numpy as np
import plateforce as pf

registry = pf.Registry.load()

force_newtons = np.loadtxt("trial.csv")        # vertical ground reaction force, newtons
trial = pf.Trial(force_newtons, sample_rate_hz=1200.0)

jump = pf.analyse_countermovement_jump(
    trial,
    weighing_epoch=registry.method("bwepoch.fixed_window").bind(duration=1.0),
    onset=registry.method("onset.threshold.noise_relative").bind(k=5.0),
    takeoff=registry.method("takeoff.threshold.absolute_force").bind(threshold_n=20.0),
)

print(jump.jump_height_takeoff_frame_meters.describe())
0.4105176602724294 meters
  jumpheight.takeoff.impulse_momentum {'gravity_meters_per_second_squared': 9.80665}
    integration_anchor = integration.anchor.single_point
    integration_direction = integration.direction.forward
    integration_rule = integration.rule.trapezoid
    integration_start = integration.start.detected_onset
  registry declaring 2026-07-25 (content-e613e95011150591)
    filter.none {}
      passband_edge = none
    bwepoch.fixed_window {'duration': 1, 'start_seconds': 0}
      centre = mean
      dispersion = sample
      window_anchor = trial_start
    onset.threshold.noise_relative {'k': 5}
      degenerate_band = refuse
      reference_distribution = quiet_stance_force
      sd_convention = sample
      onset.op.backward_offset_fixed {'offset_ms': 30}
      onset.op.crossing_selection {}
        selection = first
      onset.op.direction {}
        direction = below_only
      onset.op.persistence {'span_ms': 30}
      onset.op.search_floor_at_weighing_epoch_end {'weighing_epoch_end_seconds': 1}
    takeoff.threshold.absolute_force {'persistence_ms': 0, 'threshold_n': 20}
      takeoff.op.crossing_selection {}
        selection = first
      takeoff.op.residual_comparison {}
        comparison = signed
      takeoff.op.search_floor_at_weighing_epoch_end {'weighing_epoch_end_seconds': 1}
  acquisition block incomplete, so this result cannot be declared to match another lab's

Everything under the number is what a second lab needs to reproduce it. The quiet-standing window appears three times because it moved the answer three times: as system weight, as the noise scale that placed onset, and through the impulse that produced the velocity.

Why the number carries all that

Two independent open-source implementations of the same published methods, run over the same 244 countermovement jumps, agree at r = 0.961 on jump height and r = 0.696 on time to takeoff. Both were tested. Both passed their own tests.

Across those trials, the spread between published methods within a single trial is a median 3.51 cm of jump height. The training intervention that dataset was collected to measure moved jump height by 1.98 cm.

A float cannot tell you which of nine published onset rules produced it, so two labs comparing floats have no way to find out they were never measuring the same thing.

A result is not a float

>>> jump.jump_height_takeoff_frame_meters
Measured(value=0.3419695652891413, unit='meters', method_id='jumpheight.takeoff.impulse_momentum')

>>> jump.jump_height_takeoff_frame_meters + 0.01
TypeError: unsupported operand type(s) for +: 'plateforce.Measured' and 'float'

The bare number is one attribute away, and asking for it is a visible act:

>>> jump.jump_height_takeoff_frame_meters.value
0.3419695652891413

Each result also carries .unit and .provenance. To find the parameter that moved a downstream number, ask the chain rather than walking it:

>>> jump.time_to_takeoff_seconds.provenance.parameters_of("onset.threshold.noise_relative")
{'k': 5.0}

Look before you choose

Some published rules do not disagree, they find the wrong event. Two onset rules in this corpus place the start of the movement more than two seconds before takeoff on roughly one trial in seven, on a movement lasting under a second, and their median behaviour looks ordinary while they do it.

>>> for entry in registry.methods_that_can_fail():
...     print(entry)
MethodEntry('onset.op.backward_offset_fixed', status='accepted', implemented=False, FAILS on 36 of 241 trials (14.9%, silent))

silent means nothing warns you, so the entry shows the failure rate before you can bind it.

An entry also carries .rule, .citations, .biases, .parameters and .gui.surfacing, the registry's own ruling on how hard an interface should push the choice at a user. A bias always states the criterion it was measured against, because a bias figure without one cannot be added to anything safely.

Refusals stay beside partial results

>>> partial = pf.analyse_countermovement_jump(
...     quiet_standing_trial, weighing_epoch, onset, takeoff
... )
>>> partial.system_weight_newtons.value
600.0716666666667
>>> [(type(row).__name__, row.method_id) for row in partial.refusals]
[
    ('NoCrossingError', 'onset.threshold.noise_relative'),
    ('NoCrossingError', 'takeoff.threshold.absolute_force'),
]
>>> partial.onset_time_seconds is None
True

Each refusal carries the same class and fields it would carry if the whole request had to raise, so a notebook can branch on the record instead of parsing a sentence:

>>> error = partial.refusals[0]
>>> error.method_id, error.parameter, error.value
('onset.threshold.noise_relative', 'k', 5.0)

A request that cannot run still raises. The registry describes the literature, which is larger than the methods a selected analysis can bind. Selecting an entry with no rule behind it fails by name rather than resolving to something near it:

MethodNotImplementedError: 'onset.op.backward_offset_fixed' was passed as
the onset method, and 'onset.threshold.noise_relative' is the rule available for that step.
Available: ["bwepoch.fixed_window", "onset.threshold.noise_relative",
"takeoff.threshold.absolute_force"]

Check first with registry.method(id).implemented.

Missing data is reported, never inferred

Vendor exports write 0, -1 or 9999 to mean "no measurement". Reading one as a real value moved a published correlation in this corpus by 0.16. Declare the convention your export uses and the trial reports what matched it:

>>> trial = pf.Trial(force_newtons, sample_rate_hz=1200.0, sentinel=pf.Sentinel.zero())
>>> trial.exclusions
Exclusions(dropped_samples=600, sentinel_convention='zero', reason='600 sample(s) reported and kept in place: removing them would shift the time base')

Samples are counted and reported, never removed: closing a gap in a trace would shift every timestamp after it. For a column of per-trial results, where dropping a row is the right thing, use pf.partition_sentinel_values(values, pf.Sentinel.zero()).

Saying whether two results match

Matching analysis is not enough to make two numbers comparable. A 50 ms contact debounce living in one plate's firmware changes the answer and no reanalysis recovers from not knowing it, so acquisition is part of the record:

trial = pf.Trial(
    force_newtons,
    sample_rate_hz=1200.0,
    acquisition=pf.Acquisition(
        filter_at_capture="none",
        tare_state="tared_before_trial",
        plate_natural_frequency_hz=800.0,
        floor_surface="concrete",
        firmware_version="2.4.1",
    ),
)

Until every member is present, provenance.acquisition_complete stays False and every result says so. pf.Acquisition(...).missing lists what is still needed.

Analysing a folder

A folder states the same rate and acquisition block as one trial. The rate has no default: these files do not carry it, and guessing it changes the answer.

run = pf.batch(
    "trials",
    registry="registry",
    weighing="bwepoch.fixed_window",
    onset="onset.threshold.noise_relative",
    takeoff="takeoff.threshold.absolute_force",
    sentinel=None,
    sample_rate_hz=1200.0,
    acquisition=pf.Acquisition(
        filter_at_capture="none",
        tare_state="tared_before_trial",
        plate_natural_frequency_hz=800.0,
        floor_surface="concrete",
        firmware_version="2.4.1",
    ),
    trial_file_suffixes=[".force.txt"],
    weighing_parameters={"duration": 1.0},
    onset_parameters={"k": 5.0},
    takeoff_parameters={"threshold_n": 20.0},
)

print(run.run.run_fingerprint)
frame = run.to_pandas()
table = run.to_arrow()

The fingerprint stays None until every computed trial carries all 5 of 5 acquisition members. to_pandas() and to_arrow() preserve the rows and their order. They import pandas or pyarrow only when called, so neither table package is required for list, JSON, CSV, or Parquet output.

Arrays

Trial reads any float64 buffer without a Python-level loop: a numpy array, an array.array('d'), a memoryview, and non-contiguous views such as column[::2]. Lists and tuples work too and convert element by element.

A narrower array type is refused rather than widened, because a float32 trace does not carry the precision the impulse identity is checked at:

>>> pf.Trial(force.astype(np.float32), sample_rate_hz=1200.0)
TrialError: force_newtons has dtype float32 and plateforce reads float64. Convert it with
.astype('float64') so the widening is recorded as your choice

numpy is not a dependency of this package.

Two fields worth reading

jump.unregistered_methods lists the steps that ran with no registry entry describing them. They are choices that moved the number, printed rather than left to be discovered.

jump.weighing_epoch_tied_window_count is 1 for a fixed window. Above 1 means a search rule found exact ties and did not identify a single window, so anything downstream treating the selection as determined is reading an artefact of the arithmetic.

Licence

Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plateforce-0.1.0.tar.gz (1.1 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

plateforce-0.1.0-cp311-abi3-win_amd64.whl (3.8 MB view details)

Uploaded CPython 3.11+Windows x86-64

plateforce-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.28+ x86-64

plateforce-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl (4.0 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.28+ ARM64

plateforce-0.1.0-cp311-abi3-macosx_11_0_arm64.whl (3.7 MB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

plateforce-0.1.0-cp311-abi3-macosx_10_13_x86_64.whl (3.8 MB view details)

Uploaded CPython 3.11+macOS 10.13+ x86-64

File details

Details for the file plateforce-0.1.0.tar.gz.

File metadata

  • Download URL: plateforce-0.1.0.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for plateforce-0.1.0.tar.gz
Algorithm Hash digest
SHA256 71a9d9b1b3814da3db2550e9267ed87d33607e0cbc1424a9cb817b2d442cb22f
MD5 e1839b9e4b8ee9f9da90097bb009f098
BLAKE2b-256 69b745c048ebbd7f30378df9a9fe6abfb5621b017eb28a69fb5649bcbc31d9f3

See more details on using hashes here.

File details

Details for the file plateforce-0.1.0-cp311-abi3-win_amd64.whl.

File metadata

  • Download URL: plateforce-0.1.0-cp311-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.8 MB
  • Tags: CPython 3.11+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for plateforce-0.1.0-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a4e58b2330ea5bd002aae565e5d8ab96a95dea9b5650e5ab5d8877523f01f8e6
MD5 32caaff6d0523554f70a1c8f82c2aef9
BLAKE2b-256 911b4188c1852c4b3282662ecb98fa51fde7a216b122deeefc1872c9579a0828

See more details on using hashes here.

File details

Details for the file plateforce-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for plateforce-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d1c95df361cb494ee5bb8e759502be11f7d115d3d30823055c95d54c3e5ff6f2
MD5 6cb7464bbeb6ceefb5cc7e7d41969fa2
BLAKE2b-256 960139b65e34585b4d68e0a99bb781f83fc7d13ef69b2979247cc88dd7bda9dc

See more details on using hashes here.

File details

Details for the file plateforce-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for plateforce-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 ae71e736f5308e8a5ba19489470466178f445b8ab84d5c49e78a513e649b497a
MD5 06b8b073987a174ebd6077b91537664d
BLAKE2b-256 cd2c8cf46b0406b4598840cc703e17a93d9cf67d3229aacfa87f861fcf69db4b

See more details on using hashes here.

File details

Details for the file plateforce-0.1.0-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for plateforce-0.1.0-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 008bfe2dce1e324254365a16b02359ca8b750db102f423808fb53cd3753c9ec7
MD5 98ebb924b75f8755b107bfd526dfe080
BLAKE2b-256 dc55877ee60ef6936c210f72a479e816d39ea8555850cb1e5d8f5dc88b710e41

See more details on using hashes here.

File details

Details for the file plateforce-0.1.0-cp311-abi3-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for plateforce-0.1.0-cp311-abi3-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 f7270273fa5620c70cbefddc076038bd21b8f2e62cc08f52f78ffb2804107785
MD5 7852722490eb32cbe36816dda8574ca8
BLAKE2b-256 54e0a5ca7fbb7874af64b0143f4fb71a33b45f121ad5feabdedb59e699bd5557

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.2

6 files

0.1.1

6 files

This release

0.1.0 This release

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page