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
This commit is contained in:
1 parent
1bf651e763
commit
5643f8f6d2
142 files changed
+5251
-1523
No files matched your search
@@ -0,0 +1,297 @@
|
||||
# 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'
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
/*
|
||||
...
|
||||
*/
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user