Metadata-Version: 2.4
Name: dimsechord
Version: 0.4.0
Summary: Pure-Python building blocks for DICOM and DICOMweb services: DIMSE networking, caching, and DICOM↔JSON conversion.
Project-URL: Homepage, https://github.com/radionest/dimsechord
Project-URL: Repository, https://github.com/radionest/dimsechord
Project-URL: Issues, https://github.com/radionest/dimsechord/issues
Author: Denis Nesterov
License-Expression: MIT
License-File: LICENSE
Keywords: dicom,dicomweb,dimse,medical-imaging,pacs,pydicom,pynetdicom,wado
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Healthcare Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: cachetools>=7.0.0
Requires-Dist: pydicom>=2.3.1
Requires-Dist: pynetdicom>=3.0.4
Description-Content-Type: text/markdown

# dimsechord

Pure-Python toolkit of composable building blocks for DICOM and DICOMweb services.

`dimsechord` wraps [`pydicom`](https://github.com/pydicom/pydicom) and
[`pynetdicom`](https://github.com/pydicom/pynetdicom) behind a small, stable
public API. It gives you the networking, caching, and conversion primitives
needed to build PACS proxies, DICOMweb gateways, and imaging pipelines — and
nothing else: there is no bundled HTTP framework and no opinionated request
handlers, so you bring your own web layer.

## Features

- **DIMSE SCU** — C-FIND, C-STORE, C-MOVE and C-GET, exposed through an async
  `DicomClient`.
- **C-STORE SCP** — receive incoming instances with `StorageSCP`, one listener
  per distinct port; multiple AETs may share a port.
- **AssociationPool** — manage multiple AE-Title identities with per-AET
  concurrency limits.
- **Two-tier cache** — in-memory + disk, backed by a SQLite instance index.
- **Streaming pull-engine** — move-to-self retrieval streamed instance by
  instance, with per-UID request coalescing.
- **DICOMweb conversion** — DICOM ↔ DICOMweb JSON and `multipart/related`
  frame responses.

Runtime dependencies are just `pydicom`, `pynetdicom`, and `cachetools`.

## Installation

```bash
pip install dimsechord
```

Requires Python 3.12+.

## Quickstart

```python
import asyncio

from dimsechord import (
    AssociationPool, DicomCache, DicomClient, DicomNode, PullEngine,
    SeriesQuery, StorageSCP, StudyQuery, convert_datasets_to_dicom_json,
)

PACS = DicomNode(aet="PACS", host="127.0.0.1", port=11112)


async def main() -> None:
    # 1. Find studies and series (C-FIND, QIDO-style).
    client = DicomClient(calling_aet="MYSCU")
    studies = await client.find_studies(StudyQuery(patient_id="12345"), PACS)
    series = await client.find_series(
        SeriesQuery(study_instance_uid=studies[0].study_instance_uid), PACS)

    # 2. Pull a series move-to-self and build DICOMweb JSON (WADO-style).
    pool = AssociationPool(aets=["MYDEST"])
    scp = StorageSCP()
    scp.start({"MYDEST": 11113})
    # Your PACS must route the AET "MYDEST" back to this SCP's host:port.
    cache = DicomCache(base_dir="./cache", index_path="./cache/index.db")
    engine = PullEngine(pool=pool, scp=scp, cache=cache, pacs=PACS)
    try:
        cached = await engine.ensure_series(
            studies[0].study_instance_uid, series[0].series_instance_uid)
        metadata = convert_datasets_to_dicom_json(
            list(cached.instances.values()), base_url="https://example.org/dicom-web")
        print(len(metadata), "instances")
    finally:
        scp.stop()
        cache.shutdown()


asyncio.run(main())
```

> **0.4.0 (breaking):** `StorageSCP.start` now takes an `{AET: port}` mapping
> (was `start(aets, port)`), binding one listener per port.

## Public API

The supported surface is exactly what `dimsechord/__init__.py` exports
(`from dimsechord import …`); it is frozen by `tests/unit/test_public_api.py`.

Everything else lives in underscore-prefixed modules (`dimsechord._scu`,
`dimsechord._cache`, …) and is **private** — importing from those modules is
unsupported and may break without notice. If you need something that is not
exported, extend the public surface rather than reaching into a private module.

## Documentation

- [Why dimsechord](docs/why.md) — what problems it solves.
- [Typing](docs/typing.md) — how the typed API works with mypy/pyright.
- [Cookbook](docs/cookbook.md) — one recipe per feature.
- [Gateway tutorial](docs/tutorial-gateway.md) — an end-to-end DICOMweb gateway.

## License

MIT — see [LICENSE](https://github.com/radionest/dimsechord/blob/main/LICENSE).
