* 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
298 lines
9.3 KiB
Markdown
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'
|
|
},
|
|
}
|
|
},
|
|
},
|
|
],
|
|
/*
|
|
...
|
|
*/
|
|
}
|
|
}
|
|
```
|