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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.