plastimatch Python distributions
Platform-specific Python wheels containing the official plastimatch command line executables, so that plastimatch can be installed with
pip install plastimatch
and used straight away:
plastimatch synth --output sphere.mha --pattern sphere
plastimatch convert --input sphere.mha --output-dicom dicom/
Every tool is also callable from Python:
from plastimatch import plastimatch
Installing the wheel places a launcher for each executable on PATH via
[project.scripts]. No compiler, no CMake, and no ITK or DCMTK installation is required —
the executables are statically linked.
This repository packages plastimatch; it does not modify it. It follows the recipe established by s5cmd-python-distributions and dcmqi-python-distributions.
Included tools
| Tool | Description |
|---|---|
plastimatch |
The main multi-command driver (convert, register, warp, synth, dice, …) |
dicom_uid |
Generate DICOM UIDs |
Current state
Not yet published to PyPI. The two layers are joined up: plastimatchUrls.cmake pins the
archives from binaries-1.10.0-1, built by this
repository's own build layer, so what remains before a first release is registering a Trusted
Publisher and tagging v1.10.0 — see Publishing.
Supported platforms
| Platform | Wheel tag | Minimum OS |
|---|---|---|
| Windows x86_64 | win_amd64 |
Windows 10+ |
| macOS x86_64 (Intel) | macosx_13_0_x86_64 |
macOS 13 |
| macOS arm64 (Apple Silicon) | macosx_13_0_arm64 |
macOS 13 |
| Linux x86_64 | manylinux_2_28_x86_64 |
glibc 2.28 (RHEL 8, Debian 10, Ubuntu 18.10+) |
The platform tags are derived from the bundled binaries rather than declared by hand (see
scripts/retag_*_wheel.sh), so they always describe what the wheel can actually run on. That
is not cosmetic: an earlier set of archives compiled on ubuntu-24.04 required glibc 2.38 and
GLIBCXX_3.4.32, newer than any manylinux image pypa publishes, so the resulting wheel could
not be installed anywhere — including in cibuildwheel's own test container. Compiling inside
manylinux_2_28 is what fixes that, and scripts/check_binary_floor.sh asserts it after every
Linux build.
How it works
The repository is deliberately split into two layers that meet at a single pinned URL.
build-binaries.yml ──> per-platform archives ──> GitHub release
│
plastimatchUrls.cmake <-- the seam
│
CMakeLists.txt + pyproject.toml
│
wheels ──> PyPI
The build layer (superbuild/, .github/workflows/build-binaries.yml) compiles zlib-ng,
ITK, DCMTK and dlib as static libraries, builds plastimatch against them, and attaches one
self-contained archive per platform to a GitHub release. This exists because upstream
plastimatch publishes source archives only — there is no official binary to download.
superbuild/ is an ExternalProject chain in the style of dcmqi's CMakeExternals/, so one
CMake invocation builds everything in dependency order on all three platforms:
cmake -S superbuild -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
Which plastimatch gets packaged is configurable, and defaults to the upstream GitLab repository — this project does not fork plastimatch:
cmake -S superbuild -B build \
-DPLASTIMATCH_GIT_REPOSITORY=https://gitlab.com/plastimatch/plastimatch.git \
-DPLASTIMATCH_GIT_TAG=1.10.0
Everything the build consumes is pinned by content, not by name. The four dependencies carry
a URL_HASH SHA256, and plastimatch is pinned to a full commit SHA with its tag recorded
alongside. A version number alone is not a pin: GitHub's auto-generated tag tarballs are not
guaranteed byte-stable, and a git tag is a mutable pointer — either could change what gets
compiled into a published binary with no signal. This matches how plastimatchUrls.cmake
pins the archives the wheels are built from, so the chain from upstream source to installed
wheel is checksummed end to end.
Linux builds run inside manylinux_2_28 rather than on the runner. That is not incidental:
compiling on ubuntu-24.04 produces binaries requiring glibc 2.38 and GLIBCXX_3.4.32, which
is newer than any manylinux image provides, so wheels built from them cannot be installed
anywhere — including in cibuildwheel's own test container. scripts/check_binary_floor.sh
asserts both floors after every Linux build.
The wheel layer (CMakeLists.txt, pyproject.toml, src/plastimatch/) downloads one
of those archives, verifies its SHA256, and installs the executables into a Python package.
It does no compiling, so a wheel can be re-cut in seconds when only packaging metadata
changes.
plastimatchUrls.cmake is the seam. It names the archive and checksum for each platform and
nothing else, which means the wheel layer is indifferent to who produced the binaries. That
indifference is not hypothetical — it is what lets the pins currently reference a fork's
archives while the build layer is still being brought up, and it is why pointing this file at
upstream's own binaries and deleting the build layer would be the entire migration if
plastimatch ever publishes them.
Repointing the pins
plastimatchUrls.cmake pins the exact archives a wheel is built from — a filename and a
SHA256 per platform. Regenerate that section from a release with:
python scripts/update_plastimatch_urls.py --repo <owner>/<repo> --tag <tag>
The script rewrites the filenames and checksums and the PLASTIMATCH_BINARIES_REPO and
PLASTIMATCH_BINARIES_TAG variables, because those are the other half of the same pin: the
block names files, those two turn a name into a URL. Updating only the block yields a file that
looks consistent and resolves to nothing.
To cut a new set of archives:
gh release create binaries-<version>-<n> --draft— a draft, so the assets can be checked before the URLs become permanent.- Dispatch
build-binaries.ymlwithrelease_tagset to that tag. Attaching is opt-in and never happens on push: the pins are checksums of specific assets, so replacing them under an existing tag would break every wheel build referencing it. - Publish the release, then run the script above and commit the result.
.github/scripts/verify_pins.py re-downloads every pinned archive and checks its checksum; CI
runs it on each push, which is what would catch a release whose assets were replaced.
Versioning
A release version is the upstream plastimatch version, exactly, with packaging revisions expressed as PEP 440 post-releases:
| Wheel version | Means |
|---|---|
1.10.0 |
plastimatch 1.10.0 |
1.10.0.post1 |
plastimatch 1.10.0 again — same upstream source, packaging fixed |
1.10.0.post2 |
ditto, second packaging fix |
1.11.0 |
plastimatch 1.11.0 |
So pip install plastimatch==1.10.0 gets you plastimatch 1.10.0, which is the only thing a
user of this package is likely to reason about. These sort correctly:
1.10.0 < 1.10.0.post1 < 1.11.0.
Post-releases rather than the two obvious alternatives:
- Not a fourth component (
1.10.0.1). PEP 440 defines.postNas precisely this case — a correction to a release with no change to the software itself — whereas1.10.0.1would read as an upstream plastimatch version that does not exist, sending anyone who saw it looking for a release that was never made. Upstream's own version numbers are three-component; it does generate a fourth fromgit describe(PLM_VERSION_TWEAK), but only for builds between tags, never for a release, so that slot already reads as "commits since tag" to anyone who has built plastimatch from git. - Not a local version (
1.10.0+plm1). PyPI rejects local version identifiers outright, so this is unavailable rather than merely worse.
The cost is that a packaging change is invisible unless you read the .postN, and that a
packaging fix cannot be pre-released. Both are acceptable for a wrapper whose version is a
claim about what is inside it.
Note that the two projects this one is modelled on disagree here:
dcmqi-python-distributions published 0.1.0 … 0.4.1 under its own numbering before
switching to upstream's (1.5.6), while s5cmd-python-distributions still numbers
independently. Mirroring upstream from the start avoids the one-way version jump that switch
required — published versions can only go up.
Tags
There are two independent tag namespaces, because this repository publishes two different kinds of thing:
| Tag | What it is | Published to |
|---|---|---|
v1.10.0 |
a release of this package | PyPI |
binaries-1.10.0-1 |
a set of prebuilt plastimatch archives | GitHub release only |
Binaries releases exist so plastimatchUrls.cmake has immutable URLs to pin, and they are
re-cut whenever the build layer changes without any new upstream version. Keeping them out of
the version namespace matters in two places, both of which are configured rather than
conventional:
setuptools_scmis restricted tov-prefixed tags. At its defaults it matches any tag containing a digit, and itstag_regexhas an optional[\w-]+-prefix group, so it readsbinaries-1.10.0-1as version1.10.0and the next commit builds as1.10.0.post1.post1.- The PyPI upload job in
cd.ymlis gated on the release tag starting withv. It triggers onrelease: published, which a binaries release also is, so without the check every binaries release would publish a wheel built from whatever the pins referenced at the time.
Versions come from those tags via setuptools_scm, so cutting a release is tagging one.
version_scheme = "post-release" is set deliberately: the default, guess-next-dev, reports
an untagged commit after v1.10.0 as 1.10.1.dev2, inventing a next upstream release this
project neither controls nor may ever package. post-release reports 1.10.0.post2 instead.
Untagged builds also carry a local version segment (+g<sha>), which PyPI refuses, so a
development build cannot be uploaded by accident.
Publishing
Two repository-level prerequisites, in this order:
1. The repository must be public before the pins point at it. The wheel layer downloads the
pinned archives over plain HTTPS with no credentials — FetchContent has none to offer, and
neither does pip install on a developer's machine. Release assets on a private repository are
not reachable that way, so repointing the pins at a private repository breaks every wheel
build, not just CI. This is why the pins still reference the fork: that repository is public.
2. Register a Trusted Publisher, so there is no API token to manage. Because the project does not exist on either index yet, these are pending publishers:
| Field | PyPI | TestPyPI |
|---|---|---|
| Register at | https://pypi.org/manage/account/publishing/ | https://test.pypi.org/manage/account/publishing/ |
| Project name | plastimatch |
plastimatch |
| Owner | ImagingDataCommons |
ImagingDataCommons |
| Repository name | plastimatch-python-distributions |
plastimatch-python-distributions |
| Workflow name | cd.yml |
cd.yml |
| Environment name | pypi |
testpypi |
The owner must match wherever the repository actually lives when the workflow runs — an OIDC
claim is checked against it, so registering the wrong owner fails the upload. Register after
any planned move, not before. The environment names must match the environment: keys on the
upload_pypi and upload_testpypi jobs in cd.yml.
Both environments are created automatically on first use, but creating them explicitly with a
required reviewer makes every publish need a human approval — recommended for pypi, given
that uploads cannot be undone.
Cutting a release
Which index a release goes to is decided by the GitHub release itself, so nothing ever lands on both and a rehearsal cannot consume the real version number:
| Release | Tag | Publishes to |
|---|---|---|
| pre-release | v1.10.0rc1 |
TestPyPI |
| full release | v1.10.0 |
PyPI |
So the sequence is: tag v1.10.0rc1 and mark the GitHub release as a pre-release, confirm the
upload and that pip install -i https://test.pypi.org/simple/ plastimatch gives a working
executable, then tag v1.10.0 as a full release. 1.10.0rc1 sorts before 1.10.0, so the
rehearsal is also a legitimate pre-release rather than a number burned for nothing.
Two things worth knowing before the first upload:
- A published version can never be reused, even after deleting the release. Rehearse on TestPyPI, not on the real index.
- Only tagged upstream releases get published here. Packaging an untagged upstream snapshot
has no good PEP 440 spelling —
.postNis already spoken for, and a.devNwould sort before the release it comes after — so snapshots stay as GitHub release assets.
Because two .postN wheels can wrap different builds of the same upstream tag, the exact
upstream commit is recorded in each archive's filename and pinned in plastimatchUrls.cmake,
which is what makes a given wheel traceable to a source revision.
Development
pip install -e . # builds the wheel layer against the pinned archives
pytest tests
The test suite checks that each launcher is installed and runnable, and puts the packaged
plastimatch through a DICOM write/read round-trip. That last test is the important one: a
plastimatch linked against a DCMTK whose data dictionary is loaded from a file at run time
passes its own unit tests in the build tree and then silently fails every DICOM read once
installed elsewhere.
License
The packaging infrastructure in this repository is MIT licensed. plastimatch itself is
distributed under a BSD-style license, and the wheels bundle it statically linked against
ITK, DCMTK, dlib and zlib-ng. See LICENSE for details; the upstream license texts
are installed into plastimatch/share/licenses/ inside each wheel.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file plastimatch-1.10.0.tar.gz.
File metadata
- Download URL: plastimatch-1.10.0.tar.gz
- Upload date:
- Size: 47.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2e2b8cb00d6729ce905470eaa43d14c1f0b01d86fa318c48a5936503cef9d2b
|
|
| MD5 |
038135dd548be5c6298c852159549f1d
|
|
| BLAKE2b-256 |
ef22caff76c97d15d37d9d0a1fd8c1561e94d9ae98f0eee04825e7235b7dbc85
|
Provenance
The following attestation bundles were made for plastimatch-1.10.0.tar.gz:
Publisher:
cd.yml on ImagingDataCommons/plastimatch-python-distributions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plastimatch-1.10.0.tar.gz -
Subject digest:
c2e2b8cb00d6729ce905470eaa43d14c1f0b01d86fa318c48a5936503cef9d2b - Sigstore transparency entry: 2372971349
- Sigstore integration time:
-
Permalink:
ImagingDataCommons/plastimatch-python-distributions@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Branch / Tag:
refs/tags/v1.10.0 - Owner: https://github.com/ImagingDataCommons
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Trigger Event:
release
-
Statement type:
File details
Details for the file plastimatch-1.10.0-py3-none-win_amd64.whl.
File metadata
- Download URL: plastimatch-1.10.0-py3-none-win_amd64.whl
- Upload date:
- Size: 9.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1338c146a6d8e42e3d22925eac740f93e65cd61b8c2d3e0c789186c13874181d
|
|
| MD5 |
359af5cfa24dca18f413848ea353e07d
|
|
| BLAKE2b-256 |
653545f589e559a6035a25e26b7e5e5faf33f40da266b31ba39ce0ef24ae6abe
|
Provenance
The following attestation bundles were made for plastimatch-1.10.0-py3-none-win_amd64.whl:
Publisher:
cd.yml on ImagingDataCommons/plastimatch-python-distributions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plastimatch-1.10.0-py3-none-win_amd64.whl -
Subject digest:
1338c146a6d8e42e3d22925eac740f93e65cd61b8c2d3e0c789186c13874181d - Sigstore transparency entry: 2372971412
- Sigstore integration time:
-
Permalink:
ImagingDataCommons/plastimatch-python-distributions@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Branch / Tag:
refs/tags/v1.10.0 - Owner: https://github.com/ImagingDataCommons
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Trigger Event:
release
-
Statement type:
File details
Details for the file plastimatch-1.10.0-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: plastimatch-1.10.0-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 22.4 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6020264a7a5913da64b05077f880a453f6dc79b7bd4c885a3a908faeefd9afe
|
|
| MD5 |
1d02cd5b27ea34e21e55fd3174407547
|
|
| BLAKE2b-256 |
02b2cc540d6e2a19a043d061ca7515c8eb019b2908a8c7173d1f0f01030e6cd6
|
Provenance
The following attestation bundles were made for plastimatch-1.10.0-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
cd.yml on ImagingDataCommons/plastimatch-python-distributions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plastimatch-1.10.0-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
d6020264a7a5913da64b05077f880a453f6dc79b7bd4c885a3a908faeefd9afe - Sigstore transparency entry: 2372971426
- Sigstore integration time:
-
Permalink:
ImagingDataCommons/plastimatch-python-distributions@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Branch / Tag:
refs/tags/v1.10.0 - Owner: https://github.com/ImagingDataCommons
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Trigger Event:
release
-
Statement type:
File details
Details for the file plastimatch-1.10.0-py3-none-macosx_13_0_x86_64.whl.
File metadata
- Download URL: plastimatch-1.10.0-py3-none-macosx_13_0_x86_64.whl
- Upload date:
- Size: 19.1 MB
- Tags: Python 3, macOS 13.0+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90dda218eb06d081e3872041d583007d06b24a32b0a8cba10e55625052aa4d43
|
|
| MD5 |
bbd715f3bec5ac8f5aa4e4412448a4ca
|
|
| BLAKE2b-256 |
04417e710c2b2985559bdd3a421e59349db1d439cd9ccfe2777296ac298cbd9f
|
Provenance
The following attestation bundles were made for plastimatch-1.10.0-py3-none-macosx_13_0_x86_64.whl:
Publisher:
cd.yml on ImagingDataCommons/plastimatch-python-distributions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plastimatch-1.10.0-py3-none-macosx_13_0_x86_64.whl -
Subject digest:
90dda218eb06d081e3872041d583007d06b24a32b0a8cba10e55625052aa4d43 - Sigstore transparency entry: 2372971362
- Sigstore integration time:
-
Permalink:
ImagingDataCommons/plastimatch-python-distributions@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Branch / Tag:
refs/tags/v1.10.0 - Owner: https://github.com/ImagingDataCommons
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Trigger Event:
release
-
Statement type:
File details
Details for the file plastimatch-1.10.0-py3-none-macosx_13_0_arm64.whl.
File metadata
- Download URL: plastimatch-1.10.0-py3-none-macosx_13_0_arm64.whl
- Upload date:
- Size: 16.2 MB
- Tags: Python 3, macOS 13.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1823986835b44dfbf81e3779e656bda72b22a1e0a925afb61d0d3d5f40695c81
|
|
| MD5 |
f50f8ace14b5a799d41ff3bbee5a8b36
|
|
| BLAKE2b-256 |
1b4add548f0d079a73f6c63210c7ddf2d030eb7772a876af3cf6b6e0aae94402
|
Provenance
The following attestation bundles were made for plastimatch-1.10.0-py3-none-macosx_13_0_arm64.whl:
Publisher:
cd.yml on ImagingDataCommons/plastimatch-python-distributions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plastimatch-1.10.0-py3-none-macosx_13_0_arm64.whl -
Subject digest:
1823986835b44dfbf81e3779e656bda72b22a1e0a925afb61d0d3d5f40695c81 - Sigstore transparency entry: 2372971388
- Sigstore integration time:
-
Permalink:
ImagingDataCommons/plastimatch-python-distributions@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Branch / Tag:
refs/tags/v1.10.0 - Owner: https://github.com/ImagingDataCommons
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@fa27993b733cce17b3bafa97f63c52cf46ffff0e -
Trigger Event:
release
-
Statement type: