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.1.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.1-cp311-abi3-win_amd64.whl (3.8 MB view details)

Uploaded CPython 3.11+Windows x86-64

plateforce-0.1.1-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.1-cp311-abi3-manylinux_2_28_aarch64.whl (4.0 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.28+ ARM64

plateforce-0.1.1-cp311-abi3-macosx_11_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

plateforce-0.1.1-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.1.tar.gz.

File metadata

  • Download URL: plateforce-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 127c2579af245e00e86565aab00e7f061b5cc3a04d641468919f7317f6969121
MD5 86a530c7a8353d7e46be991e191248a2
BLAKE2b-256 d0e8f2c3a31894034809ce6e13acbc4447a3291322b707c2f846b2dd5506b7f3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plateforce-0.1.1-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.1-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 1b6e2a864fea283ebb515759226e06a9e1c71adcf4d12a36515faa1f0bde7f12
MD5 f1dcbc9046ba7fff2eaa6c798b8c7db2
BLAKE2b-256 b6f71721bd843dca8da4f393b91bdf5d77fc2ca359d7b9119f40a45132287dee

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.1-cp311-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f583a79815ebef80fca63722918c42751f5cbdbcbf36178ac62e55308e152cf0
MD5 4e94d9745decf9ac80c7ef39183215bb
BLAKE2b-256 9d83dde7ca1be67037ede81956958941187d121ad296bf382830883ae0b06909

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.1-cp311-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 a50e210ea064f830b37c868af1898ed708de66157f404d6e8a37054852da2b50
MD5 60377dcf7f1749d89d1efc2a7dd57ff6
BLAKE2b-256 564bb01ffd3a1cd254bfc18a9491e32cde63e8ca02d690e0d80e84f0cfd0fdfe

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.1-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1a8df733d2cc58b0f5fe383155de696132a5222ae74bf292c61a93c50c998858
MD5 c61de63cd33a51c28d4d3e6e26a303c5
BLAKE2b-256 e1f8ff1184e052e29bacd6e202c9aaf4fb755474d97ef4adc0f3802dcd1521e4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.1-cp311-abi3-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 c2b37278a1642498582babe65d444c813e3597791c03f87395df1032d824bb09
MD5 ea78e19b33d44a12db2e7d30adca9f32
BLAKE2b-256 c1b4751aab93f154cfd8964fd8d8827f825e89d180389057054a21e2726a9b0b

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.2

6 files

This release

0.1.1 This release

6 files

0.1.0

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