diff --git a/platform/docs/versioned_docs/version-2.0/Architecture.md b/platform/docs/versioned_docs/version-2.0/Architecture.md new file mode 100644 index 000000000..211b09cc0 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/Architecture.md @@ -0,0 +1,151 @@ +--- +sidebar_position: 5 +title: Architecture +--- + +# Architecture + +Looking to extend your instance of the OHIF Viewer? Want learn how to reuse _a +portion_ of the Viewer in your own application? Or maybe you want to get +involved and draft or suggest a new feature? Regardless, you're in the right +place! + +The OHIF Viewer aims to be decoupled, configurable, and extensible; while this +allows our code to be used in more ways, it also increases complexity. Below, we +aim to demistify that complexity by providing insight into how our Viewer is +architected, and the role each of it's dependent libraries plays. + + + +The [OHIF Medical Image Viewing Platform][viewers-project] is maintained as a +[`monorepo`][monorepo]. This means that this repository, instead of containing a +single project, contains many projects. If you explore our project structure, +you'll see the following: + +```bash +. +├── extensions +│ ├── _example # Skeleton of example extension +│ ├── cornerstone # 2D images w/ Cornerstone.js +│ ├── dicom-html # Structured Reports as HTML in viewport +│ ├── dicom-microscopy # Whole slide microscopy viewing +│ ├── dicom-pdf # View DICOM wrapped PDFs in viewport +│ └── vtk # MPR and Volume support w/ VTK.js +│ +├── platform +│ ├── core # Business Logic +│ ├── i18n # Internationalization Support +│ ├── ui # React component library +│ └── viewer # Connects platform and extension projects +│ +├── ... # misc. shared configuration +├── lerna.json # MonoRepo (Lerna) settings +├── package.json # Shared devDependencies and commands +└── README.md +``` + +The `platform` directory contains the business logic library, component library, +and the application library that combines them to create a powerful medical +imaging viewer. + +The `extensions` directory contains many packages that can be registered with +`@ohif/core`'s `ExtensionManager` to expand an application's supported features +and functionality. + +![Architecture Diagram](./assets/img/architecture-diagram.png) + + +This diagram is a conceptual illustration of how the Viewer is architected. + +1. (optional) `extensions` can be registered with `@ohif/core`'s + `ExtensionManager` +2. `@ohif/core` provides bussiness logic and a way for `@ohif/viewer` to access + registered extensions +3. The `@ohif/viewer` composes and provides data to components from our + component library (`@ohif/ui`) +4. The `@ohif/viewer` can be built and served as a stand-alone PWA, or as an + embeddable package ([`@ohif/viewer`][viewer-npm]) + +## Business Logic + +The [`@ohif/core`][core-github] project offers pre-packaged solutions for +features common to Web-based medical imaging viewers. For example: + +- Hotkeys +- DICOM Web requests +- Hanging Protocols +- Managing a study's measurements +- Managing a study's DICOM metadata +- [A flexible pattern for extensions](./extensions/index.md) +- And many others + +It does this while remaining decoupled from any particular view library or +rendering logic. While we use it to power our React Viewer, it can be used with +Vue, React, Vanilla JS, or any number of other frameworks. + +## React Component Library + +[`@ohif/ui`][ui-github] is a React Component library that contains the reusable +components that power the OHIF Viewer. It allows us to build, compose, and test +components in isolation; easing the development process by reducing the need to +stand-up a local PACS with test case data. + +Extension authors can also use these same components when building their +extension's UI; allowing for a consistent look and feel with the rest of the +application. + +[Check out our component library!](https://react.ohif.org/) + +## Extensions & Configuration + +While OHIF maintains several high value and commonly requested features in its +own extensions, there are many instances where one may wish to further extend +the viewer. Some common use cases include: + +- Adding AI/ML tools and insights +- Custom workflows for guided diagnosis +- Collecting specific annotations for training data or reports +- Authentication and granular permissions +- Teleconsultation workflow, image comments, and tracking +- Adding surgical templating tools and reports +- and many others + +We expose common integration points via [extensions](./extensions/index.md) to +make this possible. The viewer and many of our own extensions also offer +[configuration][configuration]. For a list of extensions maintained by OHIF, +[check out this helpful table](./extensions/index.md#maintained-extensions). + +If you find yourself thinking "I wish the Viewer could do X", and you can't +accomplish it with an extension today, create a GitHub issue! We're actively +looking for ways to improve our extensibility ^\_^ + +[Click here to read more about extensions!](./extensions/index.md) + +## Common Questions + +> When should I use the packaged source `@ohif/viewer` versus building a PWA +> from the source? + +... + +> Can I create my own Viewer using Vue.js or Angular.js? + +You can, but you will not be able to leverage as much of the existing code and +components. `@ohif/core` could still be used for business logic, and to provide +a model for extensions. `@ohif/ui` would then become a guide for the components +you would need to recreate. + + + + +[monorepo]: https://github.com/OHIF/Viewers/issues/768 +[viewers-project]: https://github.com/OHIF/Viewers +[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer +[pwa]: https://developers.google.com/web/progressive-web-apps/ +[configuration]: ../configuring/index.md +[extensions]: ../extensions/index.md +[core-github]: https://github.com/OHIF/viewers/platform/core +[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui + diff --git a/platform/docs/versioned_docs/version-2.0/README.md b/platform/docs/versioned_docs/version-2.0/README.md new file mode 100644 index 000000000..9cb513ec2 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/README.md @@ -0,0 +1,77 @@ +--- +id: Introduction +slug: / +sidebar_position: 1 +--- + + + + +
+ Looking for a Live Demo? +
+ + + + +
+Preview The OHIF Viewer + + +
+ + + + +> ATTENTION! You are looking at the docs for the `React` version of the OHIF +> Viewer. If you're looking for the `Meteor` version's documentation (now +> deprecated), select it's version from the dropdown box in the top right corner +> of this page. + +The [Open Health Imaging Foundation][ohif-org] (OHIF) Viewer is an open source, +web-based, medical imaging viewer. It can be configured to connect to Image +Archives that support [DicomWeb][dicom-web], and offers support for mapping to +proprietary API formats. OHIF maintained extensions add support for viewing, +annotating, and reporting on DICOM images in 2D (slices) and 3D (volumes). + +![OHIF Viewer Screenshot](./assets/img/viewer.png) + +
The OHIF Viewer: A general purpose DICOM Viewer (Live Demo)
+ +The Open Health Imaging Foundation intends to provide a simple general purpose +DICOM Viewer which can be easily extended for specific uses. If you find +yourself unable to extend the viewer for your purposes, please reach out via our +[GitHub issues][gh-issues]. We are actively seeking feedback on ways to improve +our integration and extension points. + +## Where to next? + +Check out these helpful links: + +- Ready to dive into some code? Check out our + [Getting Started Guide](./development/getting-started.md). +- We're an active, vibrant community. + [Learn how you can be more involved.](./development/contributing.md) +- Feeling lost? Read our [help page](./help.md). + + + + +[ohif-org]: http://www.ohif.org +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[gh-issues]: https://github.com/OHIF/Viewers/issues + diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram new file mode 100644 index 000000000..bbf6cf58b Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig new file mode 100644 index 000000000..8756e9f79 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg new file mode 100644 index 000000000..ad04389c6 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram new file mode 100644 index 000000000..182ad2323 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig new file mode 100644 index 000000000..460ae95dd Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg new file mode 100644 index 000000000..8e4aac5d2 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig new file mode 100644 index 000000000..5eb82e561 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig new file mode 100644 index 000000000..8982a8fed Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png new file mode 100644 index 000000000..3e562a797 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png new file mode 100644 index 000000000..f9c4a568b Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png new file mode 100644 index 000000000..54b0aa39f Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png new file mode 100644 index 000000000..f3c2a8069 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png b/platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png new file mode 100644 index 000000000..1c43d0108 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif new file mode 100644 index 000000000..22fde7a7f Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif b/platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif new file mode 100644 index 000000000..b6f9754c4 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png new file mode 100644 index 000000000..426cb7ab8 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png new file mode 100644 index 000000000..c71be8692 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif new file mode 100644 index 000000000..fd173bd70 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif new file mode 100644 index 000000000..d89a75ab3 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif new file mode 100644 index 000000000..88c313f3d Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png new file mode 100644 index 000000000..0ecffda06 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/homePage.png b/platform/docs/versioned_docs/version-2.0/assets/img/homePage.png new file mode 100644 index 000000000..9ae0624cc Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/homePage.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png new file mode 100644 index 000000000..f26509a16 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png new file mode 100644 index 000000000..0ea77f965 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png new file mode 100644 index 000000000..ad060f262 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png b/platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png new file mode 100644 index 000000000..effc7da90 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg new file mode 100644 index 000000000..1139aa2c7 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg @@ -0,0 +1,187 @@ + + + + Custom Preset 2 Copy + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif b/platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif new file mode 100644 index 000000000..834705de1 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif b/platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif new file mode 100644 index 000000000..98634e088 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png new file mode 100644 index 000000000..bd7547965 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif b/platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif new file mode 100644 index 000000000..34d564cb4 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png new file mode 100644 index 000000000..5b881abdf Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png new file mode 100644 index 000000000..6daac8bee Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/services.png b/platform/docs/versioned_docs/version-2.0/assets/img/services.png new file mode 100644 index 000000000..569c046c0 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/services.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif new file mode 100644 index 000000000..545f06863 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png new file mode 100644 index 000000000..dd5306377 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png new file mode 100644 index 000000000..573c83503 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/viewer.png b/platform/docs/versioned_docs/version-2.0/assets/img/viewer.png new file mode 100644 index 000000000..21eacd59a Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/viewer.png differ diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/worklist.png b/platform/docs/versioned_docs/version-2.0/assets/img/worklist.png new file mode 100644 index 000000000..3f126c784 Binary files /dev/null and b/platform/docs/versioned_docs/version-2.0/assets/img/worklist.png differ diff --git a/platform/docs/versioned_docs/version-2.0/configuring/_category_.json b/platform/docs/versioned_docs/version-2.0/configuring/_category_.json new file mode 100644 index 000000000..eef358871 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/configuring/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Configuring", + "position": 4 +} diff --git a/platform/docs/versioned_docs/version-2.0/configuring/data-source.md b/platform/docs/versioned_docs/version-2.0/configuring/data-source.md new file mode 100644 index 000000000..cc932255f --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/configuring/data-source.md @@ -0,0 +1,172 @@ +--- +sidebar_position: 2 +--- +# Data Source + +After following the steps outlined in +[Getting Started](./../development/getting-started.md), you'll notice that the +OHIF Viewer has data for several studies and their images. You didn't add this +data, so where is it coming from? + +By default, the viewer is configured to connect to a remote server hosted by the +nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, +the time may come when you want to develop using your own data either locally or +remotely. + +## Set up a local DICOM server + +> ATTENTION! Already have a remote or local server? Skip to the +> [configuration section](#configuration-learn-more) below. + +While the OHIF Viewer can work with any data source, the easiest to configure +are the ones that follow the [DICOMWeb][dicom-web] spec. + +1. Choose and install an Image Archive +2. Upload data to your archive (e.g. with DCMTK's [storescu][storescu] or your + archive's web interface) +3. Keep the server running + +For our purposes, we will be using `Orthanc`, but you can see a list of +[other Open Source options](#open-source-dicom-image-archives) below. + +### Requirements + +- Docker + - [Docker for Mac](https://docs.docker.com/docker-for-mac/) + - [Docker for Windows (recommended)](https://docs.docker.com/docker-for-windows/) + - [Docker Toolbox for Windows](https://docs.docker.com/toolbox/toolbox_install_windows/) + +_Not sure if you have `docker` installed already? Try running `docker --version` +in command prompt or terminal_ + +> If you are using `Docker Toolbox` you need to change the _PROXY_DOMAIN_ +> parameter in _platform/viewer/package.json_ to http://192.168.99.100:8042 or +> the ip docker-machine ip throws. This is the value [`WebPack`][webpack-proxy] +> uses to proxy requests + +### Running Orthanc + +_Start Orthanc:_ + +```bash +# Runs orthanc so long as window remains open +yarn run orthanc:up +``` + +_Upload your first Study:_ + +1. Navigate to + [Orthanc's web interface](http://localhost:8042/app/explorer.html) at + `http://localhost:8042/app/explorer.html` in a web browser. +2. In the top right corner, click "Upload" +3. Click "Select files to upload..." and select one or more DICOM files +4. Click "Start the upload" + +#### Orthanc: Learn More + +You can see the `docker-compose.yml` file this command runs at +[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on +Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. + +### Connecting to Orthanc + +Now that we have a local Orthanc instance up and running, we need to configure +our web application to connect to it. Open a new terminal window, navigate to +this repository's root directory, and run: + +```bash +# If you haven't already, enable yarn workspaces +yarn config set workspaces-experimental true + +# Restore dependencies +yarn install + +# Run our dev command, but with the local orthanc config +yarn run dev:orthanc +``` + +#### Configuration: Learn More + +> For more configuration fun, check out the +> [Essentials Configuration](./index.md) guide. + +Let's take a look at what's going on under the hood here. `yarn run dev:orthanc` +is running the `dev:orthanc` script in our project's `package.json`. That script +is: + +```js +cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w +``` + +- `cross-env` sets three environment variables + - PROXY_TARGET: `/dicom-web` + - PROXY_DOMAIN: `http://localhost:8042` + - APP_CONFIG: `config/docker_nginx-orthanc.js` +- `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration + file. It will watch for changes and update as we develop. + +`PROXY_TARGET` and `PROXY_DOMAIN` tell our development server to proxy requests +to `Orthanc`. This allows us to bypass CORS issues that normally occur when +requesting resources that live at a different domain. + +The `APP_CONFIG` value tells our app which file to load on to `window.config`. +Here is what that +configuration looks like: + +```js title="/platform/viewer/public/config/default.js" +window.config = { + routerBasename: '/', + servers: { + dicomWeb: [ + { + name: 'Orthanc', + wadoUriRoot: 'http://localhost:8899/wado', + qidoRoot: 'http://localhost:8899/dicom-web', + wadoRoot: 'http://localhost:8899/dicom-web', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + }, + ], + }, +}; +``` + +To learn more about how you can configure the OHIF Viewer, check out our +[Configuration Guide](./index.md). + +## Open Source DICOM Image Archives + +Our example uses `Orthanc`, but there are a lot of options available to you. +Here are some of the more popular ones: + +| Archive | Installation | +| --------------------------------------------- | ---------------------------------- | +| [DCM4CHEE Archive 5.x][dcm4chee] | [W/ Docker][dcm4chee-docker] | +| [Orthanc][orthanc] | [W/ Docker][orthanc-docker] | +| [DICOMcloud][dicomcloud] (**DICOM Web only**) | [Installation][dicomcloud-install] | +| [OsiriX][osirix] (**Mac OSX only**) | Desktop Client | +| [Horos][horos] (**Mac OSX only**) | Desktop Client | + +_Feel free to make a Pull Request if you want to add to this list._ + + + + +[dcmjs-org]: https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[storescu]: http://support.dcmtk.org/docs/storescu.html +[webpack-proxy]: https://webpack.js.org/configuration/dev-server/#devserverproxy +[orthanc-docker-compose]: https://github.com/OHIF/Viewers/tree/master/.docker/Nginx-Orthanc + +[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light +[dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker +[orthanc]: https://www.orthanc-server.com/ +[orthanc-docker]: http://book.orthanc-server.com/users/docker.html +[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud +[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code +[osirix]: http://www.osirix-viewer.com/ +[horos]: https://www.horosproject.org/ + diff --git a/platform/docs/versioned_docs/version-2.0/configuring/index.md b/platform/docs/versioned_docs/version-2.0/configuring/index.md new file mode 100644 index 000000000..3d602fa40 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/configuring/index.md @@ -0,0 +1,124 @@ +--- +sidebar_position: 1 +--- + +# Configuration + +> This step assumes you have an imaging archive. If you need assistance setting +> one up, check out the [`Data Source` Guide](./data-source.md) or a deployment +> recipe that contains an open Image Archive + + + +### Configuration Files + +The configuration for our viewer is in the `platform/viewer/public/config` +directory. Our build process knows which configuration file to use based on the +`APP_CONFIG` environment variable. By default, its value is +[`config/default.js`][default-config]. The majority of the viewer's features, +and registered extension's features, are configured using this file. + +**Embedded Use Note:** + +Alternatively, when using the `umd` bundle for embedded use cases, these same +values are what you'll pass to `installViewer` method: + +`OHIFStandaloneViewer.installViewer(window.config)` + +### Environment Variables + +We use environment variables at build and dev time to change the Viewer's +behavior. We can update the `HTML_TEMPLATE` to easily change which extensions +are registered, and specify a different `APP_CONFIG` to connect to an +alternative data source (or even specify different default hotkeys). + +| Environment Variable | Description | Default | +| -------------------- | -------------------------------------------------------------------------------------------------- | ------------------- | +| `HTML_TEMPLATE` | Which [HTML template][html-templates] to use as our web app's entry point. Specific to PWA builds. | `index.html` | +| `PUBLIC_URL` | The route relative to the host that the app will be served from. Specific to PWA builds. | `/` | +| `APP_CONFIG` | Which [configuration file][config-file] to copy to output as `app-config.js` | `config/default.js` | +| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` | +| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` | + +## How do I configure my project? + +The simplest way is to update the existing default config: + + +```js title="/platform/viewer/public/config/default.js" +window.config = { + routerBasename: '/', + servers: { + dicomWeb: [ + { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + }, + ], + }, +}; +``` + +The configuration can also be written as a JS Function in case you need to inject dependencies like external services: + +```js +window.config = ({ servicesManager } = {}) => { + const { UIDialogService } = servicesManager.services; + return { + cornerstoneExtensionConfig: { + tools: { + ArrowAnnotate: { + configuration: { + getTextCallback: (callback, eventDetails) => UIDialogService.create({... + } + } + }, + }, + routerBasename: '/', + servers: { + dicomWeb: [ + { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + }, + ], + }, + }; +}; +``` + +You can also create a new config file and specify its path relative to the build +output's root by setting the `APP_CONFIG` environment variable. You can set the +value of this environment variable a few different ways: + +- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~ + - Previous `react-scripts` functionality that we need to duplicate with + `dotenv-webpack` +- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~ + - Previous `react-scripts` functionality that we need to duplicate with + `dotenv-webpack` +- Using the `cross-env` package in an npm script: + - `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"` + +After updating the configuration, `yarn run build` to generate updated build +output. + + + + +[default-config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js +[html-templates]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates +[config-files]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config + diff --git a/platform/docs/versioned_docs/version-2.0/deployment/_category_.json b/platform/docs/versioned_docs/version-2.0/deployment/_category_.json new file mode 100644 index 000000000..88be8d879 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/deployment/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Deployment", + "position": 8 +} diff --git a/platform/docs/versioned_docs/version-2.0/deployment/index.md b/platform/docs/versioned_docs/version-2.0/deployment/index.md new file mode 100644 index 000000000..995a48077 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/deployment/index.md @@ -0,0 +1,309 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- +# Deployment + +The OHIF Viewer can be embedded in other web applications via it's [packaged +script source][viewer-npm], or served up as a stand-alone PWA ([progressive web +application][pwa-url]) by building and hosting a collection of static assets. In +either case, you will need to configure your instance of the Viewer so that it +can connect to your data source (the database or PACS that provides the data +your Viewer will display). + + +Our goal is to make deployment as simple and painless as possible; however, +there is an inherent amount of complexity in configuring and deploying web +applications. If you find yourself a little lost, please don't hesitate to +[reach out for help](/help.md) + +## Deployment Scenarios + +### Embedded Viewer + +The quickest and easiest way to get the OHIF Viewer up and running is to embed +it into an existing web application. It allows us to forego a "build step", and +add a powerful medical imaging viewer to an existing web page using only a few +include tags. + +- Read more about it here: [Embedded Viewer](./recipes/embedded-viewer.md) +- And check out our [live demo on CodeSandbox][code-sandbox] + +![embeddedViewer](../assets/img/embedded-viewer-diagram.png) + + +### Stand-alone Viewer + +Deploying the OHIF Viewer as a stand-alone web application provides many +benefits, but comes at the cost of time and complexity. Some benefits include: + +_Today:_ + +- Leverage [extensions](/extensions/index.md) to drop-in powerful new features +- Add routes and customize the viewer's workflow +- Finer control over styling and whitelabeling + +_In the future:_ + +- The ability to package the viewer for [App Store distribution][app-store] +- Leverage `service-workers` for offline support and speed benefits from caching + +#### Hosted Static Assets + +At the end of the day, a production OHIF Viewer instance is a collection of +HTML, CSS, JS, Font Files, and Images. We "build" those files from our +`source code` with configuration specific to our project. We then make those +files publicly accessible by hosting them on a Web Server. + +If you have not deployed a web application before, this may be a good time to +[reach out for help](/help.md), as these steps assume prior web development and +deployment experience. + +##### Part 1 - Build Production Assets + +"Building", or creating, the files you will need is the same regardless of the +web host you choose. You can find detailed instructions on how to configure and +build the OHIF Viewer in our +["Build for Production" guide](./recipes/build-for-production.md). + +##### Part 2 - Host Your App + +There are a lot of [benefits to hosting static assets][host-static-assets] over +dynamic content. You can find instructions on how to host your build's output +via one of these guides: + +_Drag-n-drop_ + +- [Netlify: Drop](/deployment/recipes/static-assets.md#netlify-drop) + +_Easy_ + +- [Surge.sh](/deployment/recipes/static-assets.md#surgesh) +- [GitHub Pages](/deployment/recipes/static-assets.md#github-pages) + +_Advanced_ + +- [AWS S3 + Cloudfront](/deployment/recipes/static-assets.md#aws-s3--cloudfront) +- [GCP + Cloudflare](/deployment/recipes/static-assets.md#gcp--cloudflare) +- [Azure](/deployment/recipes/static-assets.md#azure) + +## Data + +The OHIF Viewer is able to connect to any data source that implements the [DICOM +Web Standard][dicom-web-standard]. [DICOM Web][dicom-web] refers to RESTful +DICOM Services -- a recently standardized set of guidelines for exchanging +medical images and imaging metadata over the internet. Not all archives fully +support it yet, but it is gaining wider adoption. + +### Configure Connection + +If you have an existing archive and intend to host the OHIF Viewer at the same +domain name as your archive, then connecting the two is as simple as following +the steps layed out in our +[Configuration Essentials Guide](./../configuring/index.md). + +#### What if I don't have an imaging archive? + +We provide some guidance on configuring a local image archive in our +[Data Source Essentials](./../configuring/data-source.md) guide. Hosting an +archive remotely is a little trickier. You can check out some of our +[advanced recipes](#recipes) for modeled setups that may work for you. + +#### What if I intend to host the OHIF Viewer at a different domain? + +There are two important steps to making sure this setup works: + +1. Your Image Archive needs to be exposed, in some way, to the open web. This + can be directly, or through a `reverse proxy`, but the Viewer needs _some + way_ to request it's data. +2. \* Your Image Archive needs to have appropriate CORS (Cross-Origin Resource + Sharing) Headers + +> \* Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional +> HTTP headers to tell a browser to let a web application running at one origin +> (domain) have permission to access selected resources from a server at a +> different origin. - [MDN Web Docs: Web - Http - CORS][cors] + +Most image archives do not provide either of these features "out of the box". +It's common to use IIS, Nginx, or Apache to route incoming requests and append +appropriate headers. You can find an example of this setup in our +[Nginx + Image Archive Deployment Recipe](./recipes/nginx--image-archive.md). + +#### What if my archive doesn't support DicomWeb? + +It's possible to supply all Study data via JSON format, in the event you do not have a DicomWeb endpoint. +You can host all of the relevant files on any web accessible server (Amazon S3, Azure Blob Storage, Local file server etc.) + +This JSON is supplied via the '?url=' query parameter. +It should reference an endpoint that returns **application/json** formatted text. + +If you do not have an API, you can simply return a text file containing the JSON from any web server. + + +You tell the OHIF viewer to use JSON by appending the `'?url='` query to the `/Viewer` route: + +eg. `https://my-test-ohif-server/viewer?url=https://my-json-server/study-uid.json` + +The returned JSON object must contain a single root object with a 'studies' array. + + +*Sample JSON format:* +```json +{ + "studies": [ + { + "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78", + "StudyDescription": "BRAIN SELLA", + "StudyDate": "20010108", + "StudyTime": "120022", + "PatientName": "MISTER^MR", + "PatientId": "832040", + "series": [ + { + "SeriesDescription": "SAG T-1", + "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", + "SeriesNumber": 2, + "SeriesDate": "20010108", + "SeriesTime": "120318", + "Modality": "MR", + "instances": [ + { + "metadata": { + "Columns": 512, + "Rows": 512, + "InstanceNumber": 3, + "AcquisitionNumber": 0, + "PhotometricInterpretation": "MONOCHROME2", + "BitsAllocated": 16, + "BitsStored": 16, + "PixelRepresentation": 1, + "SamplesPerPixel": 1, + "PixelSpacing": [0.390625, 0.390625], + "HighBit": 15, + "ImageOrientationPatient": [0,1,0,0,0,-1], + "ImagePositionPatient": [11.600000,-92.500000, 98.099998], + "FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470", + "ImageType": ["ORIGINAL","PRIMARY","OTHER"], + "Modality": "MR", + "SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124", + "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", + "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78" + }, + "url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm" + } + ] + } + ] + } + ] +} +``` +More info on this JSON format can be found here [Issue #1500](https://github.com/OHIF/Viewers/issues/1500) + + +**Implementation Notes:** + +1. When hosting the viewer, you will also need to host a /viewer route on the server - or the browser may not be able to find the route. +2. For each instance url (dicom object) in the returned JSON, you must prefix the `url` with `dicomweb:` in order for the cornerstone image loader to retrieve it correctly. + eg. `https://image-server/my-image.dcm` ---> `dicomweb:https://image-server/my-image.dcm` +3. The JSON format above is compatible with >= v3.7.8 of the application. Older versions of the viewer used a different JSON format. As of 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does not support this format yet. +4. The JSON format is case-sensitive. Please ensure you have matched casing with the naturalised Dicom format referenced in [Issue #1500](https://github.com/OHIF/Viewers/issues/1500). + +*CORS Issues (Cross-Origin Resource Sharing)* + +If you host a JSON API or Images on a different domain from the the app itself, you will likely have CORS issues. This will also happen when testing from Localhost and reaching out to remote servers. +Even if the domain is the same, different ports, subdomains or protocols (https vs http) will also cause CORS errors. +You will to need add a configuration on each server hosting these assets to allow your App server origin. + +For example: + +Lets assume your application is hosted on `https://my-ohif-server.com`. + +Your JSON API is hosted on `https://my-json-api.aws.com` + +And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com` + +When you first start your application, browsing to `https://my-ohif-server.com/viewer?url=https://my-json-api.aws.com/api/my-json-study-info.json`, you will likely get a CORS error in the browser console as it tries to connect to `https://my-json-api.aws.com`. + +Adding a setting on the JSON server to allow the CORS origin = `https://my-ohif-server.com` should solve this. + +Next, you will likely get a similar CORS error, as the browser tries to go to `https://my-s3-bucket.aws.com`. +You will need to go to the S3 bucket configuration, and add a CORS setting to allow origin = `https://my-ohif-server.com`. + +Essentially, whenever the application connects to a remote resource, you will need to add the applications url to the allowed CORS Origins on that resource. Adding an origin similar to https://localhost:3000 will also allow for local testing. + + +### Securing Your Data + +> Feeling lost? Securing your data is important, and it can be hard to tell if +> you've gotten it right. Don't hesitate to work with professional auditors, or +> [enlist help from experts](./../help.md). + +The OHIF Viewer can be configured to work with authorization servers that +support one or more of the OpenID-Connect authorization flows. The Viewer finds +it's OpenID-Connect settings on the `oidc` configuration key. You can set these +values following the instructions laid out in the +[Configuration Essentials Guide](./../configuring/index.md). + +_Example OpenID-Connect Settings:_ + +```js +window.config = { + ... + oidc: [ + { + // ~ REQUIRED + // Authorization Server URL + authority: 'http://127.0.0.1/auth/realms/ohif', + client_id: 'ohif-viewer', + redirect_uri: 'http://127.0.0.1/callback', // `OHIFStandaloneViewer.js` + response_type: 'code', // "Authorization Code Flow" + scope: 'openid', // email profile openid + // ~ OPTIONAL + post_logout_redirect_uri: '/logout-redirect.html', + }, + ], +} +``` + +You can find an example of this setup in our +[User Account Control Deployment Recipe](./recipes/user-account-control.md). + +#### Choosing a Flow for the Viewer + +In general, we recommend using the "Authorization Code Flow" ( [see +`response_type=code` here][code-flows]); however, the "Implicit Flow" ( [see +`response_type=token` here][code-flows]) can work if additonal precautions are +taken. If the flow you've chosen produces a JWT Token, it's validity can be used +to secure access to your Image Archive as well. + +### Recipes + +We've included a few recipes for common deployment scenarios. There are many, +many possible configurations, so please don't feel limited to these setups. +Please feel free to suggest or contribute your own recipes. + +- Script Include + - [Embedding the Viewer](./recipes/embedded-viewer.md) +- Stand-Alone + - [Build for Production](./recipes/build-for-production.md) + - [Static](./recipes/static-assets.md) + - [Nginx + Image Archive](./recipes/nginx--image-archive.md) + - [User Account Control](./recipes/user-account-control.md) + + + + +[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer +[pwa-url]: https://developers.google.com/web/progressive-web-apps/ +[static-assets-url]: https://www.maxcdn.com/one/visual-glossary/static-content/ +[app-store]: https://medium.freecodecamp.org/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned-7cb3f56daf9b +[dicom-web-standard]: https://www.dicomstandard.org/dicomweb/ +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[host-static-assets]: https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/ +[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +[code-flows]: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660 +[code-sandbox]: https://codesandbox.io/s/viewer-script-tag-tprch + diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json b/platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json new file mode 100644 index 000000000..5b36cc15d --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Recipes", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md b/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md new file mode 100644 index 000000000..7ba3bb64b --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md @@ -0,0 +1,138 @@ +--- +sidebar_position: 2 +--- +# Build for Production + +> If you've already followed the +> ["Getting Started" Guide](/development/getting-started.md), you can skip ahead +> to [Configuration](#configuration) + +## Overview + +### Build Machine Requirements + +- [Node.js & NPM](https://nodejs.org/en/download/) +- [Yarn](https://yarnpkg.com/lang/en/docs/install/) +- [Git](https://www.atlassian.com/git/tutorials/install-git) + +### Getting the Code + +_With Git:_ + +```bash +# Clone the remote repository to your local machine +git clone https://github.com/OHIF/Viewers.git +``` + +More on: _[`git clone`](https://git-scm.com/docs/git-clone), +[`git checkout`](https://git-scm.com/docs/git-checkout)_ + +_From .zip:_ + +[OHIF/Viewers: react.zip](https://github.com/OHIF/Viewers/archive/master.zip) + +### Restore Dependencies & Build + +Open your terminal, and navigate to the directory containing the source files. +Next run these commands: + +```bash +// If you haven't already, enable yarn workspaces +yarn config set workspaces-experimental true + +// Restore dependencies +yarn install + +// Build source code for production +yarn run build +``` + +If everything worked as expected, you should have a new `dist/` directory in the +project's folder. It should roughly resemble the following: + +```bash +platform/viewer/dist/ +├── app-config.js +├── app.bundle.js +├── app.css +├── index.html +├── manifest.json +├── service-worker.js +└── ... +``` + +By default, the build output will connect to OHIF's publicly accessible PACS. If +this is your first time setting up the OHIF Viewer, it is recommended that you +test with these default settings. After testing, you can find instructions on +how to configure the project for your own imaging archive below. + +### Configuration + +The configuration for our viewer is in the `platform/viewer/public/config` +directory. Our build process knows which configuration file to use based on the +`APP_CONFIG` environment variable. By default, its value is +[`config/default.js`][default-config]. The majority of the viewer's features, +and registered extension's features, are configured using this file. + +The easiest way to apply your own configuration is to modify the `default.js` +file. For more advanced cofiguration options, check out our +[configuration essentials guide](/configuring/index.md). + +## Next Steps + +### Deploying Build Output + +_Drag-n-drop_ + +- [Netlify: Drop](/deployment/recipes/static-assets.md#netlify-drop) + +_Easy_ + +- [Surge.sh](/deployment/recipes/static-assets.md#surgesh) +- [GitHub Pages](/deployment/recipes/static-assets.md#github-pages) + +_Advanced_ + +- [AWS S3 + Cloudfront](/deployment/recipes/static-assets.md#aws-s3--cloudfront) +- [GCP + Cloudflare](/deployment/recipes/static-assets.md#gcp--cloudflare) +- [Azure](/deployment/recipes/static-assets.md#azure) + +### Testing Build Output Locally + +A quick way to test your build output locally is to spin up a small webserver. +You can do this by running the following commands in the `dist/` output +directory: + +```bash +// Install http-server as a globally available package +yarn global add http-server + +// Serve the files in our current directory +// Accessible at: `http://localhost:8080` +http-server +``` + +### Automating Builds and Deployments + +If you found setting up your environmnent and running all of these steps to be a +bit tedious, then you are in good company. Thankfully, there are a large number +of tools available to assist with automating tasks like building and deploying +web application. For a starting point, check out this repository's own use of: + +- [CircleCI][circleci]: [config.yaml][circleci-config] +- [Netlify][netlify]: [netlify.toml][netlify.toml] | + [build-deploy-preview.sh][build-deploy-preview.sh] + +## Troubleshooting + +> Issues and resolutions for common GitHub issues will be summarized here + +... + + +[circleci]: https://circleci.com/gh/OHIF/Viewers +[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml +[netlify]: https://app.netlify.com/sites/ohif/deploys +[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml +[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh + diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md b/platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md new file mode 100644 index 000000000..07dc7502e --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md @@ -0,0 +1,174 @@ +--- +sidebar_position: 1 +--- +# Embedded Viewer + +The quickest and easiest way to get the OHIF Viewer up and running is to embed +it into an existing web application. It allows us to forego a "build step", and +add a powerful medical imaging viewer to an existing web page using only a few +include tags. Here's how it works: + +{% include "./../_embedded-viewer-diagram.md" %} + +1. Create a new web page or template that includes the following external + dependencies: + + + +
    +
  1. Create a JS Object or Function to hold the OHIF Viewer's configuration. Here are some + example values that would allow the viewer to hit our public PACS:
  2. +
+ +```js +// Set before importing `ohif-viewer` (JS Object) +window.config = { + // default: '/' + routerBasename: '/', + servers: { + dicomWeb: [ + { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + }, + ], + }, +}; +``` + +To learn more about how you can configure the OHIF Viewer, check out our +[Configuration Guide](../../configuring/index.md). + +
  1. + Render the viewer in the web page's target div +
+ +```js +// Made available by the `@ohif/viewer` script included in step 1 +var containerId = 'id-of-div-to-render-component-to'; +var componentRenderedOrUpdatedCallback = function() { + console.log('OHIF Viewer rendered/updated'); +}; +window.OHIFViewer.installViewer( + window.config, + containerId, + componentRenderedOrUpdatedCallback +); +``` + +You can see a live example of this recipe in [this CodeSandbox][code-sandbox]. + +## Add Extensions + +The UMD build of the OHIF Viewer is a "light weight" build that only contains +the core extensions required for basic 2D image viewing. It's possible to add +other extensions at runtime. + +This only requires us to include a single script tag, and add it using the +`extensions` key to our config. In this practical example, we register our +popular whole slide microscopy extension: + +```html + + + + +``` + +You can see an example of a slide microscopy study in the viewer [with the +extension enabled here][whole-slide-ext-demo] ([source code][ext-code-sandbox]) +and [without it here][whole-slide-base-demo] ([source code][code-sandbox]). + +You can read more about extensions and how to create your own in our +[extensions guide](/extensions/index.md). + +#### FAQ + +> I'm having trouble getting this to work. Where can I go for help? + +First, check out this fully functional [CodeSandbox][code-sandbox] example. If +you're still having trouble, feel free to search or GitHub issues. Can't find +anything related your problem? Create a new one. + +> My application's styles are impacting the OHIF Viewer's look and feel. What +> can I do? + +When you include stylesheets and scripts, they are added globally. This has the +potential of causing conflicts with other scripts and styles on the page. To +prevent this, `embed` the viewer in a new/empty web page. Have that working? +Good. Now `embed` that new page using an +[` + + + + +### Building for Production + +> More comprehensive guides for building and publishing can be found in our +> [deployment docs](./../deployment/index.md) + +```bash +# Build static assets to host a PWA +yarn run build + +# Build packaged output (script-tag use) +yarn run build:package +``` + +## Troubleshooting + +- If you receive a _"No Studies Found"_ message and do not see your studies, try + changing the Study Date filters to a wider range. +- If you see a 'Loading' message which never resolves, check your browser + JavaScript console inside the Developer Tools to identify any errors. + + + + +[fork-a-repo]: https://help.github.com/en/articles/fork-a-repo +[clone-a-repo]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork +[add-remote-repo]: https://help.github.com/en/articles/fork-a-repo#step-3-configure-git-to-sync-your-fork-with-the-original-spoon-knife-repository +[sync-changes]: https://help.github.com/en/articles/syncing-a-fork +[triangular-workflow]: https://github.blog/2015-07-29-git-2-5-including-multiple-worktrees-and-triangular-workflows/#improved-support-for-triangular-workflows +[ohif-viewers-repo]: https://github.com/OHIF/Viewers +[ohif-viewers]: https://github.com/OHIF/Viewers + diff --git a/platform/docs/versioned_docs/version-2.0/development/testing.md b/platform/docs/versioned_docs/version-2.0/development/testing.md new file mode 100644 index 000000000..4ca92dcbd --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/development/testing.md @@ -0,0 +1,149 @@ +--- +sidebar_position: 4 +title: Testing +--- +# Tests + +> Testing is an opinionated topic. Here is a rough overview of our testing +> philosiphy. See something you want to discuss or think should be changed? Open +> a PR and let's discuss. + +You're an engineer. You know how to write code, and writing tests isn't all that +different. But do you know why we write tests? Do you know when to write one, or +what kind of test to write? How do you know if a test is a _"good"_ test? This +document's goal is to give you the tools you need to make those determinations. + +Okay. So why do we write tests? To increase our... **CONFIDENCE** + +- If I do a large refactor, does everything still work? +- If I changed some critical piece of code, is it safe to push to production? + +Gaining the confidence we need to answer these questions after every change is +costly. Good tests allow us to answer them without manual regression testing. +What and how we choose to test to increase that confidence is nuanced. + +## Kinds of Tests + +Test's buy us confidence, but not all tests are created equal. Each kind of test +has a different cost to write and maintain. An expensive test is worth it if it +gives us confidence that a payment is processed, but it may not be the best +choice for asserting an element's border color. + +| Test Type | Example | Speed | Cost | +| ----------- | ------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------ | +| Static | `addNums(1, '2')` called with `string`, expected `int`. | :rocket: Instant | :money_with_wings: | +| Unit | `addNums(1, 2)` returns expected result `3` | :airplane: Fast | :money_with_wings::money_with_wings: | +| Integration | Clicking "Sign In", navigates to the dashboard (mocked network requests) | :running: Okay | :money_with_wings::money_with_wings::money_with_wings: | +| End-to-end | Clicking "Sign In", navigates to the dashboard (no mocks) | :turtle: Slow | :money_with_wings::money_with_wings::money_with_wings::money_with_wings: | + +- :rocket: Speed: How quickly tests run +- :money_with_wings: Cost: Time to write, and to debug when broken (more points + of failure) + +### Static Code Analysis + +Modern tooling gives us this "for free". It can catch invalid regular +expressions, unused variables, and guarantee we're calling methods/functions +with the expected paramater types. + +Example Tooling: + +- [ESLint][eslint-rules] +- [TypeScript][typescript-docs] or [Flow][flow-org] + +### Unit Tests + +The building blocks of our libraries and applications. For these, you'll often +be testing a single function or method. Conceptually, this equates to: + +_Pure Function Test:_ + +- If I call `sum(2, 2)`, I expect the output to be `4` + +_Side Effect Test:_ + +- If I call `resetViewport(viewport)`, I expect `cornerstone.reset` to be called + with `viewport` + +#### When to use + +Anything that is exposed as public API should have unit tests. + +#### When to avoid + +You're actually testing implementation details. You're testing implementation +details if: + +- Your test does something that the consumer of your code would never do. + - IE. Using a private function +- A refactor can break your tests + +### Integration Tests + +We write integration tests to gain confidence that several units work together. +Generally, we want to mock as little as possible for these tests. In practice, +this means only mocking network requests. + +#### When to use + +... + +### End-to-End Tests + +These are the most expensive tests to write and maintain. Largely because, when +they fail, they have the largest number of potential points of failure. So why +do we write them? Because they also buy us the most confidence. + +#### When to use + +Mission critical features and functionality, or to cover a large breadth of +functionality until unit tests catch up. Unsure if we should have a test for +feature `X` or scenario `Y`? Open an issue and let's discuss. + +## Summary + +- Does your test increase confidence? +- Does the test type chosen balance the cost-to-confidence ratio? + +## Further Reading + +### General + +- [Assert(js) Conf 2018 Talks][assert-js-talks] + - [Write tests. Not too many. Mostly integration.][kent-talk] - Kent C. Dodds + - [I see your point, but…][gleb-talk] - Gleb Bahmutov +- [Static vs Unit vs Integration vs E2E Testing][kent-blog] - Kent C. Dodds + (Blog) + +### End-to-end Testing w/ Cypress + +- [Getting Started](https://docs.cypress.io/guides/overview/why-cypress.html) + - Be sure to check out `Getting Started` and `Core Concepts` +- [Best Practices](https://docs.cypress.io/guides/references/best-practices.html) +- [Example Recipes](https://docs.cypress.io/examples/examples/recipes.html) + +## Testing Dorito + +[![testing dorito][testing-dorito-img]][testing-dorito] + + + + +[eslint-rules]: https://eslint.org/docs/rules/ +[typescript-docs]: https://www.typescriptlang.org/docs/home.html +[flow-org]: https://flow.org/ + +[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw +[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE +[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk +[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests + +[testing-trophy]: https://twitter.com/kentcdodds/status/960723172591992832?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E960723172591992832&ref_url=https%3A%2F%2Fkentcdodds.com%2Fblog%2Fwrite-tests +[aaron-square]: https://twitter.com/Carofine247/status/966727489274961920 +[gleb-pyramid]: https://twitter.com/Carofine247/status/966764532046684160/photo/3 +[testing-pyramid]: https://dojo.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid +[testing-dorito]: https://twitter.com/denvercoder/status/960752578198843392 +[testing-dorito-img]: https://pbs.twimg.com/media/DVVHXycUMAAcN-F?format=jpg&name=4096x4096 + diff --git a/platform/docs/versioned_docs/version-2.0/extensions/_category_.json b/platform/docs/versioned_docs/version-2.0/extensions/_category_.json new file mode 100644 index 000000000..51dd48da0 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Extensions", + "position": 6 +} diff --git a/platform/docs/versioned_docs/version-2.0/extensions/index.md b/platform/docs/versioned_docs/version-2.0/extensions/index.md new file mode 100644 index 000000000..0f4c12221 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/index.md @@ -0,0 +1,304 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- +# Overview + + + +We use extensions to help us isolate and package groups of related features. +Extensions provide functionality, ui components, and new behaviors. Ideally, +they're built in a way that allows them to extend entirely different +implementations of the `@ohif/viewer` project. + + + +![extensions](../assets/img/extensions-diagram.png) + + +The `@ohif/viewer`'s application level configuration gives us the ability to add +and configure extensions. When the application starts, extensions are registered +with the `ExtensionManager`. Different portions of the `@ohif/viewer` project +will use registered extensions to influence application behavior. + +Extensions allow us to: + +- Wrap and integrate functionality of 3rd party dependencies in a reusable way +- Change how application data is mapped and transformed +- Display a consistent/cohesive UI +- Inject custom components to override built-in components + +Practical examples of extensions include: + +- A set of segmentation tools that build on top of the `cornerstone` viewport +- Showing ML/AI report summaries for the selected study/series/image +- Support for parsing DICOM structured reports and displaying them in a user + friendly way +- [See our maintained extensions for more examples of what's possible](#maintained-extensions) + +## Concepts + +### Extension Skeleton + +An extension is a plain JavaScript object that has an `id` property, and one or +more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks). + +```js +// prettier-ignore +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'example-extension', + + // Lifecyle + preRegistration() { /* */ }, + // Modules + getCommandsModule() { /* */ }, + getToolbarModule() { /* */ }, + getPanelModule() { /* */ }, + getSopClassHandler() { /* */ }, + getViewportModule() { /* */ }, +} +``` + +### Registering an Extension + +There are two different ways to register and configure extensions: At +[runtime](#registering-at-runtime) and at +[build time](#registering-at-build-time). + +You can leverage one or both strategies. Which one(s) you choose depend on your +application's requirements. Each [module](#modules) defined by the extension +becomes available to the core application via the `ExtensionManager`. + +#### Registering at Runtime + +The `@ohif/viewer` uses a [configuration file](../viewer/configuration.md) at +startup. The schema for that file includes an `Extensions` key that supports an +array of extensions to register. + +```js +// prettier-ignore +const config = { + extensions: [ + MyFirstExtension, + [ + MySecondExtension, + { /* MySecondExtensions Configuration */ }, + ], + ]; +} +``` + +#### Registering at Build Time + +The `@ohif/viewer` works best when built as a "Progressive Web Application" +(PWA). If you know the extensions your application will need, you can specify +them at "build time" to leverage advantages afforded to us by modern tooling: + +- Code Splitting (dynamic imports) +- Tree Shaking +- Dependency deduplication + +You can update the list of bundled extensions by: + +1. Having your `@ohif/viewer` project depend on the extension +2. Importing and adding it to the list of extensions in the + `/platform/src/index.js` entrypoint. + +### Lifecycle Hooks + +Currently, there is only a single lifecycle hook for extensions: +[`preRegistration`](./lifecycle/pre-registration.md) + +If an extension defines the [`preRegistration`](./lifecycle/pre-registration.md) +lifecycle hook, it is called before any modules are registered in the +`ExtensionManager`. It's most commonly used to wire up extensions to +[services](./../services/index.md) and [commands](./modules/commands.md), and to +bootstrap 3rd party libraries. + +### Modules + +Modules are the meat of extensions. They provide "definitions", components, and +filtering/mapping logic that are then made available by various managers and +services. + +Each module type has a special purpose, and is consumed by our viewer +differently. + +| Type | Description | Examples | +| ------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- | +| [Commands](./modules/commands.md) | Adds named commands, scoped to a context, to the CommandsManager | `setToolActive()`, `nextSeries()` | +| [Panel](./modules/panel.md) | Adds left or right hand side panels | ``, `` | +| [SOPClassHandler](./modules/sop-class-handler.md) | Determines how retrieved study data is split into "DisplaySets" | `getDisplaySetFromSeries()` | +| [Toolbar](./modules/toolbar.md) | Adds buttons or custom components to the toolbar | Toolbar button, nested buttons, custom | +| [Viewport](./modules/viewport.md) | Adds a component responsible for rendering a "DisplaySet" | ``, `` | + +Tbl. Module types with abridged descriptions and examples. Each module links to a dedicated documentation page. + +### Contexts + +The `@ohif/viewer` tracks "active contexts" that extensions can use to scope +their functionality. Some example contexts being: + +- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST` +- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK` + +An extension module can use these to say "Only show this Toolbar Button if the +active viewport is a Cornerstone viewport." This helps us use the appropriate UI +and behaviors depending on the current contexts. + +For example, if we have hotkey that "rotates the active viewport", each Viewport +module that supports this behavior can add a command with the same name, scoped +to the appropriate context. When the `command` is fired, the "active contexts" +are used to determine the appropriate implementation of the rotate behavior. + +## Consuming Extensions + +We consume extensions, via the `ExtensionManager`, in our `@ohif/viewer` +project. + +```js +const extensionManager = new ExtensionManager({ + commandsManager, + servicesManager, + hotkeysManager +}); + +// prettier-ignore +extensionManager.registerExtensions([ /** **/ ]); +``` + +The `@ohif/viewer` project handles data fetching, basic routing, wires up UI +services, and is the home to the more bespoke application logic that doesn't +make as much sense to make reusable. + +Long-term, replacing the `@ohif/viewer` application and consuming extensions +(and the `ExtensionManager`) in your own project is the ideal path for +applications requiring a high degree of customization that can't be achieved +with current theming, configuration, extension, and services support. + +If you're not sure how to achieve your goals with the extensibility available +today, create a GitHub issue! + +### `ExtensionManager` + +The `ExtensionManager` is a class made available to us via the `@ohif/core` +project (platform/core). Our application instantiates a single instance of it, +and provides a `ServicesManager` and `CommandsManager` along with the +application's configuration through the appConfig key (optional). + +```js +const commandsManager = new CommandsManager(); +const servicesManager = new ServicesManager(); +const extensionManager = new ExtensionManager({ + commandsManager, + servicesManager, + appConfig, +}); +``` + +The `ExtensionManager` only has a few public members: + +- `registerExtension` - Registers a single extension +- `registerExtensions` - Registers an array of extensions +- `modules` - An object containing registered extensions by `MODULE_TYPE` + +During registration, lifecycle hooks and modules have access to the extension's +config, the application's config and `ExtensionManager`'s `ServicesManager` and +`CommandsManager` instances. + +Our `@ohif/viewer` uses the `modules` member to access registered extensions at +appropriate places in our application. + +## Maintained Extensions + +A small number of powerful extensions for popular use cases are maintained by +OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in +the top level [`extensions/`][ext-source] directory. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionDescriptionModules
+ + Cornerstone + + + A viewport powered by cornerstone.js. Adds support for 2D DICOM rendering and manipulation, as well as support for the tools features in cornerstone-tools. Also adds "CINE Dialog" to the Toolbar. + Viewport, Toolbar
+ + VTK.js + + + A viewport powered by vtk.js. Adds support for volume renderings and advanced features like MPR. Also adds "3D Rotate" to the Toolbar. + Viewport, Toolbar
+ DICOM HTML + + Renders text and HTML content for specific SopClassUIDs. + Viewport, SopClassHandler
+ DICOM PDF + + Renders PDFs for a specific SopClassUID. + Viewport, SopClassHandler
+ DICOM Microscopy + + Renders Microscopy images for a specific SopClassUID. + Viewport, SopClassHandler
+ DICOM Segmentation + + Renders segmentation images for a specific SopClassUID. + Panel, Toolbar
+ + + + +[viewers-repo]: https://github.com/OHIF/Viewers +[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions +[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js + diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json b/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json new file mode 100644 index 000000000..2528dd5e9 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "LifeCycle", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md b/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md new file mode 100644 index 000000000..cf2016779 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md @@ -0,0 +1,44 @@ +--- +sidebar_position: 1 +sidebar_label: Pre Registration +--- +# Lifecylce Hook: preRegistration + +If an extension defines the `preRegistration` lifecycle hook, it is called +before any modules are registered in the `ExtensionManager`. This hook can be +used to: + +- initialize 3rd party libraries +- register event listeners +- add or call services +- add or call commands + +The `preRegistration` hook receives an object containing the +`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any +`configuration` that was provided with the extension at time of registration. + +_Example `preRegistration` hook implementation_ + +```js +export default { + id: 'MyExampleExtension', + + /** + * @param {object} params + * @param {object} params.configuration + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + * @returns void + */ + preRegistration({ servicesManager, commandsManager, configuration }) { + console.log('Wiring up important stuff.'); + + window.importantStuff = () => { + console.log(configuration); + }; + + console.log('Important stuff has been wired.'); + window.importantStuff(); + }, +}; +``` diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json new file mode 100644 index 000000000..c131ccdd7 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Modules", + "position": 3 +} diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md b/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md new file mode 100644 index 000000000..3f9e864a9 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md @@ -0,0 +1,156 @@ +--- +sidebar_position: 1 +sidebar_label: Commands +--- +# Module: Commands + + +## Overview + +An extension can register a Commands Module by defining a `getCommandsModule` +method. The Commands Module allows us to register one or more commands scoped to +specific [contexts](./../index.md#contexts). Commands have several unique +characteristics that make them tremendously powerful: + +- Multiple implementations for the same command can be defined +- Only the correct command's implementation will be run, dependent on the + application's "context" +- Commands can be called from extensions, modules, and the consuming application + +Here is a simple example commands module: + +```js +export default { + id: 'example-commands-module', + + /** + * @param {object} params + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + */ + getCommandsModule({ servicesManager, commandsManager }) { + return { + definitions: { + sayHello: { + commandFn: ({ words }) => { + console.log(words); + }, + options: { words: 'Hello!' }, + }, + }, + defaultContext: 'VIEWER', + }; + }, +}; +``` + +Each definition returned by the Commands Module is registered to the +`ExtensionManager`'s `CommandsManager`. + +## Command Definitions + +The command definition consists of a named command (`myCommandName` below) and a +`commandFn`. The command name is used to call the command, and the `commandFn` +is the "command" that is actioned. + +```js +myCommandName: { + commandFn: ({ viewports, other, options }) => { }, + storeContexts: ['viewports'], + options: { words: 'Just kidding! Goodbye!' }, + context: 'ACTIVE_VIEWPORT::CORNERSTONE', +} +``` + +| Property | Type | Description | +| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | +| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. | +| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | +| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | + +## Command Behavior + +**I have many similar commands. How can I share their `commandFn` and make it +reusable?** + +This is where `storeContexts` and `options` come in. We use these in our +`setToolActive` command. `storeContexts` helps us identify our `activeViewport`, +and `options` allow us to pass in the name of a tool we would like to set as +active. + +**If there are multiple valid commands for the application's active contexts** + +- What happens: all commands are run +- When to use: A `clearData` command that cleans up state for multiple + extensions + +**If no commands are valid for the application's active contexts** + +- What happens: a warning is printed to the console +- When to use: a `hotkey` (like "invert") that doesn't make sense for the + current viewport (PDF or HTML) + +## `CommandsManager` + +The `CommandsManager` is a class defined in the `@ohif/core` project. A single +instance of it should be defined in the consuming application, and it should be +used when constructing the `ExtensionManager`. + +### Instantiating + +When we instantiate the `CommandsManager`, we need to pass it two methods: + +- `getAppState` - Should return the application's state when called +- `getActiveContexts` - Should return the application's active contexts when + called + +These methods are used internally to help determine which commands are currently +valid, and how to provide them with any state they may need at the time they are +called. + +```js +const commandsManager = new CommandsManager({ + getAppState, + getActiveContexts, +}); +``` + +### Public API + +If you would like to run a command in the consuming app or an extension, you can +use one of the following methods: + +```js +// Returns all commands for a given context +commandsManager.getContext('string'); + +// Attempts to run a command +commandsManager.runCommand('speak', { command: 'hello' }); + +// Run command, but override the active contexts +commandsManager.runCommand('speak', { command: 'hello' }, ['VIEWER']); +``` + +The `ExtensionManager` handles registering commands and creating contexts, so +most consumer's won't need these methods. If you find yourself using these, ask +yourself "why can't I register these commands via an extension?" + +```js +// Used by the `ExtensionManager` to register new commands +commandsManager.registerCommand('context', 'name', commandDefinition); + +// Creates a new context; clears the context if it already exists +commandsManager.createContext('string'); +``` + +### Contexts + +It is up to the consuming application to define what contexts are possible, and +which ones are currently active. As extensions depend heavily on these, we will +likely publish guidance around creating contexts, and ways to override extension +defined contexts in the near future. If you would like to discuss potential +changes to how contexts work, please don't hesistate to createa new GitHub +issue. + +[Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md b/platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md new file mode 100644 index 000000000..8079c5731 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 2 +sidebar_label: Panel +--- +# Module: Panel + +An extension can register a Panel Module by defining a `getPanelModule` method. +The panel module provides the ability to define `menuOptions` and `components` +that can be used by the consuming application. `components` are React Components +that can be displayed in the consuming application's "Panel" Component. + +![Panel Extension](../../assets/img/extensions-panel.gif) + +
A panel extension example
+ +The `menuOptions`'s `target` key points to a registered `components`'s `id`. A +`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can +optional provide it's own `context` value. + +The `getPanelModule` receives an object containing the `ExtensionManager`'s +associated `ServicesManager` and `CommandsManager`. + +```js +import MyComponent from './MyComponent.js'; + +export default { + id: 'example-panel-module', + + /** + * @param {object} params + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + */ + getPanelModule({ servicesManager, commandsManager }) { + return { + menuOptions: [ + { + // A suggested icon + // Available icons determined by consuming app + icon: 'list', + // A suggested label + label: 'Magic', + // 'right' or 'left' + from: 'right', + // The target component to toggle open/close + target: 'target-component-id', + // UI Hint; If the target panel is in a "disabled" state + isDisabled: studies => { + return false; + }, + // Overrides `defaultContext`, if specified + context: ['ACTIVE_VIEWPORT:MAGIC'], + }, + ], + components: [ + { + id: 'target-component-id', + component: MyComponent, + }, + ], + defaultContext: ['ROUTE:VIEWER'], + }; + }, +}; +``` diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md new file mode 100644 index 000000000..c6763e400 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md @@ -0,0 +1,109 @@ +--- +sidebar_position: 3 +sidebar_label: SOP Class Handler +--- +# Module: SOP Class Handler + +An extension can register a [SOP Class][sop-class-link] Handler Module by +defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link] +Handler is a bit different from the other modules, as it doesn't provide a `1:1` +schema for UI or provide it's own components. It instead defines: + +- `sopClassUIDs`: an array of string SOP Class UIDs that the + `getDisplaySetFromSeries` method should be applied to. +- `getDisplaySetFromSeries`: a method that maps series and study metadata to a + display set + +A `displaySet` has the following shape: + +```js +return { + plugin: 'html', + Modality: 'SR', + displaySetInstanceUID: 0, + wadoRoot: study.getData().wadoRoot, + wadoUri: instance.getData().wadouri, + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), + authorizationHeaders, +}; +``` + +Where the `plugin` key is used to influence the default `ViewportComponent` for +rendering the `displaySet`. Additional properties are passed to the +`ViewportComponent` and used by the default `StudyBrowser` to render +"thumbnails" for each `displaySet` + +## Example SOP Class Handler Module + +```js +const SOP_CLASS_UIDS = { + BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11', + ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22', +}; + +export default { + id: 'example-sop-class-handler-module', + +/** + * @param {object} params + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + */ +getSopClassHandlerModule({ servicesManager, commandsManager }) { + return { + id: 'OHIFDicomHtmlSopClassHandler', + sopClassUIDs: Object.values(SOP_CLASS_UIDS), + + /** + * @param {object} series - + * @param {object} study - + * @param {object} dicomWebClient - + * @param {object} authorizationHeaders - + */ + getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { + const instance = series.getFirstInstance(); + + return { + plugin: 'html', + displaySetInstanceUID: 0, + wadoRoot: study.getData().wadoRoot, + wadoUri: instance.getData().wadouri, + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), + authorizationHeaders, + }; + }, + } +}; +``` + +### More examples : + +- [Dicom-HTML SOP][dicom-html-sop] +- [Dicom-PDF SOP][dicom-pdf-sop] +- [Dicom-Microscopy SOP][dicom-micro-sop] +- [Dicom-Segmentation SOP][dicom-seg-sop] + +## `@ohif/viewer` usage + +We use the `sopClassHandlerModule`s in three different places: + +- `ViewerLocalFileData.js` +- `ViewerRetrieveStudyData.js` +- `StandaloneRouting.js` + +Each time, it is used to map study and series data to `displaySets`. It does +this by working alongside the `StudyMetadataManager` in `@ohif/core`. That +manager has the method `createDisplaySets` that takes an array of +`sopClassHandlerModules`. + + +[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html +[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12 +[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6 +[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7 +[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7 + diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md new file mode 100644 index 000000000..87e7050a8 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md @@ -0,0 +1,136 @@ +--- +sidebar_position: 4 +sidebar_label: Toolbar +--- +# Module: Toolbar + +An extension can register a Toolbar Module by defining a `getToolbarModule` +method. This module is commonly used to define: + +- [Module: Toolbar](#module-toolbar) + - [Example Toolbar Module](#example-toolbar-module) + - [Button Definitions](#button-definitions) + - [Nested Toolbar Menus](#nested-toolbar-menus) + - [Custom Components](#custom-components) + +![Toolbar Extension](../../assets/img/extensions-toolbar.gif) + +
Example toolbar button using the Dialog Service to show CINE controls.
+ +## Example Toolbar Module + +The Toolbar Module should return an array of `definitions` and a +`defaultContext`. There are currently a few different variations of definitions, +each one is detailed further down. + +```js +export default { + id: 'example-toolbar-module', + + /** + * @param {object} params + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + */ + getToolbarModule({ servicesManager, commandsManager }) { + return { + definitions: [ + /* Array of definitions */ + ], + defaultContext: ['ROUTE:VIEWER'], + }; + }, +}; +``` + +## Button Definitions + +The simplest definition has the following properties: + +```js +{ + id: 'StackScroll', + label: 'Stack Scroll', + icon: 'bars', + type: 'setToolActive', + commandName: 'setToolActive', + commandOptions: { toolName: 'StackScroll' }, +}, +``` + +| property | description | values | +| ---------------- | ----------------------------------------------------------------- | ----------------------------------------- | +| `id` | Unique string identifier for the definition | \* | +| `label` | User/display friendly to show in UI | \* | +| `icon` | A string name for an icon supported by the consuming application. | \* | +| `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` | +| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` | +| `commandOptions` | (optional) Options to pass the target `commandName` | \* | +| `context` | (optional) Overrides module's `defaultContext` | Array of string context names | + +Where a button with a `type` of `setToolActive` has an "active" styling applied +when clicked; removing the active styling from all other buttons. + +## Nested Toolbar Menus + +You can indicate that buttons should be grouped and nested in a submenu by +including `buttons` property in a definition: + +```js +{ + id: 'More', + label: 'More', + icon: 'ellipse-circle', + buttons: [ + { + id: 'cstInvert', + label: 'Invert', + icon: 'circle', + type: 'command', + commandName: 'invertViewport', + }, + ], +}, +``` + +![Toolbar Extension](../../assets/img/extensions-toolbar-nested.gif) + +
Example toolbar button demonstrating nested buttons.
+ +## Custom Components + +The Toolbar Modules supports rendering custom components in place of the +application's default. In place of the `type`, `commandName`, and +`commandOptions` properties, we instead specify a `CustomComponent`. + +```js +{ + id: 'Custom', + label: 'Custom', + icon: 'custom-icon', + CustomComponent: CustomToolbarComponent, +} + +``` + +The `CustomComponent` components will receive the following props: + +```html + +``` + +| Property | Type | Description | +| ---------------------- | -------- | ------------------------------- | +| `activeButtons` | string[] | list of active buttons | +| `button` | object | its own definition object | +| `key` | string | React key prop | +| `isActive` | boolean | If current button is active | +| `parentContext` | ? | The parent component's context? | +| `toolbarClickCallback` | func | Callback method for clicks | diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md new file mode 100644 index 000000000..4291c99f3 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 5 +sidebar_label: Viewport +--- +# Module: Viewport + +An extension can register a Viewport Module by defining a `getViewportModule` +method that returns a React component. Currently, we use viewport components to +add support for: + +- 2D Medical Image Viewing (cornerstone ext.) +- Structured Reports as HTML (dicom html ext.) +- Encapsulated PDFs as PDFs (dicom pdf ext.) +- Whole Slide Microscopy Viewing (whole slide ext.) +- etc. + +The general pattern is, the [`sopClassHandlerModule`](#) helps us determine +which Viewport Component a set of `sopClassUIDs` should default to. The Viewport +Component receives props containing a display set it should know how to render. + +## Viewport Component Props + +Each `ViewportComponent` will receive the following props: + +```html + +``` + +| Property | Type | Description | +| --------------- | --------------- | --------------------------------- | +| `children` | React.element[] | | +| `viewportData` | object | `viewportSpecificData` (probably) | +| `viewportIndex` | number | | + +### `@ohif/viewer` + +Viewport components are managed by the `ViewportGrid` Component. Which Viewport +component is used depends on: + +- The Layout Configuration +- Registered SopClassHandlers +- The SopClassUID for visible/selected datasets + +![Cornerstone Viewport](../../assets/img/extensions-viewport.png) + +
An example of three cornerstone Viewports
diff --git a/platform/docs/versioned_docs/version-2.0/faq/_category_.json b/platform/docs/versioned_docs/version-2.0/faq/_category_.json new file mode 100644 index 000000000..092e3e857 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/faq/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "FAQ", + "position": 10 +} diff --git a/platform/docs/versioned_docs/version-2.0/faq/browser-support.md b/platform/docs/versioned_docs/version-2.0/faq/browser-support.md new file mode 100644 index 000000000..8d664095a --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/faq/browser-support.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 3 +--- +# Browser Support + +The browsers that we support are specified in the `.browserlistrc` file located +in the `platform/viewer` project. While we leverage the latest language features +when writing code, we rely on `babel` to _transpile_ our code so that it can run +in the browsers that we support. + +## In Practice + +The OHIF Viewer is capable of _running_ on: + +- IE 11 +- FireFox +- Chrome +- Safari +- Edge + +However, we do not have the resources to adequately test and maintain bug free +functionality across all of these. In order to push web based medical imaging +forward, we focus our development efforts on recent version of modern evergreen +browsers. + +Our support of older browsers equates to our willingness to review PRs for bug +fixes, and target their minimum JS support whenever possible. + +### Polyfills + +> A polyfill, or polyfiller, is a piece of code (or plugin) that provides the +> technology that you, the developer, expect the browser to provide natively. + +An example of a polyfill is that you expect `Array.prototype.filter` to exist, +but for some reason, the browser that's being used has not implemented that +language feature yet. Our earlier transpilation will rectify _syntax_ +discrepencies, but unimplemented features require a "temporary" implementation. +That's where polyfills step in. + +You can utilize a service like [polyfill.io](https://polyfill.io/v3/) to +auto-detect and apply polyfills as needed, or you can update the PWA build to +include polyfill's in your bundle by incorporating [core-js][core-js] + + + + +[core-js]: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md + diff --git a/platform/docs/versioned_docs/version-2.0/faq/index.md b/platform/docs/versioned_docs/version-2.0/faq/index.md new file mode 100644 index 000000000..c75a50028 --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/faq/index.md @@ -0,0 +1,86 @@ +--- +sidebar_position: 1 +sidebar_label: Overview + +--- +# Frequently Asked Questions + +## Index + +- [Report a bug][report-bug] +- [Request a feature][new-feature] +- [Commercial Support & Consulting][commercial-support] +- [Academic collaborations][academic] +- [FDA Clearance or CE Marking][fda-clearance] +- [HIPAA Compliance][hipaa] + +### How do I report a bug? + +Navigate to our [GitHub Repository][new-issue], and submit a new bug report. +Follow the steps outlined in the [Bug Report Template][bug-report-template]. + +### How can I request a new feature? + +At the moment we are in the process of defining our roadmap and will do our best +to communicate this to the community. If your requested feature is on the +roadmap, then it will most likely be built at some point. If it is not, you are +welcome to build it yourself and [contribute it](development/contributing.md). +If you have resources and would like to fund the development of a feature, +please [contact us](http://www.ohif.org) or work with community members that +offer [consulting services][commercial-support]. + +### Who should I contact about Academic Collaborations? + +[Gordon J. Harris](http://www.dfhcc.harvard.edu/insider/member-detail/member/gordon-j-harris-phd/) +at Massachusetts General Hospital is the primary contact for any academic +collaborators. We are always happy to hear about new groups interested in using +the OHIF framework, and may be able to provide development support if the +proposed collaboration has an impact on cancer research. + +### Does OHIF offer commercial support? + +The Open Health Imaging Foundation does not offer commercial support, however, +some community members do offer consulting services. The following contacts may +be useful: + +- Rob Lewis ([Radical Imaging](http://radicalimaging.com/)) + +**Please file a Pull Request if you wish to add your name or organization to +this list.** + +### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? + +**NO.** The OHIF Viewer is **NOT** F.D.A. cleared or CE Marked. It is the users +responsibility to ensure compliance with applicable rules and regulations. The +[License](https://github.com/OHIF/Viewers/blob/master/LICENSE) for the OHIF +Platform does not prevent your company or group from seeking F.D.A. clearance +for a product built using the platform. + +If you have gone this route (or are going there), please let us know because we +would be interested to hear about your experience. + +### Is The OHIF Viewer [HIPAA][hipaa-def] Compliant? + +**NO.** The OHIF Viewer **DOES NOT** fulfill all of the criteria to become HIPAA +Compliant. It is the users responsibility to ensure compliance with applicable +rules and regulations. + + + + + +[report-bug]: #how-do-i-report-a-bug +[new-feature]: #how-can-i-request-a-new-feature +[commercial-support]: #does-ohif-offer-commercial-support +[academic]: #who-should-i-contact-about-academic-collaborations +[fda-clearance]: #does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission +[hipaa]: #is-the-ohif-viewer-hipaa-compliant + +[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/ +[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en +[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act +[new-issue]: https://github.com/OHIF/Viewers/issues/new/choose +[bug-report-template]: https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Bug+Report+%3Abug%3A&template=---bug-report.md&title= + diff --git a/platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md new file mode 100644 index 000000000..cf4c0bb0e --- /dev/null +++ b/platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md @@ -0,0 +1,59 @@ +--- +sidebar_position: 4 +--- +# PWA vs Packaged + +It's important to know that the OHIF Viewer project provides two different build +processes: + +```bash +# Static Asset output: For deploying PWAs +yarn run build + +# Single `.js` script, for embedding viewer into existing apps +yarn run build:package +``` + +## Progressive Web Application (PWA) + +> [Progressive Web Apps][pwa] are a new breed of web applications that meet the +> [following requirements][pwa-checklist]. Notably, targeting a PWA allows us +> provide a reliable, fast, and engaging experience across different devices and +> network conditions. + +The OHIF Viewer is maintained as a [monorepo][monorepo]. We use WebPack to build +the many small static assets that comprise our application. Also generated is an +`index.html` that will serve as an entry point for loading configuration and the +application, as well as a `service-worker` that can intelligently cache files so +that subsequent requests are from the local file system instead of over the +network. + +You can read more about this particular strategy in our +[Build for Production Deployment Guide](./../deployment/recipes/build-for-production.md) + +## Commonjs Bundle (Packaged Script) + +The [@ohif/viewer][viewer-npm] package is built with WebPack to provide a React +component that can be dropped into a larger application. The `OHIFViewer` +component is the entire viewer, configurable via React `props`. This is useful +for including the OHIF Viewer in a larger web application, as the entire +application can be provided via a `