ImprintX SDK (Python)
Python SDK for Imprint X and Imprint TUI. Imprint X support provides event-stream acquisition and device control. Imprint TUI support provides synchronized five-channel tactile image acquisition, raw IMU data, device-state control, and data streaming.
The SDK distinguishes the devices as sensor and tui in module names and
the service's --device option. The Python classes remain Sensor and TUI.
Use imprintx.tui, TUI, --device tui, and example/tui/ consistently.
Reinstall the SDK to use these names; legacy aliases are not available.
Dual-device recording uses the tui field in hardware_sync.json for device
information.
The SDK supports Python 3.8 and newer. The package includes resident Imprint TUI
button recording; model inference, GUI, and example programs are not included.
Python dependencies are NumPy/PyZMQ and the bundled Rust extension. Automatic TUI
recording also requires a system FFmpeg executable with a working libx265 encoder
on PATH; startup checks this before enabling recording. Communication-only mode
and Imprint X do not require FFmpeg.
What's New in 1.0.0
- Imprint X events are stored in a fixed ring with a global event index and
read through read-only
events(start, end)andslots(start, end)snapshots, replacing batch polling. Service batches carrystart/endindices, and gaps show overwritten events. - EVT20 decode errors and timestamp jumps are reported in
sensor.statsand the service STATUS reply. imprintx-sdk --device tuinow defaults to resident button recording inrecordings/; use--no-recordfor communication only or an external recording controller. The shared TUI client and recorder are included in the package.
Installation
pip install imprintx
Imprint X
Shared mode (the service owns the serial port):
imprintx-sdk --device sensor /dev/ttyACM0
Direct mode (the application owns the serial port; do not run the service for the same device at the same time):
from imprintx.sensor import Sensor
with Sensor("/dev/ttyACM0") as sensor:
sensor.set_light(True)
try:
start = sensor.events_written
sensor.start()
end = sensor.wait_for_events(start, timeout_sec=1.0)
if end == start:
raise TimeoutError("No events received")
events = sensor.events(start, end)
print(events["x"], events["y"], events["polarity"], events["timestamp_us"])
finally:
try:
if sensor.is_streaming:
sensor.stop()
finally:
if not sensor.is_streaming:
sensor.set_light(False)
Imprint X events include 320 x 320 coordinates, polarity, and a device timestamp
in microseconds, in serial arrival order. Starting stops previous output, waits
500 ms, clears old input, and enables capture. Valid events arriving before the
startup reply are preserved. One resident reader reads up to 8 KiB at a time;
serial failures raise errors without reconnecting. The event ring holds 200,000
events (SENSOR_EVENT_CAPACITY), and the completed-slot ring holds 1,000 slots
(SENSOR_SLOT_CAPACITY). Both use global indices and overwrite their oldest items.
events(start, end) and slots(start, end) return read-only NumPy snapshots
copied under the reader's lock. Events or TIME_HIGH advancing device milliseconds
publish the previous slot; stopping flushes the final nonempty slot.
Zero TIME_HIGH is valid. Regressing CD timestamps are dropped and counted in
decode_errors without losing word alignment. Resynchronization validates a
candidate sequence and preserves incomplete candidates across serial reads.
A TIME_HIGH decrease adopts raw device time without unwrapping rollover;
regressions in successfully decoded events count as timestamp_jumps and do not
split slots. sync_lost_bytes counts bytes skipped during resynchronization,
protocol_words counts valid auxiliary messages other than TIME_HIGH, and command_reply_bytes
and total_raw_bytes count removed command replies and serial input bytes.
Statistics are available through sensor.stats and the service STATUS reply.
Decode errors are counted without stopping capture; command failures still raise.
events() returns an empty array if its start was overwritten. slots() advances
to retained slots whose events remain readable. Unwritten ends are clipped to the
current count; callers must check returned lengths and global indices for gaps.
The Sensor examples use the same IPC subscriber: stream.py prints every event's
x, y, polarity, and timestamp_us. The GUI follows the reference raw view:
accumulate all events received between display refreshes on a 320 x 320 gray image,
draw ON white and OFF black, and let later events overwrite earlier polarities at
the same pixel. Each display snapshots and clears the image, flips it vertically,
and scales to 640 x 640 with nearest-neighbor interpolation and the same raw header.
The default display limit is 60 FPS; a refresh with no new events shows gray.
The example retains all raw events, without the reference tracking application's
nine calibrated hot-pixel exclusions or coordinate correction.
The window title reports two independent rates: FPS (1 ms) counts completed
device-time milliseconds per elapsed host second, normally around 1000 during
continuous acquisition; Display FPS measures display refreshes. --display-fps
adjusts only the display limit while event reception and time accounting continue.
Without advancing device time, window FPS falls to zero; no windows are fabricated
from host time. Event index gaps and device timestamp regressions raise explicit
GUI errors. The separate Sensor recording example has been removed.
Sensor services and examples use ZeroMQ ipc:// endpoints. Examples launch and
clean up a private service by default. To share a service, explicitly pass matching
custom data/control IPC endpoints to the service and examples. Existing TCP
endpoints must be replaced with local IPC addresses. The event message remains
[events, {session, start, end}, binary_events] with the existing event dtype.
Images and time slots are not published; control replies and errors retain their
own topics.
The Sensor Python flow uses a hierarchical FSM. Common controls and errors belong to the connected parent state. Idle waits for commands and checks serial errors every 100 ms. Active acquisition waits on Rust event notifications for up to 5 ms, without an additional fixed 5 ms message wait. Publication backpressure retains the cursor and retries after a 5 ms control wait. STOP drains retained events before returning to idle; batches include events in the slot still being filled.
The client dispatches subscription, control-reply, and event waits through one
state handler. Only reply waits queue events; a full queue raises an error.
Client.receive(0) performs a nonblocking read. GUI and stream share the
Client.streaming() lifecycle, registering STOP only after START succeeds.
The GUI switches between receiving and presenting at display deadlines, handling
window events at the display cadence. OpenCV uses one thread, as in the reference
application. Raw rendering, 1 ms time accounting, IPC message fields, and explicit
error handling retain their existing behavior.
Send/receive high-water marks are one batch, but do not bound all IPC buffering.
Each batch reads at most half the event ring; skipped events remain visible as
global index gaps. The GUI raises on gaps, and stream reports them on exit.
Finite buffering cannot preserve arbitrarily long subscriber pauses. Slow
subscribers still affect the shared service; printing throughput depends on the
terminal. A receive timeout returns None, and timed streaming ends on schedule.
Imprint TUI
Shared mode (the service owns the serial port and defaults to resident button recording):
imprintx-sdk --device tui /dev/ttyACM0
The first button press starts a segment; the service writes RECORDING only after
the first real complete frame has been written. The second press stops recording,
finalizes and saves the files, then restores CONNECTED and waits for the next
segment. Each segment creates a unique MP4 in recordings/ under the current
working directory, with matching _timestamps.json and _imu.json sidecars;
existing files are never overwritten. Wait for RECORDING before pressing again.
Ctrl+C exits while armed; during recording it saves the complete frames already
received before closing the service. Wait for the next armed message after
Saving recording... before starting another segment.
For communication only, or when another program owns recording state:
imprintx-sdk --device tui --no-record /dev/ttyACM0
The current reference firmware (ecf17ce) has a button-state defect: its first
normal press reports recording end, and its toggle is not synchronized with host
state changes or a forced stop. Firmware must fix this to guarantee the first-press
start sequence above; the SDK preserves the state actually reported.
Image and IMU streams use their own enable commands.
Only one recording controller may own a device's recording state. TUI examples
use a communication-only service and share the packaged imprintx.tui.client
and imprintx.tui.recording implementation. The repository command
example/tui/record.py --button still saves one segment and exits; resident
recording keeps waiting for further segments. Imprint X behavior is unchanged.
Direct Access
Direct mode (the application owns the serial port; do not run the service for the same device at the same time):
from imprintx.tui import TUI
with TUI("/dev/ttyACM0") as tui:
print(f"Firmware: {tui.firmware_version}; device: {tui.sn}")
tui.start()
if not tui.wait_for_batch(timeout_sec=1.0):
raise TimeoutError("No synchronized tactile batch received")
batch = tui.poll()
for frame in batch:
print(
f"channel={frame.channel} "
f"timestamp={frame.timestamp_us}us "
f"shape={frame.image.shape}"
)
TUI IMU samples now follow the 24-byte firmware layout introduced in
tacprint_dev commit b9bb433 (current firmware reference: ecf17ce). Each
little-endian <QI6h sample contains unix_us, sensor_time, and six raw axes.
The direct API, client NumPy views, binary messages, and recording JSON all retain
unix_us. It is UTC Unix microseconds recorded by firmware after reading the IMU;
18446744073709551615 means no valid date anchor and is preserved unchanged.
A valid date anchor alone does not prove synchronization with the host. The low
24 bits of sensor_time still provide the BMI088 clock and recording sample rate.
Image and RTC calendars use fixed UTC+8; RTC synchronization and image timestamp
conversion use that offset regardless of the host timezone. Setting RTC may
change the common wall-clock timeline.
Compatibility: old firmware with 16-byte IMU samples is no longer supported.
Upgrade firmware and update SDK services and clients together. The SDK does not
guess the layout from payload length: 16-byte and 24-byte batches can have the
same length. The connection handshake requires firmware V1.5 or newer.
The old JSON IMU message fallback has been removed; imu messages always carry
binary samples.
Up to three consecutive dropped synchronized batches are tolerated. Receive sequence gaps confirming more than three consecutive drops raise an error; a complete batch resets the count. Channel timestamps must still agree within 5 ms; missing images are not filled or combined across acquisition cycles. Receive counters cannot detect cycles the device never reports at all. The existing two-second timeout without a complete batch remains in effect.
Each image is a 192 x 192 NumPy array. A batch is returned only after the
detected active tactile channels have been synchronized. Opening an Imprint TUI
fails unless firmware version 1.5 or newer and the device identity are both
validated.
Unsupported firmware, invalid version responses and an unwritten SN raise explicit
Chinese errors and abort the TUI connection. Timeouts report that the identity
could not be read, without assuming its contents. Imprint X get_sn() also rejects
an empty SN with a Chinese error. Internal provisioning can still write an SN to
an unprovisioned device.
Examples
pps, fsync = tui.get_io_sync_status() reads DI 0x2F100300 and returns
the tuple (pps, fsync). Each integer is 0 for invalid or 1 for valid.
It works while idle or streaming; the shared-service equivalent is
client.control(Control.GET_IO_SYNC_STATUS), which returns a JSON array
[pps, fsync]. PPS and FSYNC validity are separate from the recording state
at 0x2F100200. Unsupported firmware, malformed responses, and a one-second response
timeout raise explicit errors rather than returning 0.
For usage examples, see the ImprintX SDK repository. TUI stream, GUI, snapshot, inference, and single-recording examples close their subscriptions on exit without stopping a shared service. Examples that launch a private service also close that service when they finish.
Data Integrity
Imprint X commands validate their checksums and acknowledgements; Imprint X event streams validate EVT20 types, coordinates, and timestamps. Imprint TUI streams validate checksums, payload sizes, hardware timestamps, and synchronized channel batches. Missing channels, timestamps, serial numbers, events, and frames are never fabricated. Queue overflow, malformed batches, timestamp regressions, and stream continuity failures beyond the three-batch allowance raise explicit errors. A full TUI publication queue fails immediately instead of blocking service controls; the SDK does not automatically reconnect or silently retry control requests.
License
Copyright (c) ImprintX. All rights reserved. This package is proprietary software; use and distribution are subject to the included license terms.
TUI firmware upgrade
Firmware image validation and USB OTG transfer are implemented in
imprintx.tui.upgrade. This feature additionally requires pyserial>=3.5;
install it separately or use the repository's example requirements.
Stop the SDK service and other programs using the target serial port first.
The device must have a matching preinstalled bootloader and support firmware
transfer. Use an App .bin supplied for your hardware by the maintainer.
python -m imprintx.tui.upgrade /path/to/app.bin --check
python -m imprintx.tui.upgrade /path/to/app.bin --port /dev/ttyACM0
The repository's example/tui/upgrade.py calls this same implementation.
Device identity and firmware build are checked after restart; failures are
reported without automatic retransmission. Real-device upgrades, USB
re-enumeration and power-loss recovery have not yet been validated.
Metadata
Release files for imprintx 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| imprintx-1.0.0-cp38-abi3-manylinux_2_34_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.34+ x86-64 | Details for imprintx-1.0.0-cp38-abi3-manylinux_2_34_x86_64.whl |
Release files / imprintx-1.0.0-cp38-abi3-manylinux_2_34_x86_64.whl
| Download URL | imprintx-1.0.0-cp38-abi3-manylinux_2_34_x86_64.whl |
|---|---|
| Size | 566.1 kB |
| Tags | CPython 3.8 Linux glibc 2.34+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
aa5c9a1c8ddd72e877a71541d7252a17557297b5fd8e555af19c2291d8efb7ff
|
|
BLAKE2b-256 checksum How to use checksums |
5e0f58968f2aebd5672040b60ee7bd5de9da9ee5580298b144d28518661d93ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Security for imprintx 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.