Skip to main content

Python client library for Proscia Concentriq Life Sciences API

Project description

Concentriq LS Python Client

Python client library for Proscia Concentriq Life Sciences API.

Python 3.10+ License: MIT

Installation

pip install concentriq-ls-client

Or with uv:

uv add concentriq-ls-client

Quick Start

from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth

client = ConcentriqClient(
    base_url="https://app.concentriq.com",
    auth=ApiKeyAuth(api_key="your-api-key")
)

# List repositories sorted by image count
repos = client.v1.image_sets.list(
    page=1, rows_per_page=10, sort_by="imageCount", descending=True
)

for repo in repos:
    print(f"{repo['name']}: {repo['imageCount']} images")

Authentication

Four authentication methods are supported:

from concentriq.auth import ApiKeyAuth, BasicAuth, JWTAuth, SessionAuth

# API Key (most common)
auth = ApiKeyAuth(api_key="your-api-key")

# Basic Auth
auth = BasicAuth(username="user", password="pass")

# JWT Bearer Token
auth = JWTAuth(token="your-jwt-token")

# Session Cookie
auth = SessionAuth(session_cookie="connect.sid=...")

Image Upload

Upload image files with automatic single-part or concurrent multipart upload:

from concentriq.upload import upload_image

# Upload from file path (auto-detects name and size)
image = upload_image(client, "/path/to/slide.svs", image_set_id=100)
print(f"Created image: {image['id']}")

# Upload from file object
with open("slide.svs", "rb") as f:
    image = upload_image(
        client, f, image_set_id=100,
        file_name="slide.svs", file_size=os.path.getsize("slide.svs")
    )

# Tune concurrency and part size
image = upload_image(
    client, "/path/to/large_slide.svs",
    image_set_id=100,
    part_size=50 * 1024 * 1024,   # 50 MB parts (default: 15 MB)
    max_concurrency=8,             # parallel uploads (default: 4)
)

Files smaller than part_size use a single presigned PUT. Larger files use S3 multipart upload with concurrent part uploads (3-4x faster than sequential). Multipart uploads are automatically aborted on failure.

Pagination and Sorting

Use server-side sorting and pagination — avoid fetching everything client-side:

from concentriq.models.v1.filters import ImageFilters

# Top 10 repos by image count
repos = client.v1.image_sets.list(
    page=1, rows_per_page=10, sort_by="imageCount", descending=True
)

# Most recent images in a repository
images = client.v1.images.list(
    page=1, rows_per_page=20, sort_by="created", descending=True,
    filters=ImageFilters(imageSetId=[123])
)

# List responses include pagination metadata
repos = client.v1.image_sets.list(page=1, rows_per_page=10)
total = repos.meta["pagination"]["totalRows"]    # total matching rows
returned = repos.meta["pagination"]["rowsReturned"]  # rows in this page
print(f"Showing {returned} of {total} repositories")

# Iterate all pages (only when you truly need everything)
page = 1
all_images = []
while True:
    batch = client.v1.images.list(
        page=page, rows_per_page=1000,
        filters=ImageFilters(imageSetId=[123])
    )
    all_images.extend(batch)
    if len(batch) < 1000:
        break
    page += 1
print(f"Fetched {len(all_images)} images")

Metadata

Reading and Writing Values

from concentriq.models.v1.filters import MetadataValueFilters

# Look up a field by name (cached — only fetches from API once)
field = client.v1.metadata_fields.get_by_name("Patient ID")
field = client.v1.metadata_fields.get_by_name("Status", resource_type="image")

# Read metadata values for an image
values = client.v1.metadata_values.list(
    filters=MetadataValueFilters(imageId=[456])
)

# Update metadata values (batch)
client.v1.metadata_values.update([
    {"fieldId": 1, "resourceId": 456, "content": "PAT-2025-001"},
    {"fieldId": 2, "resourceId": 456, "content": 42},
    {"fieldId": 3, "resourceId": 456, "content": True},
    {"fieldId": 4, "resourceId": 456, "content": "03/17/2026"},  # dates: MM/DD/YYYY
])

Dropdown Fields

Dropdown metadata fields store numeric option IDs, not text. Use DropdownResolver to translate:

from concentriq.metadata import DropdownResolver

field = client.v1.metadata_fields.get_by_name("Status", resource_type="image")
resolver = DropdownResolver(field)

resolver.to_id("Active")    # → 10 (for writing)
resolver.to_value(10)        # → "Active" (for reading)
resolver.values              # ["Active", "Inactive", "Pending"]

# Use in updates
client.v1.metadata_values.update([
    {"fieldId": field["id"], "resourceId": 456, "content": resolver.to_id("Active")}
])

Annotation XML Export/Import

# Export all annotations for an image as Aperio XML
xml_bytes = client.v1.images.export_annotations_xml(456)
with open("annotations.xml", "wb") as f:
    f.write(xml_bytes)

# Export a subset of annotations
xml_bytes = client.v1.images.export_annotations_xml(456, annotation_ids=[100, 101])

# Import annotations from XML (supports Aperio XML, HALO XML, NDPA)
result = client.v1.images.import_annotations(456, "annotations.xml")
print(f"Imported {result['importedAnnotationsCount']} annotations")

# Preview missing annotation classes before importing
result = client.v1.images.import_annotations(
    456, "annotations.xml", require_confirmation=True
)
if result["requireConfirmation"]:
    print(f"Missing classes: {result['nonExistingAnnotationClassNames']}")

Error Handling

from concentriq.exceptions import (
    AuthenticationError,  # 401
    AuthorizationError,   # 403
    NotFoundError,        # 404
    ValidationError,      # 400/422
    ServerError,          # 500+
    APIError,             # base class for all API errors
)

try:
    image_set = client.v1.image_sets.get(id=999)
except NotFoundError:
    print("Not found")
except AuthorizationError:
    print("No permission")
except APIError as e:
    print(f"API error: {e}")

API Coverage

V1 API (/api/*)

  • ImageSets — CRUD, pagination, sorting, filters
  • Images — CRUD, download, annotation XML export/import, upload
  • Folders — Full CRUD
  • Annotations — Full CRUD
  • Metadata — Fields (cached lookup by name), values (batch update), DropdownResolver
  • Users, Organizations, Templates, Workflows, Orders, Attachments

V2 API (/api/v2/*)

  • UserGroups — CRUD with permissions
  • Modules — External module configuration
  • SavedDisplaySettings — Fluorescence display settings

V3 API (/api/v3/*)

  • Auth — JWT tokens, API keys, whoami
  • Studies — Full CRUD + fields/users/statistics
  • ImageSets — Seed generation, blinding
  • Files — Multipart upload infrastructure
  • AnnotationClasses, ChannelGroups, AppConfig, Users, AuditLogs

Requirements

  • Python 3.10+
  • httpx >= 0.27.0
  • pydantic >= 2.0.0

License

Copyright 2025 Proscia Inc. Licensed under the MIT License.

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

concentriq_ls_client-0.2.1.tar.gz (132.6 kB view details)

Uploaded Source

Built Distribution

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

concentriq_ls_client-0.2.1-py3-none-any.whl (50.5 kB view details)

Uploaded Python 3

File details

Details for the file concentriq_ls_client-0.2.1.tar.gz.

File metadata

  • Download URL: concentriq_ls_client-0.2.1.tar.gz
  • Upload date:
  • Size: 132.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for concentriq_ls_client-0.2.1.tar.gz
Algorithm Hash digest
SHA256 72a969cf73e1ee72fca0b4a088adee60be3ce6e7aa53edbe3822f3353d9e9b76
MD5 7a4ba3f3732b1490b847063585841838
BLAKE2b-256 385ed27215a38c35d30a544b3e9211a819a21782c8c59d15226977e47ff05b9d

See more details on using hashes here.

Provenance

The following attestation bundles were made for concentriq_ls_client-0.2.1.tar.gz:

Publisher: publish.yml on Proscia/concentriq-ls-python-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file concentriq_ls_client-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for concentriq_ls_client-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 037c514ac5e413d6e952ab1ab0bb20a6cd8081f873b2dd8c0bf97adb8a16805f
MD5 b0f648d36cefca4b958446fdb33ed4bd
BLAKE2b-256 2e437144bf4a61cf54b84a6332c7fea28d190fc9f1c6603fe18346aece3ec876

See more details on using hashes here.

Provenance

The following attestation bundles were made for concentriq_ls_client-0.2.1-py3-none-any.whl:

Publisher: publish.yml on Proscia/concentriq-ls-python-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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