diff --git a/README.md b/README.md index bc26af73..dc0a9ddd 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ The application is based on the [dicom-microscopy-viewer](https://github.com/Ima - [Memory monitoring](#memory-monitoring) - [Authentication and authorization](#authentication-and-authorization) - [Configuration](#configuration) - - [Server configuration](#server-configuration) - - [Handling mixed content and HTTPS](#handling-mixed-content-and-https) - - [Messages/popups configuration](#messagespopups-configuration) - - [Memory monitoring configuration](#memory-monitoring-configuration) + - [Server Configuration](#server-configuration) + - [Handling Mixed Content and HTTPS](#handling-mixed-content-and-https) + - [Messages/Popups Configuration](#messagespopups-configuration) + - [Additional configuration topics](#additional-configuration-topics) - [Deployment](#deployment) - [Local](#local) - [Google Cloud Platform](#google-cloud-platform) @@ -133,20 +133,32 @@ Users can authenticate and authorize the application to access data via [OpenID ## Configuration -### Server configuration +The app can be configured via a `public/config/{name}.js` JavaScript configuration file (see for example the default `public/config/local.js`). +Please refer to the [AppConfig.d.ts](src/AppConfig.d.ts) file for configuration options. -The app can be configured via a `public/config/{name}.js` JavaScript configuration file (see, for example, the default `public/config/local.js`). Refer to [AppConfig.d.ts](src/AppConfig.d.ts) for configuration options. +A single-page guide covering external servers, runtime server selection, the `gcp` secondary data source, annotation colors, and read-only / worklist flags is available in [docs/CONFIGURATION.md](docs/CONFIGURATION.md) and on the [project wiki](https://github.com/ImagingDataCommons/slim/wiki/Configuration). -The configuration can be changed at build time using the `REACT_APP_CONFIG` environment variable. +The configuration can be changed at build-time using the `REACT_APP_CONFIG` environment variable. -#### Runtime server selection +### Server Configuration -When `enableServerSelection` is enabled in config, users can switch the active DICOMweb server at runtime via the header. +#### Runtime Server Selection -- **Full URLs:** Paste the complete server URL (for example, `https://healthcare.googleapis.com/v1/projects/.../dicomWeb`). -- **Path-only (GCP Healthcare):** Paste a GCP DICOM store path without the domain (for example, `/projects/my-project/locations/us-central1/datasets/my-dataset/dicomStores/my-store`). The app prepends `https://healthcare.googleapis.com/v1` and appends `/dicomWeb` automatically. +When `enableServerSelection` is enabled in config (default `false`), users can switch the active DICOMweb server at runtime via the header **Select server** button (`ApiOutlined` icon): -Authorization is re-applied when switching servers, so a page reload is not needed after changing the active server. +```js +window.config = { + // ... + enableServerSelection: true, +}; +``` + +- **Full URLs**: Paste the complete server URL (e.g. `https://healthcare.googleapis.com/v1/projects/.../dicomWeb`). +- **Path-only (GCP Healthcare)**: Paste a GCP DICOM store path without the domain (e.g. `/projects/my-project/locations/us-central1/datasets/my-dataset/dicomStores/my-store`). The app always prepends `https://healthcare.googleapis.com/v1` and appends `/dicomWeb` (`normalizeServerUrl`; not controlled by `gcpBaseUrl`). + +Custom selections are stored in `localStorage`, re-apply the current Bearer token when OIDC is in use, and use a temporary **read-only** client (`write: false`) for all SOP classes until you switch back to the default server. + +See [docs/CONFIGURATION.md](docs/CONFIGURATION.md#runtime-server-selection-header-button) for details. ### Handling mixed content and HTTPS @@ -244,6 +256,20 @@ window.config = { When enabled, the memory footer appears at the bottom of all pages and monitors memory usage every 5 seconds. +### Additional configuration topics + +The following topics are documented in [docs/CONFIGURATION.md](docs/CONFIGURATION.md): + +| Topic | Config / mechanism | +| --- | --- | +| External DICOMweb server | `servers[].url` | +| Runtime server selection (header button) | `enableServerSelection` | +| Secondary GCP annotation store | `?gcp=` query parameter | +| Annotation / finding colors | `annotations[].style` | +| Read-only annotation UI | `disableAnnotationTools` | +| Hide study worklist | `disableWorklist` | +| Local Orthanc / CORS troubleshooting | see [Local deployment tips](docs/CONFIGURATION.md#local-deployment-tips) | + ## Deployment ### Prerequisites diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 00000000..2fee524f --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,345 @@ +# Slim configuration guide + +Single-page documentation for the most important Slim configuration options. +The app is configured via a JavaScript file under `public/config/` (for example +`public/config/local.js`). The file is loaded at runtime from +`public/config/{name}.js` via `public/index.html`. Select `{name}` at build / +start time with the `REACT_APP_CONFIG` environment variable (defaults to +`local` via `.env`). + +For the full type definitions, see [`src/AppConfig.d.ts`](../src/AppConfig.d.ts). +Example configs live in [`public/config/`](../public/config/). + +## Table of contents + +- [External DICOMweb server](#external-dicomweb-server) +- [Runtime server selection (header button)](#runtime-server-selection-header-button) +- [Secondary GCP data source (`gcp` query parameter)](#secondary-gcp-data-source-gcp-query-parameter) +- [Annotation colors](#annotation-colors) +- [Read-only mode and worklist](#read-only-mode-and-worklist) +- [Local deployment tips](#local-deployment-tips) +- [Related documentation](#related-documentation) + +## External DICOMweb server + +Point Slim at any DICOMweb-conformant archive by setting `servers` in the config +file: + +```js +window.config = { + path: '/', + servers: [ + { + id: 'local', + url: 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs', + write: true, + }, + ], +} +``` + +Notes: + +- Prefer a DICOMweb service root (QIDO-RS / WADO-RS / STOW-RS base), not a + study or series URL. Alternatively, `servers[].path` may be a path relative + to the Slim origin (resolved by `DicomWebManager`). +- `write: true` allows Slim’s `DicomWebManager.storeInstances` to call STOW-RS + on that server. Today Slim stores verified Comprehensive 3D SR annotation + reports this way. dicom-microscopy-viewer itself does not write instances. +- Optional path prefixes (`qidoPathPrefix`, `wadoPathPrefix`, `stowPathPrefix`) + and `upgradeInsecureRequests` are documented in the + [README](../README.md#handling-mixed-content-and-https). +- For Google Cloud Healthcare, see the + [GCP deployment section](../README.md#google-cloud-platform) in the README + (includes OIDC settings). + +## Runtime server selection (header button) + +Slim can let users change the active DICOMweb endpoint at runtime without +rebuilding the app. This is the feature described in +[issue #5](https://github.com/ImagingDataCommons/slim/issues/5) +(“configurable servers” / external server via the UI). + +### Enable the header button + +Set `enableServerSelection` to `true` in the config (default is `false`): + +```js +window.config = { + path: '/', + servers: [ + { + id: 'default', + url: 'https://example.com/dicomweb', + write: false, + }, + ], + enableServerSelection: true, +} +``` + +When enabled, a **Select server** button appears in the header (Ant Design +`ApiOutlined` icon; often called the “link” icon in issue discussions). +Clicking it opens the **Select DICOMweb server** dialog. + +Reference configs that already enable this: + +- [`public/config/example.js`](../public/config/example.js) +- [`public/config/wg26.js`](../public/config/wg26.js) + +### Using the dialog + +1. Choose **Use default server** to restore the clients built from the config + file (including any `?gcp=` secondary mapping), or **Use custom server** to + enter another endpoint. +2. For a custom server, paste either: + - a **full DICOMweb URL**, e.g. + `https://healthcare.googleapis.com/v1/projects/.../dicomStores/.../dicomWeb` + - a **GCP Healthcare path** without the domain, e.g. + `/projects/my-project/locations/us-central1/datasets/my-dataset/dicomStores/my-store` + — Slim always prepends `https://healthcare.googleapis.com/v1` and appends + `/dicomWeb` via `normalizeServerUrl` (this path does **not** use + `gcpBaseUrl`) +3. Leading/trailing spaces in the URL are trimmed automatically. + +Persistence and behavior: + +- Mode is stored in `localStorage` as `slim_server_selection_mode` + (`default` | `custom`). +- The custom URL is stored as `slim_selected_server`. +- On a custom switch, Slim creates a temporary client with `read: true` and + **`write: false`**, re-applies the current Bearer token when OIDC is in use, + and maps **all** SOP-class clients to that single client (so a prior `?gcp=` + primary/secondary split is replaced until you switch back to the default + server). + +### `gcpBaseUrl` (not used by the dialog) + +`gcpBaseUrl` (default `https://healthcare.googleapis.com/v1`) is used when a +**default** server has no `storageClasses` and the browser path looks like a +GCP Healthcare study URL (`/projects/.../study/...`). It is **not** applied to +path-only URLs entered in the server-selection dialog, and it is **not** +applied to the `?gcp=` query parameter. + +## Secondary GCP data source (`gcp` query parameter) + +You can keep images on the primary configured server and route selected +derived SOP classes to a second Google Cloud Healthcare DICOMweb store by +adding a `gcp` query parameter: + +```text +https:///studies//series/?gcp=https://healthcare.googleapis.com/v1/projects//locations//datasets//dicomStores//dicomWeb +``` + +Behavior: + +- Slim registers a server with id `gcp_secondary_annotation_server`, + `write: true`, and `url` taken **verbatim** from the query parameter (no + `normalizeServerUrl`, no `gcpBaseUrl`). +- That secondary server is mapped to these storage classes for QIDO/WADO (and + STOW when writing those classes): + Comprehensive SR, Comprehensive 3D SR, Segmentation, Microscopy Bulk Simple + Annotations, Parametric Map, and the listed presentation-state classes. +- The primary / default server remains the client for VL Whole Slide + Microscopy Images. +- Saving a verified Comprehensive 3D SR annotation report uses the client + mapped to Comprehensive 3D SR — i.e. the secondary store when `?gcp=` is + present. + +Caveat (dicom-microscopy-viewer): Slim searches/loads ANN **metadata** via the +secondary client, but dmv’s `addAnnotationGroups` currently fetches ANN +bulkdata through the VL Whole Slide Microscopy Image client mapping (typically +the primary store). Absolute `BulkDataURI` values with shared auth often still +work; relative URIs or P10 fallback may hit the wrong store. SEG and Parametric +Map loaders correctly use their SOP-class clients. + +> Related enhancement request for loading *all* data from both stores: +> [issue #320](https://github.com/ImagingDataCommons/slim/issues/320). + +## Annotation colors + +dicom-microscopy-viewer exposes **two** style models. Slim’s config +`annotations[].style` matches the ROI / SR path. + +### Default color per finding (config) — SR ROIs + +Each entry in `annotations` can define a `style` used when drawing SR ROIs for +that finding code. Colors are RGBA-style arrays (`[r, g, b]` or +`[r, g, b, a]`; RGB channels `0–255`, alpha typically `0–1`). Slim maps these +into dmv `ROIStyleOptions` via `formatRoiStyle` / `setROIStyle`: + +```js +window.config = { + annotations: [ + { + finding: { + value: '85756007', + schemeDesignator: 'SCT', + meaning: 'Tissue', + }, + geometryTypes: ['polygon', 'freehandpolygon'], + style: { + stroke: { + color: [255, 255, 0, 1], + width: 2, + }, + fill: { + color: [255, 255, 255, 0.2], + }, + }, + }, + { + finding: { + value: '108369006', + schemeDesignator: 'SCT', + meaning: 'Tumor', + }, + geometryTypes: ['polygon', 'freehandpolygon'], + style: { + stroke: { + color: [255, 0, 255, 1], + width: 2, + }, + fill: { + color: [255, 255, 255, 0.2], + }, + }, + }, + ], +} +``` + +Styles are keyed by finding as `CodingSchemeDesignator-CodeValue` (see +`buildKey` in `src/components/SlideViewer/utils/roiUtils.ts`). + +See [`public/config/local.js`](../public/config/local.js) for a full working +example with several findings and colors. + +Fallbacks: + +1. Finding listed in `config.annotations` **without** `style` → Slim’s default + ROI style (yellow stroke `[255, 234, 0]`). +2. Loaded ROI whose finding is **not** in `config.annotations` → when + formatted, Slim assigns a color from `DEFAULT_ANNOTATION_COLOR_PALETTE`. + +### Microscopy Bulk Simple Annotations (ANN groups) + +Bulk annotation groups use dmv’s `setAnnotationGroupStyle` / +`showAnnotationGroup` options: RGB `color` plus a separate `opacity` (not the +ROI stroke/fill schema). The annotation panel can change group color/opacity +at runtime; those edits update only that group’s on-screen style and do **not** +update Slim’s finding-key `roiStyles` map. + +When config styles are applied to groups, Slim currently passes the configured +`fill.color` as the group `color` (RGB channels only); stroke color and fill +alpha are not used the same way as for SR ROIs. + +### Changing SR ROI colors in the UI + +For Comprehensive SR ROIs, the annotation panel can change color / opacity. +`handleRoiStyleChange` updates `roiStyles` for that finding key and calls +`setROIStyle` on the edited ROI. Other existing ROIs of the same finding may +not all restyle immediately. + +### Selection / highlight colors + +- **Selected SR ROI** highlight is hard-coded in Slim as stroke + `[0, 153, 255]` (alpha 1) and is **not** configurable via `window.config`. +- **Bulk ANN hover highlight** uses dmv’s `highlightColor` (default + `[140, 184, 198]`), which is separate from Slim’s ROI selection style. + +## Read-only mode and worklist + +### Disable annotation tools (`disableAnnotationTools`) + +Set `disableAnnotationTools: true` to hide the slide toolbar that contains +annotation creation / editing controls (default is `false`, tools enabled): + +```js +window.config = { + // ... + disableAnnotationTools: true, +} +``` + +Existing annotations remain viewable. The same flag also hides other controls +bundled in that toolbar (for example **Go to**), not only draw/edit/save. + +### Disable the worklist (`disableWorklist`) + +Set `disableWorklist: true` to replace the study worklist on `/` with +“Worklist has been disabled.” and to hide the worklist navigation button +(default is `false`): + +```js +window.config = { + // ... + disableWorklist: true, +} +``` + +Deep links such as `/studies/...` still work; routes are not removed. + +Example combining both flags: + +```js +window.config = { + path: '/', + servers: [{ id: 'readonly', url: 'https://example.com/dicomweb', write: false }], + disableWorklist: true, + disableAnnotationTools: true, + enableServerSelection: false, +} +``` + +## Local deployment tips + +### Docker Compose (dcm4chee) + +`docker-compose up -d` serves Slim at `http://localhost:8008` and exposes +DICOMweb at: + +```text +http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs +``` + +That URL is already set in [`public/config/local.js`](../public/config/local.js). +nginx in the compose stack proxies the dcm4chee DICOMweb paths; Orthanc is not +part of that stack. + +### Orthanc or another local archive + +If you point Slim at Orthanc (or any other DICOMweb server) instead of the +compose stack, use that server’s DICOMweb root. Orthanc’s DICOMweb plugin +defaults to `/dicom-web` (configurable via `DicomWeb.Root`): + +```js +servers: [ + { + id: 'orthanc', + url: 'http://localhost:8042/dicom-web', + write: true, + }, +] +``` + +If the browser shows a communication / search-for-studies error while `curl` +against the same URL succeeds, check: + +1. **CORS** – the DICOMweb server must allow the Slim origin + (`http://localhost:3000` in development, or `http://localhost:8008` when + served from compose). Orthanc needs an explicit CORS configuration for + cross-origin browser calls. +2. **Correct base URL** – use the DICOMweb root (`.../dicom-web` or + `.../rs`), not a study page URL. +3. **Mixed content** – an HTTPS Slim deployment cannot call plain HTTP archives + unless you terminate TLS in front of the archive or use a same-origin proxy. +4. **Auth** – secured endpoints need matching `oidc` settings in the config. + +## Related documentation + +- [README – Configuration](../README.md#configuration) +- [Logger configuration](./LOGGER_CONFIGURATION.md) +- [Memory monitoring](./MEMORY_MONITORING.md) +- [AppConfig type definitions](../src/AppConfig.d.ts) +- GitHub wiki mirror: https://github.com/ImagingDataCommons/slim/wiki