Python client library for Proscia Concentriq Life Sciences API
Project description
Concentriq LS Python Client
Python client library for Proscia Concentriq Life Sciences API.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72a969cf73e1ee72fca0b4a088adee60be3ce6e7aa53edbe3822f3353d9e9b76
|
|
| MD5 |
7a4ba3f3732b1490b847063585841838
|
|
| BLAKE2b-256 |
385ed27215a38c35d30a544b3e9211a819a21782c8c59d15226977e47ff05b9d
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
concentriq_ls_client-0.2.1.tar.gz -
Subject digest:
72a969cf73e1ee72fca0b4a088adee60be3ce6e7aa53edbe3822f3353d9e9b76 - Sigstore transparency entry: 1116902191
- Sigstore integration time:
-
Permalink:
Proscia/concentriq-ls-python-client@25959024f899078e1af373ac9d5b899ff8358f75 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Proscia
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@25959024f899078e1af373ac9d5b899ff8358f75 -
Trigger Event:
release
-
Statement type:
File details
Details for the file concentriq_ls_client-0.2.1-py3-none-any.whl.
File metadata
- Download URL: concentriq_ls_client-0.2.1-py3-none-any.whl
- Upload date:
- Size: 50.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
037c514ac5e413d6e952ab1ab0bb20a6cd8081f873b2dd8c0bf97adb8a16805f
|
|
| MD5 |
b0f648d36cefca4b958446fdb33ed4bd
|
|
| BLAKE2b-256 |
2e437144bf4a61cf54b84a6332c7fea28d190fc9f1c6603fe18346aece3ec876
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
concentriq_ls_client-0.2.1-py3-none-any.whl -
Subject digest:
037c514ac5e413d6e952ab1ab0bb20a6cd8081f873b2dd8c0bf97adb8a16805f - Sigstore transparency entry: 1116902222
- Sigstore integration time:
-
Permalink:
Proscia/concentriq-ls-python-client@25959024f899078e1af373ac9d5b899ff8358f75 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Proscia
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@25959024f899078e1af373ac9d5b899ff8358f75 -
Trigger Event:
release
-
Statement type: