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

Uploaded CPython 3.11+Windows x86-64

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

Uploaded CPython 3.11+manylinux: glibc 2.28+ ARM64

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

Uploaded CPython 3.11+macOS 11.0+ ARM64

plateforce-0.1.2-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.2.tar.gz.

File metadata

  • Download URL: plateforce-0.1.2.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.2.tar.gz
Algorithm Hash digest
SHA256 9cf42c0837106dc8cd26ee600b22ef26a3ae8992fd9bed97778fa879f6aa9456
MD5 89e63ce36d16f68f011624400ef9451b
BLAKE2b-256 37cdfd51b814f4e0b703044084cf5f171f667976e60b4867cd30d401c19c579a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plateforce-0.1.2-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.2-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 eb9f5ab89bb644854db90fc9fee57f181a78148d116fbabc10cd1d1ad8203c6e
MD5 27af41c207feda2689888f9b833c9606
BLAKE2b-256 1e4785085de14113257c87d5edf8a44e0700dfa7411a00f23d442d09aa986c08

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.2-cp311-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f3f95f8ac4224e5d0c779c17d40024123ae4ae2fa885eddf3260202dcc8619cb
MD5 2384116beb6dddca9aad47e80835de76
BLAKE2b-256 3131c291388e3a3fe5501b2cd91b919b2d6d22227c15da1f2f75f6ec99e7b9af

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.2-cp311-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 d19e2dce2a5d07995e33aee8939e5745842d665e218517890c13ea8d82978879
MD5 de9ea3e11617f99293e68726e2a6d558
BLAKE2b-256 4ba15b7d31c8250defbcb66cd9219260bd26ffbb3d45cfd73a7d8231a3a00c2f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.2-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b09938e89c5a85b8cddb693024229bd0a3266df23c3bcaed9327bbbbddf4ecdb
MD5 2721caebd0e363cd50104c2eabec9c31
BLAKE2b-256 21ee60448d471e209e3f3e5ffb7b7ebe5b7fcea1e664cb9320439a735b2ede06

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for plateforce-0.1.2-cp311-abi3-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 0d8fb1ac412b0e1fcbbcbb41d3703bacb7f1d83791238d67f61f4c697fdcab46
MD5 16966bb66ba635e5ec25cc960d0ef8d3
BLAKE2b-256 858e6fc49eb94d6c67d34391f3da2f69bb72f93cfcd58776ab9199d1a7c2e1cd

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

6 files

0.1.1

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