Updates to Sonador Documentation describing Sonador Viewer (OHIF refactor) to... authored by Rob Oakes's avatar Rob Oakes
Updates to Sonador Documentation describing Sonador Viewer (OHIF refactor) to React 18, M3D, and Cornerstone3D viewports
# Sonador Design Patterns
TODO:Describe the design philosophy and "rules of thumb" which govern the Sonador interfaces.
The Sonador client libraries are designed around a single idea: **interacting with a remote medical
imaging system should feel like working with ordinary Python objects.** Rather than assembling URLs
and parsing JSON by hand, a developer connects to a server, asks it for a resource, and receives a
model whose properties and methods map onto the underlying DICOM/Sonador data and the operations the
server supports. The libraries favor *predictability over cleverness* — every resource follows the
same conventions, so once you have learned how to work with one (e.g. a study) you already know how
to work with the rest (series, instances, reports, segmentations).
These patterns build directly on the
[Guru Client](https://code.oak-tree.tech/django-apps/guru/-/wikis/dev.client-tools) (servers,
models, collections, and the `fetch_*` helpers). This page describes how Sonador applies them and the
"rules of thumb" that govern the Sonador IO client specifically.
* Sonador client libraries provide an object relational map (ORM) interface to interact with system resources stored within Sonador and Orthanc.
- The [Sonador IO Client](https://code.oak-tree.tech/oak-tree/medical-imaging/sonador-client) libary (which is a core dependency of all other Sonador libraries) inherits from the [Guru Client](https://code.oak-tree.tech/guru-labs/guru-client), which provides base utilities and core object clases.
......@@ -25,13 +36,54 @@ The Guru Client provides three base components:
* collection classes which can be used to interact with logically grouped sets of models
TODO:Describe server implementation
#### Servers
Sonador IO provides two server classes, both derived from the Guru Client `RemoteServer`:
TODO:Describe base model class and primary interface
* **`sonador.servers.SonadorServer`** — the connection to a Sonador web application. It handles
authentication (an API access token sent as the `api-token` header, an OAuth `Bearer` token, or an
HMAC-SHA1 signed URL built from an access-id/secret-key pair), exposes administrative resources
(users, groups, credentials, tokens), and is the entry point for retrieving imaging servers via
`get_imageserver` / `fetch_imageservers`.
* **`sonador.servers.SonadorImagingServer`** — a handle to a specific Orthanc-backed PACS that is
registered with the Sonador app. It is itself a Sonador resource (fetched from
`/visionaire/api/pacs`) and provides the query, retrieval, upload, and bulk-operation methods used
to work with imaging data. Requests to Orthanc are authenticated and routed through Sonador, so
ACLs are enforced.
TODO:Describe base collection model class and primary interface
A server is most often built from environment variables with `sonador.helpers.initenv_sonador_server`
(`SONADOR_URL`, `SONADOR_ACCESS_ID`, `SONADOR_SECRET_KEY`, `SONADOR_APITOKEN`, `SONADOR_INTERNAL_DNS`,
`SONADOR_VERIFY_SSL`), which is the same configuration surface exposed by the command-line tools.
TODO:Describe REST-like rules of thumb for Sonador applications.
#### Base model class
Imaging models inherit (through `SonadorBaseObject`) the Guru Client model interface and add the
Sonador conventions:
* **`fetch_endpoint`** — the management endpoint for the resource type (e.g. `patients`, `studies`,
`series`). Scheme/host/port come from the server.
* **`pk_attr`** / **`pk`** — selects the JSON field that uniquely identifies the instance (Orthanc
resources use `ID`); `resource_url` is derived from the endpoint and the `pk`.
* **`update(odata)`**`PUT` changed attributes (the REST/details endpoint).
* **`delete()`**`DELETE` the instance from the server.
Resource models additionally expose **navigation properties and `fetch_*` methods** that return child
collections (a study's series, a series' instances), and metadata helpers (`fetch_meta`,
`fetch_attachments`, ACL accessors). Many of these are cached on first access.
#### Base collection class
Collections inherit the Guru Client `JsonObjectCollection`/pagination behavior and add the
management-endpoint conventions as class methods:
* **`fetch(...)`** — retrieve a set of models (with pagination and filtering).
* **`fetch_modelinstance(objectid, ...)`** — retrieve one model by identifier.
* **`create(odata, ...)`**`POST` a new instance.
Child collections that belong to a parent (for example `DcmSRSeriesCollection` under a study, or
`ResourceCommentCollection` under any resource) take the parent as an argument and thread it through
`_init_collection_models` so that each model can build its own URLs and reach the imaging server
(`parent.pacs`). The Sonador IO client also provides `SonadorCachedObjectCollectionMixin`, which keeps
an in-memory hashmap of models keyed by `pk` for rapid `get_modelinstance(pk)` lookup.
#### REST-like rules of thumb for Sonador applications
* By convention, every Guru, Oak-Tree, and Sonador API resource is associated with **two primary endpoints**.
- **A "management" endpoint** that is used for describing the resource, retrieving collections of logically grouped models, and creating new instances of the model.
......@@ -45,7 +97,7 @@ TODO:Describe REST-like rules of thumb for Sonador applications.
+ `DELETE`: remove the model instance
* Client design philosophies
- Client model classes contain the properties that define what the model "is" and where its data resides.
- Client collection classes read model properties to execute
- Client collection classes read model properties to execute requests against the management endpoint on the model's behalf.
- Client model and collection operations are organized around the capabilities of the endpoints they interact with.
+ **Collection operations** mirror management endpoint capabilities.
* `fetch` (class method): retrieve a set of models from the endpoint
......@@ -64,17 +116,113 @@ TODO:Describe REST-like rules of thumb for Sonador applications.
### `local`
TODO:Describe the `local` module of Sonador, the problems it solves, when it should be used, and examples.
The `local` axis covers data that lives **outside** a server API — either DICOM files read from disk
or in-memory collections of models that follow Sonador conventions without being backed by a remote
endpoint. It solves two related problems:
**1. Reading and normalizing DICOM from a folder.** The helpers in `sonador.helpers.local` load DICOM
files with `pydicom` and back-fill the attributes Orthanc requires for ingestion:
### `remote`
TODO:Describe the `remote` module of Sonador, the problems it solves, when it should be used and examples.
* **`dcmread_backfill(fpath, ...)`** reads a file and fills any missing required identifiers — Patient
ID, Study/Series/SOP Instance UIDs — and study/series/content dates and times, generating values
where the source data is incomplete. This is what makes ad-hoc or partial DICOM safe to upload.
* **`dcm_part10_backfill(dcm)`** ensures the dataset is well-formed per the DICOM Part-10 encoding
rules — inferring the transfer syntax and constructing the file-meta block (SOP class/instance UIDs,
implementation class/version) when it is absent.
```python
from sonador.helpers.local import dcmread_backfill, dcm_part10_backfill
dcm = dcmread_backfill('/data/scan/IM0001') # fills missing UIDs / timestamps
dcm, _changed = dcm_part10_backfill(dcm) # ensures valid Part-10 file meta
# `dcm` is now safe to upload via SonadorImagingServer.upload_image(...)
```
The `remote` module includes models (sometimes called data objects)
**2. In-memory model collections.** `sonador.local` provides `SonadorLocalObject` and
`SonadorLocalCollection` (built on the Guru Client `local` base classes). They present the same model
interface as remote resources but hold data the application has assembled itself. The
`SonadorCachedObjectCollectionMixin` they use indexes models by `pk` so that `get_modelinstance(pk)`,
`append`, and `extend` stay fast for large sets. Use `local` when you need DICOM in the Sonador object
model before (or without) a round-trip to a server — bulk-prepping files for upload, or caching
results client-side.
Model class properties.
* `fetch_endpoint` (`str` or `property` which returns `str`): defines the "management" interface for the dataclass. _The endpoint should contain the resource path, query parameters and fragments. Schema, network location, and port will be taken from the server._
> 📘 The data-preparation tasks in `sonador.tasks` (uploads, reindexing) build on these helpers.
### `remote`
The `remote` axis is the heart of day-to-day use: models and collections backed by the Sonador and
Orthanc server APIs. A model (sometimes called a *data object*) maps a server resource to a Python
object whose properties expose its DICOM tags and whose methods expose the operations the server
supports.
Model class properties:
* **`fetch_endpoint`** (`str`, or a `property` returning `str`): defines the "management" interface
for the dataclass. _The endpoint should contain the resource path, query parameters, and fragments.
Scheme, network location, and port are taken from the server._
* **`pk_attr`** / **`pk`**: identifies the resource; Orthanc resources key on `ID`.
* **`resource_url`**: the details (REST) endpoint for the instance, derived from `fetch_endpoint` and
the `pk`.
The imaging resource hierarchy mirrors DICOM and is reachable by navigation:
| Model | Endpoint | Key navigation |
|-------|----------|----------------|
| `ImagingPatient` | `patients` | `studies` / `fetch_studies` |
| `ImagingStudy` | `studies` | `fetch_series`, `fetch_sr`, `fetch_seg`, `fetch_m3d`, `fetch_doc`, `create_comment` |
| `ImagingSeries` | `series` | `fetch_dcminstances`, `instances`, `segmentations`, `m3d_models` |
| `DcmInstance` | (instance) | DICOM tags, attachments |
Resources are located through the imaging server's `query` family
(`query_patient` / `query_study` / `query_series` / `query_instance`, plus `query_sr` / `query_seg` /
`query_m3d` / `query_doc`) or fetched directly by ID (`get_patient` / `get_study` / `get_series` /
`get_dcm_instance`). Under the hood queries prefer Orthanc's ACL-mediated, cache-enabled
`/tools/secure-find`; a `rapid_lookup` cache path and the admin `/tools/find` database path are also
available.
```python
from sonador.helpers import initenv_sonador_server
server = initenv_sonador_server() # SonadorServer from env vars
pacs = server.get_imageserver('research-pacs') # SonadorImagingServer
# Query, then navigate the hierarchy
for study in pacs.query_study({'PatientID': 'CT-*', 'StudyDescription': '*Chest*'}):
for series in study.fetch_series():
for dcm in series.fetch_dcminstances():
... # work with each DcmInstance
```
**Extension models.** Resources can carry server-side attachments that are not part of the DICOM
payload — most notably **comments** (`ResourceComment` / `ResourceCommentCollection`). These follow
the standard collection/model conventions but are *parented* to a resource: they reach the server via
`parent.pacs` and build their URLs from the parent's `comments_url` (with a DICOMweb variant). This is
the general pattern for any DICOM-extension data Sonador layers on top of Orthanc resources.
> 📘 Extension models require a valid parent; their `create`/`fetch`/`fetch_modelinstance`
> class methods take the parent resource (not the server) as their first argument.
### `sr`
TODO:Describe the `sr` module of Sonador and how it relates to the `local` and `remote` modules.
The `sr` axis adds models for **DICOM Structured Reporting** — reports that carry structured data and
references to the images they describe. It is a specialization of `remote`: `DcmSRSeries` /
`DcmSRInstance` are imaging resources (fetched and navigated exactly like ordinary series and
instances, e.g. via `study.fetch_sr()`), with extra behavior for parsing the SR content tree.
Its defining feature is **reference resolution**. An SR instance walks its evidence sequences to
collect the UIDs of the images it references:
* `DcmSRInstance.instance_reference_uids` — the set of referenced SOP instance UIDs.
* `DcmSRInstance.series_reference_uids` — the set of referenced series UIDs.
* `DcmSRSeries.reference_series_collection` — the actual sibling `ImagingSeries` in the same study
that the report points at, most-recent first.
This lets an application start from a report and resolve back to the source imagery (or vice versa),
which is the basis for displaying measurements and findings against the images they were derived from.
The same structured-instance base classes are shared with DICOM-SEG (segmentation) documents, so
segmentations resolve their referenced series the same way.
> 📘 `sr` relates to `local` and `remote` as follows: `remote` provides the transport and the resource
> hierarchy, `sr` adds the structured-content parsing on top of it, and `local` (with the
> `sonador.helpers.sr` utilities) handles SR data that is being read from or written to disk rather
> than fetched from a server.