Skip to main content

Python client for Scentience olfaction instruments over BLE, with a hosted OVL inference client

Project description

scentience

Python client for Scentience olfaction instruments over BLE Bluetooth.

Installation

BLE only:

pip install scentience

BLE + COLIP embedding models:

pip install "scentience[models]"

Requirements

  • Python 3.8+
  • Bluetooth 4.0+ adapter
  • A Scentience developer API key (obtain from the Scentience portal)
  • For embedding models: torch, torchvision, transformers, huggingface-hub, Pillow (installed via [models] extra)

Connecting a single device

Auto-discover

Scans for the first Scentience device in range and connects to it.

import scentience as scn

device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(char_uuid="YOUR_CHAR_UUID")

Target a specific device

Pass the device UID (serial number or BLE name) to connect to a particular instrument.

device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")

Take a single reading

data = device.sample_ble()   # returns dict
print(data)

Stream continuously

def on_sample(data: dict) -> None:
    print(data["UID"], data.get("CO2"), data.get("ENV_temperatureC"))

device.stream_ble(callback=on_sample)

# ... your application runs here ...

device.stop_stream()
device.disconnect()

Context manager

disconnect() is called automatically on exit.

with scn.ScentienceDevice(api_key="YOUR_API_KEY") as device:
    device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")
    data = device.sample_ble()
    print(data)

Connecting multiple devices

Pass a list of device UIDs to connect_ble. All devices are scanned for in a single BLE scan and connected concurrently. The API key and characteristic UUID are the same for every device.

Snapshot from all devices

import scentience as scn

device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(
    char_uuid="YOUR_CHAR_UUID",
    device_uids=["A00022", "A00010"],   # as many UIDs as needed
)

readings = device.sample_ble()   # returns List[dict], one entry per device
for r in readings:
    print(r)

Stream from all devices simultaneously

The same callback receives packets from every device. Use the UID field to identify the source.

def on_sample(data: dict) -> None:
    print(f"[{data['UID']}]  CO2={data.get('CO2')}  temp={data.get('ENV_temperatureC')}°C")

device.stream_ble(callback=on_sample)

# ... your application runs here ...

device.stop_stream()
device.disconnect()

Context manager (multiple devices)

with scn.ScentienceDevice(api_key="YOUR_API_KEY") as device:
    device.connect_ble(
        char_uuid="YOUR_CHAR_UUID",
        device_uids=["A00022", "A00010"],
    )
    readings = device.sample_ble()

Discovering devices

Before connecting, use scan_devices() to see exactly what BLE devices are visible and confirm the names your instruments advertise.

nearby = scn.ScentienceDevice.scan_devices(timeout=10.0)
for d in nearby:
    print(d["name"], d["address"], d["rssi"])

Pass any value from the name column as device_uid / device_uids.


Logging and export

All readings from sample_ble() and stream_ble() are automatically buffered in memory across all connected devices.

print(device.log)             # list of dicts

device.export_json("readings.json")
device.export_csv("readings.csv")

device.clear_log()

CSV column order: UID and TIMESTAMP first, then all remaining fields alphabetically. Missing fields are written as empty values.


Response payload

Each reading dict may contain:

Key Description
UID Device serial number
TIMESTAMP Reading timestamp
ENV_temperatureC Ambient temperature (°C)
ENV_humidity Relative humidity (%)
ENV_pressureHpa Barometric pressure (hPa)
BATT_health Battery health
BATT_v Battery voltage
BATT_charge Battery charge (%)
BATT_time Estimated battery time remaining
STATUS_opuA Operational status
CO2, NH3, NO, NO2, CO, C2H5OH Chemical compounds (non-zero only)
H2, CH4, C3H8, C4H10, H2S, HCHO, SO2, VOC Chemical compounds (non-zero only)

COLIP embedding models

ColipModel integrates the Olfaction-Vision-Language Embeddings to produce joint embeddings across olfaction, vision, and language modalities.

Four variants are available:

Variant Embed dim Architecture Best for
colip-small-base 512 base GNN Fast inference / edge devices
colip-small-gat 512 GAT Higher accuracy on edge devices
colip-large-base 2048 base GNN Accuracy-critical tasks
colip-large-gat 2048 GAT Highest accuracy, slower inference

Weights are downloaded from HuggingFace Hub and cached locally on first use.

Example — small base model

import scentience as scn
from PIL import Image

model = scn.ColipModel.from_pretrained("colip-small-base")
# ColipModel(variant='colip-small-base', embed_dim=512, device='cpu')

image   = Image.open("scene.jpg")
olf_vec = [0.0] * 138          # 138-dimensional olfactory descriptor

embedding = model.embed(image, olf_vec)       # torch.Tensor, shape (512,)
arr       = model.embed_numpy(image, olf_vec) # numpy array

Example — large GAT model

model = scn.ColipModel.from_pretrained("colip-large-gat")
embedding = model.embed(image, olf_vec)       # torch.Tensor, shape (2048,)

Example — BLE streaming + real-time embedding

import scentience as scn
from PIL import Image

model  = scn.ColipModel.from_pretrained("colip-small-base")
device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")

scene = Image.open("scene.jpg")

def on_sample(data: dict) -> None:
    olf_vec   = [data.get(k, 0.0) for k in sorted(data) if k not in ("UID", "TIMESTAMP")]
    embedding = model.embed(scene, olf_vec)
    print(f"embedding shape: {embedding.shape}, norm: {embedding.norm():.4f}")

device.stream_ble(callback=on_sample)

Targeting a specific compute backend

model = scn.ColipModel.from_pretrained("colip-large-base", device="cuda")  # GPU
model = scn.ColipModel(variant="colip-small-gat", device="mps")             # Apple Silicon

Cloud inference — OVL API

OVLClient calls the hosted Olfaction-Vision-Language model with an API key, so you don't download weights or install torch/transformers — it uses only the Python standard library. It maps a molecule, a gas-sensor reading, or text/image into one shared embedding space, and can localize which object in an image is emitting a detected aroma (with an optional heatmap overlay).

import scentience as scn

ovl = scn.OVLClient(api_key="YOUR_API_KEY")   # get a key from the Scentience portal

# 1) molecule -> embedding + predicted odor descriptors
r = ovl.embed_molecule("CC(=C)C1CCC(C)=CC1")  # limonene
print([d["descriptor"] for d in r["descriptors"]])
# -> ['citrus', 'terpenic', 'herbal', 'woody']

# 2) "which object in this image emits this smell?" + save the heatmap overlay
with open("scene.jpg", "rb") as f:
    image = f.read()
g = ovl.ground(image, smiles="CC(=C)C1CCC(C)=CC1", return_heatmap=True)
print(g["regions"][0])                         # highest-scoring image region
scn.OVLClient.save_heatmap(g, "aroma_heatmap.png")
method what it does
embed_molecule(smiles, top_k=8) molecule → 1152-d embedding + odor descriptors
embed_sensor(readings, top_k=8) e-nose window [T, 6] → embedding + substance + descriptors
descriptors(smiles=… | readings=…) just the top odor descriptors
embed_text(text) / embed_image(bytes) text / image → shared-space embedding
ground(image, smiles=… | readings=…, return_heatmap=False) aroma-source regions (+ optional heatmap PNG)
info() / health() endpoint metadata / liveness

Example — live device reading → cloud inference

import scentience as scn

ovl    = scn.OVLClient(api_key="YOUR_API_KEY")
device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")

# collect a short window of sensor samples, then embed it
window = [scn.OVLClient.device_reading_to_ovl(device.sample_ble()) for _ in range(64)]
result = ovl.embed_sensor(window)
print(result["substance"], [d["descriptor"] for d in result["descriptors"]])

Note on the sensor path. The OVL sensor encoder was trained on the SmellNet MOX array, whose channels don't line up 1:1 with the Scentience device. device_reading_to_ovl provides a best-effort mapping, so device-sourced sensor inference is experimental until the encoder is recalibrated to your hardware. The molecule and vision/grounding endpoints are unaffected and production-ready.

A private deployment can be targeted with OVLClient(api_key=..., base_url="https://your-host"). A runnable end-to-end script is in example_ovl_cloud.py.


API reference

See the full BLE API documentation.

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

scentience-2.2.0.tar.gz (29.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

scentience-2.2.0-py3-none-any.whl (24.0 kB view details)

Uploaded Python 3

File details

Details for the file scentience-2.2.0.tar.gz.

File metadata

  • Download URL: scentience-2.2.0.tar.gz
  • Upload date:
  • Size: 29.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.0

File hashes

Hashes for scentience-2.2.0.tar.gz
Algorithm Hash digest
SHA256 82f96d2a07308d7cbc89b96b64fb35bd5c017ff1308e45122527905e092022a3
MD5 2337da9737398d5d14b871ec9ebbafcc
BLAKE2b-256 e591cfbcef6cf739fea6cb85585251862db990a0fb53c5ff8f69ebd0270d10c9

See more details on using hashes here.

File details

Details for the file scentience-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: scentience-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 24.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.0

File hashes

Hashes for scentience-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 26714e6a9b52ab11740fb4138c416511a429853b298aefc51634c87925813116
MD5 04f6ea6f5815c3c9ade9b454647838d4
BLAKE2b-256 902b9eb3ca206495d1a39f0bd2c4f8dc91c9c96a28080177b0e27d4e0d5992fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page