Skip to main content

intomind

The IntoMind instrument API. One library, every IntoMind device.

from intomind.client import display_names, scan
from intomind.session import Session

found = await scan()                   # every device that answered, as heard
labels = display_names(found)          # each one's name, numbered when two share one
chosen = next(d for d in found if labels[d.address] == "Ada's IntoMind One")
s = Session()
await s.connect(chosen)                # only the one picked
device = s.devices[0]

It owns the link to a device, the protocol spoken over it, the recording of what it streams, the provenance of that recording, the formats it can be read out as, and the analyses that read it back. It knows nothing about any particular device beyond what that device tells it, and nothing about any application built on top of it.

What is in here

protocol.py the wire, and nothing else
client.py one device: discovery, control, streaming, the clock
session.py discovery and the set of connected devices
instrument.py instrument state and decoding, independent of any protocol
experiments.py the recorder, and the cued protocols built on it
provenance.py what a capture records about how it was produced
analysis.py the analyses a capture supports
export.py a capture as npz, EDF, BDF, csv, tsv, or MATLAB
model.py the device's encoder, on the host, and head training
adapters.py support for hardware this library was not written for
montage.py where the electrodes are, as data rather than as a sentence
placement.py how a device was placed, and what that implies
timebase.py putting events and samples on one ruler
controller.py game controllers, as a second input stream on that ruler
audio.py spoken cues
headmap.py scalp coordinates, for drawing

Capabilities are the device's answer, never this library's assumption

A device declares what it is over the protocol, and everything follows from that declaration. The channel count, the converter's resolution and reference, the rates it offers, whether it can detect lead-off, whether it carries a model: all of it is asked and none of it is assumed.

device.info.channels            # what the device said
device.can("model")             # a capability bit, not a version number
device.profile()                # controls composed from the above

A table of specific hardware in this library is a defect. Hardware that needs one brings an adapter, which is a separate package, and whatever it offers appears as device.extra on the devices it claims. See adapters.py.

The timeline is never silently repaired

Every sample carries the device's own index and the device time of its own conversion. A forward step in that index is a loss with an exact count. A step that is not forward is a break whose extent is not a number, and this library says so rather than returning a count that would be a fiction.

A recording writes its gaps down. Nothing splices.

The contract is tested, not assumed

contract/conformance.json is emitted by the firmware from the codec the device itself runs. Every message this library decodes, it decodes to the fields that file states, and every malformed one it refuses for the reason that file states. An implementation that drifts from the device fails a test rather than a bench session.

python3 tests/run_all.py

No device, no network, no pytest. That is the command, and there is no other.

The model

The device's encoder turns four seconds of signal into an embedding of 76 numbers. Its weights are not published: they reach a device only as a signed image. The device sends its embeddings instead, so a head is trained on a host and uploaded:

from intomind.model import train_head

await device.start()
windows = await device.collect_embeddings(100)    # about seven minutes
blob = train_head([w.embedding for w in windows], labels, name="focus",
                  encoder_id=windows[0].encoder_id)
await device.upload_head(blob, slot=2, select=True)   # slot 1 ships with the open sex head
await device.set_predictions(True)

A head needs more windows than the embedding has numbers. A head is weights, never code. What it produces on the host is what it produces on the device, because the device evaluates exactly those integers.

Exports

from intomind import export

export.export(label, "bdf")        # any name in export.FORMATS
export.export_npz(label)           # or by name, same six keys back

Six formats, one shape, and every one of them refuses a capture that does not verify. npz and bdf return every sample to the count it was recorded at, measured per file rather than assumed. edf is sixteen bits and says so. mat is MATLAB v7.3, which needs h5py and says which line to type when it is missing.

Lead-off is in every format, and only where the recording says detection was running: a lead-off status of zero is an attached electrode there and nothing at all anywhere else. csv and tsv add a chN_leadoff column per channel, 1 where the electrode was off, 0 where it was attached, and empty where nothing was checking. bdf and edf label each stretch an electrode was off BAD_LEADOFF and the channel, which MNE treats as bad. npz and mat hold the flag itself as leadoff_active. Every summary says in contact whether contact was checked, and a recording made before the flag was kept says "not known".

In bdf and edf, every sample is written at the time it was recorded. Where samples were lost, the file holds one placeholder for each of them, under a BAD_ACQ_SKIP annotation that covers the hole. MNE reads that label as its own: it filters each side of a hole separately, rejects an epoch that overlaps one, and returns the hole as NaN when asked. A reader that does not know the label shows a hole as a flat line at the last value before it. These files are whole records of about a second, and the last record is completed after the last sample with placeholders under the same label, as MNE's own writer completes it, so no recorded sample is cut. tail_filled says how many placeholders that took, and samples_dropped is 0.

The placeholder repeats the last recorded value, as MNE's own writer does. NaN is not possible, because these formats store whole numbers. A value that stands apart from the signal, such as the lowest number the file can store, is a step thousands of microvolts deep, and a filter or a spectrum that does not read the label takes it for signal. The last value adds no step and costs no precision. Zero is possible, but each signal's range would have to stretch to reach it, and most recordings sit thousands of microvolts from zero, so every recorded sample in a 16-bit file would be rounded more coarsely. A build that prefers zero sets edf.GAP_FILL_UV = 0.0 and takes on that cost.

A recording whose sample count starts again within its first second begins with samples from before the recording started. bdf and edf start at the reset, and samples_before_restart says how many samples were left out. A count that steps back later is a break whose length is not known, and they refuse it rather than invent one.

Where captures live

$INTOMIND_CAPTURES, or ~/.local/share/intomind/captures. An application that keeps its own collection says so once:

from intomind import provenance
provenance.use_captures_dir("~/my-study/captures")

Nothing is inferred from where a file happens to sit.

License

Free software under the GNU Affero General Public License, version 3. We also license it commercially, on fair terms shaped by your use case: write to contact@intomind.com. See LICENSING.md, and CONTRIBUTING.md before a first pull request.

IntoMind is a trademark of IntoMind, Inc. The license covers the code, not the name.

Metadata

Release files for intomind 1.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for intomind 1.4.1
File Size Uploaded Provenance Actions
intomind-1.4.1.tar.gz 271.5 kB Without provenance Details for intomind-1.4.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for intomind 1.4.1
File Interpreter ABI Platform Actions
intomind-1.4.1-py3-none-any.whl Python 3 none any Details for intomind-1.4.1-py3-none-any.whl

Total release size: 463.9 kB

Release files / intomind-1.4.1.tar.gz

Download URL intomind-1.4.1.tar.gz
Size 271.5 kB
Tags Source
SHA-256 checksum
How to use checksums
556411055217bac0c3686d00f9abd7400ba453b994ca3bdbb1f90e24954dc82b
BLAKE2b-256 checksum
How to use checksums
e55840bfe933b079407291642947b5b1b9d3e7560c72ae95e424abc735e3a0d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / intomind-1.4.1-py3-none-any.whl

Download URL intomind-1.4.1-py3-none-any.whl
Size 192.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a1603a4ded82fe0a9a15998249fdda6cd2bc75e090676f034554e165c5d4747e
BLAKE2b-256 checksum
How to use checksums
4bad5039175af4b17291b602f3340f10ac1da6380f3bfaa90f0f49b5ba6cad25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Security for intomind 1.4.1

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.

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