Skip to main content

loom-client

Python WebSocket client for the Loom Genome Browser. Provides a fully async, typed interface for programmatically controlling a running Loom instance -- navigating loci, managing tracks and regions of interest, exporting views, and subscribing to browser events.

Installation

pip install loom-client

Or install from source:

git clone https://github.com/riyavsinha/loom-client.git
cd loom-client
pip install -e .

Requires Python >= 3.11.

Quick start

Option 1: Connect to a Loom instance

Use this when your code initiates the WebSocket connection to a running Loom server.

import asyncio
from loom_client import LoomClient

async def main():
    client = LoomClient("ws://localhost:8080")
    await client.connect()

    # Get current browser state
    state = await client.get_browser_state()
    print(state.locus_string)   # e.g. "chr1:1,000-2,000"
    print(state.zoom_level)     # e.g. ZoomLevel.GENE

    # Navigate to a locus
    await client.navigate(locus="chr17:7565097-7590856")  # TP53

    # Zoom out 2x
    await client.navigate(zoom="out", factor=2.0)

    await client.close()

asyncio.run(main())

Option 2: Use an existing WebSocket connection

Use this when the WebSocket is already established — for example, a frontend-initiated connection handled by your backend, or when bridging through a proxy.

from loom_client import LoomClient

async def handle_connection(websocket):
    """Example: called by your WebSocket server when a client connects."""
    client = LoomClient.from_websocket(websocket)
    client.start_listening()

    state = await client.get_browser_state()
    print(state.locus_string)

    await client.close()

API reference

Connection

# Option 1: Initiate a new connection
client = LoomClient(url)   # Create client
await client.connect()     # Open WebSocket + start listener

# Option 2: Reuse an existing WebSocket
client = LoomClient.from_websocket(ws)  # Wrap existing connection
client.start_listening()                # Start listener

await client.close()       # Tear down

Commands

All command methods are async and return typed Pydantic models.

get_browser_state(record=None) -> ProjectedState

Returns the current viewport state including locus, zoom level, loaded tracks, ROIs, and annotations.

state = await client.get_browser_state()
for track in state.tracks:
    print(f"{track.name} ({track.type})")

navigate(locus=None, zoom=None, factor=None) -> bool

Move the viewport to a genomic locus or zoom in/out.

await client.navigate(locus="chr1:1000-2000")
await client.navigate(zoom="in", factor=3.0)

modify_tracks(actions) -> ModifyTracksResult

Add, remove, find, or update tracks in a single batch.

from loom_client import AddTrackAction, RemoveTrackAction, TrackSessionConfig, DataSourceConfig, WireTrackSelector

# Add a BigWig track
result = await client.modify_tracks([
    AddTrackAction(config=TrackSessionConfig(
        type="wig",
        name="H3K27ac",
        data_source=DataSourceConfig(
            type="bigwig",
            url="https://example.com/h3k27ac.bw",
        ),
    ))
])

# Remove by name pattern
result = await client.modify_tracks([
    RemoveTrackAction(selector=WireTrackSelector(name_regex="H3K.*"))
])

query_features(track_id, summarize=None) -> QueryFeaturesResult

Query features for a loaded track.

result = await client.query_features("track-id-123", summarize=True)
print(result.feature_count)

set_layout(tracks, locus=None) -> SetLayoutResult

Replace the entire track layout at once.

result = await client.set_layout(
    tracks=[
        TrackSessionConfig(type="ruler"),
        TrackSessionConfig(type="sequence"),
        TrackSessionConfig(
            type="annotation",
            name="GENCODE",
            data_source=DataSourceConfig(type="gencode", genome="hg38"),
        ),
    ],
    locus="chr1:1000-5000",
)

export_view(format, width=None) -> str | SessionConfig

Export the current view as SVG, PNG (returned as strings), or a session config object.

svg = await client.export_view("svg", width=1200)
session = await client.export_view("session")  # returns SessionConfig

manage_rois(action) -> ROI result

Manage regions of interest. The action type determines the operation.

from loom_client import AddROIAction, ROI, ListROIAction, FindROIAtLocusAction

# Add a region of interest
result = await client.manage_rois(AddROIAction(
    roi=ROI(id="roi-1", chr="chr17", start=7565097, end=7590856,
            name="TP53", color="rgba(255,0,0,0.3)"),
    set_name="Genes of interest",
))

# List all ROIs
result = await client.manage_rois(ListROIAction())

# Find ROIs overlapping a locus
result = await client.manage_rois(
    FindROIAtLocusAction(chr="chr17", start=7500000, end=7600000)
)

annotate(action) -> annotation result

Draw and manage the user/agent annotation layer — track-scoped box / point / freehand markup, distinct from ROIs (cross-track highlights). The server generates id/createdAt, so pass an AnnotationInput (only chr/start/end/trackIds are required; kind defaults to box, author to user).

from loom_client import (
    AddAnnotationAction, AnnotationInput, UpdateAnnotationAction,
    ListAnnotationAction, AnnotationFilter,
)

# Agent draws a glowing box to call attention to a region on one track
result = await client.annotate(AddAnnotationAction(
    annotation=AnnotationInput(
        kind="box", chr="chr17", start=7674200, end=7674260,
        track_ids=["h3k27ac"], text="likely active enhancer",
        author="agent", emphasis="glow",   # 'pulse' (size) | 'glow'/'flash' (color)
    ),
))
annotation_id = result["annotation"]["id"]

# Toggle emphasis later (e.g. a history stepper highlighting the current step's mark)
await client.annotate(UpdateAnnotationAction(
    annotation_id=annotation_id, changes={"emphasis": "pulse"}))

# List agent-authored annotations
result = await client.annotate(ListAnnotationAction(
    filter=AnnotationFilter(author="agent")))

# "Circle this area": an ellipse outline reads as a callout, not a BED-like box.
# outline=stroke-only (no fill); corner_radius 0..1 (1 = ellipse). box-only.
await client.annotate(AddAnnotationAction(
    annotation=AnnotationInput(
        kind="box", chr="chr17", start=7674200, end=7674260,
        track_ids=["h3k27ac"], author="agent",
        outline=True, corner_radius=1, text="candidate enhancer",
    ),
))

Actions: AddAnnotationAction, RemoveAnnotationAction, UpdateAnnotationAction, ClearAnnotationAction, ListAnnotationAction, GetVisibleAnnotationAction. A subscribed client receives annotationadded when a human draws, and every get_browser_state includes an annotations list of what's visible.

subscribe_events(events) -> SubscribeEventsResult

Subscribe to server-pushed events. Use ["*"] for all events or [] to unsubscribe.

await client.subscribe_events(["locuschange", "trackadded", "dataloaded"])

Event handling

Register callbacks for browser events:

def on_locus_change(data):
    locus = data["locus"]
    print(f"Moved to {locus['chr']}:{locus['start']}-{locus['end']}")

client.on("locuschange", on_locus_change)

# Wildcard handler receives (event_name, data)
client.on("*", lambda name, data: print(f"Event: {name}"))

# Unregister
client.off("locuschange", on_locus_change)

Available events: locuschange, trackadded, trackremoved, dataloaded, dataerror, rendererror, trackclick, trackhover, trackcontextmenu, trackorderchanged, roiadded, roiremoved, roichanged, roiclick, roicontextmenu

Error handling

Server errors raise CommandException:

from loom_client import CommandException

try:
    await client.navigate(locus="invalid")
except CommandException as e:
    print(e.code, e.message)

Development

pip install -e ".[dev]"
pytest

License

MIT

Metadata

Release files for loom-client 0.0.6

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

Source distribution (sdist)

Source distribution for loom-client 0.0.6
File Size Uploaded
loom_client-0.0.6.tar.gz 24.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for loom-client 0.0.6
File Interpreter ABI Platform
loom_client-0.0.6-py3-none-any.whl Python 3 none any Details

Total release size: 38.3 kB

Release files / loom_client-0.0.6.tar.gz

Download URL loom_client-0.0.6.tar.gz
Size 24.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c18fec08bdacd079739e9a095a3408016eb3ca058ac3e99a61d74ea9644367b0
BLAKE2b-256 checksum
How to use checksums
f1529649b6b1049e5065e32511bb99454618e70547a991c77ad706d151cfcf2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.7

Release files / loom_client-0.0.6-py3-none-any.whl

Download URL loom_client-0.0.6-py3-none-any.whl
Size 14.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5fe3b41407857197282329314a3b6f0f7cc6f12d611eac95e5464c62a2c42b4b
BLAKE2b-256 checksum
How to use checksums
c279b708cc8f8a6cba013757a895fa3ba3bde9aa90aca219eb1680fdb81f7b58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.7

Release history Release notifications | RSS feed

0.0.7

2 release files

This release

0.0.6 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

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