ohif-viewer/docs/latest/modes/routes.md
Alireza 5643f8f6d2
feat: Added documentation for OHIF-v3 (#2450)
* Added docs with new screenshots

* Added doc to architecture

* Added documentations to various extension modules

* Added more documentation to modes

* Added docs to managers

* Added docs for services

* Fixed deployment docs

* Added white labelling documentation

* Added i18n docs and measurement export
2021-06-15 11:15:29 -04:00

298 lines
9.3 KiB
Markdown

# Mode: Routes
- [Mode: Routes](#mode-routes)
- [Overview](#overview)
- [Route](#route)
- [Route: path](#route-path)
- [Route: init](#route-init)
- [Default init](#default-init)
- [Writing a custom init](#writing-a-custom-init)
- [Route: layoutTemplate](#route-layouttemplate)
- [FAQ](#faq)
## Overview
Modes are tied to a specific route in the viewer, and multiple modes/routes can be present within a single application. This makes `routes` config, THE most important part of the mode configuration.
## Route
`@ohif/viewer` **compose** extensions to build applications on different routes for the platform.
Below, you can see a simplified version of the `longitudinal` mode and the `routes` section
which has defined one `route`. Each route has three different configuration:
- **route path**: defines the route path to access the built application for that route
- **route init**: hook that runs when application enters the defined route path, if not defined the default init function will run for the mode.
- **route layout**: defines the layout of the application for the specified route (panels, viewports)
```js
export default function mode() {
return {
id: 'viewer',
displayName: '',
routes: [
{
path: 'longitudinal',
/*init: ({ servicesManager, extensionManager }) => {
//defaultViewerRouteInit
},*/
layoutTemplate: ({ location, servicesManager }) => {
return {
id: ohif.layout,
props: {
leftPanels: [
'org.ohif.measurement-tracking.panelModule.seriesList',
],
rightPanels: [
'org.ohif.measurement-tracking.panelModule.trackedMeasurements',
],
viewports: [
{
namespace:
'org.ohif.measurement-tracking.viewportModule.cornerstone-tracked',
displaySetsToDisplay: [
'org.ohif.default.sopClassHandlerModule.stack',
],
},
{
namespace: 'org.ohif.dicom-sr.viewportModule.dicom-sr',
displaySetsToDisplay: [
'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr',
],
},
],
},
}
},
},
],
/*
...
*/
}
}
```
### Route: path
Upon initialization the viewer will consume extensions and modes and build up the route desired, these can then be accessed via the study list, or directly via url parameters.
> Note: Currently, only one route is built for each mode, but we will enhance route
> creation to create separate routes based on the `path` config for each `route` object.
There are two types of `routes` that are created by the mode.
- Routes with dataSourceName `/${mode.id}/${dataSourceName}`
- Routes without dataSourceName `/${mode.id}`
Therefore navigating to `http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` will run the app with the layout and functionalities of the `viewer` mode using the `defaultDataSourceName` which is defined in the [App Config](../configuring/index.md)
You can use the same exact mode using a different registered data source (e.g., `dicomjson`) by navigating to `http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1`
### Route: init
The mode also has an init hook, which initializes the mode. If you don't define an `init`
function the `default init` function will get run (logic is located inside `Mode.jsx`). However, you
can define you own init function following certain steps which we will discuss next.
#### Default init
Default init function will:
- `retriveSeriesMetaData` for the `studyInstanceUIDs` that are defined in the URL.
- Subscribe to `instanceAdded` event, to make display sets after a series have finished
retrieving its instances metadata.
- Subscribe to `seriesAdded` event, to run the `HangingProtocolService` on the retrieves series
from the study.
A *simplified* "pseudocode" for the `defaultRouteInit` is:
```js
async function defaultRouteInit({
servicesManager,
studyInstanceUIDs,
dataSource,
}) {
const { DisplaySetService, HangingProtocolService } = servicesManager.services
// subscribe to run the function after the event happens
DicomMetadataStore.subscribe(
'instancesAdded',
({ StudyInstanceUID, SeriesInstanceUID }) => {
const seriesMetadata = DicomMetadataStore.getSeries(
StudyInstanceUID,
SeriesInstanceUID
)
DisplaySetService.makeDisplaySets(seriesMetadata.instances)
}
)
studyInstanceUIDs.forEach((StudyInstanceUID) => {
dataSource.retrieveSeriesMetadata({ StudyInstanceUID })
})
DicomMetadataStore.subscribe('seriesAdded', ({ StudyInstanceUID }) => {
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID)
HangingProtocolService.run(studyMetadata)
})
return unsubscriptions
}
```
#### Writing a custom init
You can add your custom init function to enhance the default initialization for:
- Fetching annotations from a server for the current study
- Changing the initial image index of the series to be displayed at first
- Caching the next study in the work list
- Adding a custom sort for the series to be displayed on the study browser panel
and lots of other modifications.
You just need to make sure, the mode `retrieveSeriesMetadata`, `makeDisplaySets` and `run` the
HangingProtocols at some point. There are various `events` that you can subscribe to and add your custom logic. <mark>point to events
For instance for jumping to the slice where a measurement is located at the initial render, you need to follow a pattern similar to the following:
```js
init: async ({
servicesManager,
extensionManager,
hotkeysManager,
dataSource,
studyInstanceUIDs,
}) => {
const { DisplaySetService } = servicesManager.services
/**
...
**/
const onDisplaySetsAdded = ({ displaySetsAdded, options }) => {
const displaySet = displaySetsAdded[0]
const { SeriesInstanceUID } = displaySet
const toolData = myServer.fetchMeasurements(SeriesInstanceUID)
if (!toolData.length) {
return
}
toolData.forEach((tool) => {
const instance = displaySet.images.find(
(image) => image.SOPInstanceUID === tool.SOPInstanceUID
)
const { SOPInstanceUID, url } = instance
displaySet.initialImageIdIndex = displaySet.images.indexOf(instance)
})
MeasurementService.addMeasurement(/**...**/)
}
// subscription to the DISPLAY_SETS_ADDED
const { unsubscribe } = DisplaySetService.subscribe(
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
onDisplaySetsAdded
)
/**
...
**/
return unsubscriptions
}
```
### Route: layoutTemplate
`layoutTemplate` is the last configuration for a certain route in a `mode`. `layoutTemplate` is
a function that returns an object that configures the overall layout of the application. The returned
object has two properties:
- `id`: the id of the `layoutTemplate` being used (it should have been registered via an extension)
- `props`: the required properties to be passed to the `layoutTemplate`.
For instance `default extension` provides a layoutTemplate that builds the app using left/right panels
and viewports. Therefore, the `props` include `leftPanels`, `rightPanels` and `viewports` sections. Note that the `layoutTemplate` defines the properties it is expecting. So, if you write a `layoutTemplate-2` that accepts a footer section, its logic should be written in the extension, and any mode that
is interested in using `layoutTemplate-2` **should** provide the `id` for the footer component.
<mark>What module should the footer be registered?
```js
/*
...
*/
layoutTemplate: ({ location, servicesManager }) => {
return {
id: 'org.ohif.default.layoutTemplateModule.viewerLayout',
props: {
leftPanels: [
'myExtension.panelModule.leftPanel1',
'myExtension.panelModule.leftPanel2',
],
rightPanels: ['myExtension.panelModule.rightPanel'],
viewports: [
{
namespace: 'myExtension.viewportModule.viewport1',
displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop1'],
},
{
namespace: 'myExtension.viewportModule.viewport2',
displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop2'],
},
],
},
}
}
/*
...
*/
```
## FAQ
> What is the difference between `onModeEnter` and `route.init`
`onModeEnter` gets run first than `route.init`; however, each route can have their own `init`, but they share the `onModeEnter`.
> How can I change the `workList` appearance or add a new login page?
This is where `OHIF-v3` shines! Since the default `layoutTemplate` is written for the viewer part, you can simply add a new `layoutTemplate` and use the component you have written for that route. `Mode` handle showing the correct component for the specified route.
```js
export default function mode() {
return {
id: 'viewer',
displayName: '',
routes: [
{
path: 'worklist',
init,
layoutTemplate: ({ location, servicesManager }) => {
return {
id: 'worklistLayout',
props: {
component: 'myNewWorkList'
},
}
},
},
],
/*
...
*/
}
}
```