Skip to main content
Version: 3.9.0-beta.66 (Latest)

Introduction

We have re-designed the architecture of the OHIF-v3 to enable building applications that are easily extensible to various use cases (modes) that behind the scene would utilize desired functionalities (extensions) to reach the goal of the use case.

Previously, extensions were “additive” and could not easily be mixed and matched within the same viewer for different use cases. Previous OHIF-v2 architecture meant that any minor extension alteration usually would require the user to hard fork. E.g. removing some tools from the toolbar of the cornerstone extension meant you had to hard fork it, which was frustrating if the implementation was otherwise the same as master.

  • Developers should make packages of reusable functionality as extensions, and can consume publicly available extensions.
  • Any conceivable radiological workflow or viewer setup will be able to be built with the platform through modes.

Practical examples of extensions include:

Diagram showing how extensions are configured and accessed.

Extension Skeleton

An extension is a plain JavaScript object that has id and version properties, and one or more modules and/or lifecycle hooks.

// prettier-ignore
export default {
/**
* Required properties. Should be a unique value across all extensions.
*/
id,

// Lifecycle
preRegistration() { /* */ },
onModeEnter() { /* */ },
onModeExit() { /* */ },
// Modules
getLayoutTemplateModule() { /* */ },
getDataSourcesModule() { /* */ },
getSopClassHandlerModule() { /* */ },
getPanelModule() { /* */ },
getViewportModule() { /* */ },
getCommandsModule() { /* */ },
getContextModule() { /* */ },
getToolbarModule() { /* */ },
getHangingProtocolModule() { /* */ },
getUtilityModule() { /* */ },
}

OHIF-Maintained Extensions

A small number of powerful extensions for popular use cases are maintained by OHIF. They're co-located in the OHIF/Viewers repository, in the top level extensions/ directory.

ExtensionDescriptionModules
defaultDefault extension provides default viewer layout, a study/series browser, and a datasource that maps to a DICOMWeb compliant backendcommandsModule, ContextModule, DataSourceModule, HangingProtocolModule, LayoutTemplateModule, PanelModule, SOPClassHandlerModule, ToolbarModule
cornerstoneProvides 2d and 3d rendering functionalitiesViewportModule, CommandsModule, UtilityModule
dicom-pdfRenders PDFs for a specific SopClassUID.Viewport, SopClassHandler
dicom-videoRenders DICOM Video files.Viewport, SopClassHandler
cornerstone-dicom-srMaintained extensions for cornerstone and visualization of DICOM Structured ReportsViewportModule, CommandsModule, SOPClassHandlerModule
measurement-trackingTracking measurements in the measurement panel ContextModule,PanelModule,ViewportModule,CommandsModule

Registering of Extensions

viewer starts by registering all the extensions specified inside the pluginConfig.json, by default we register all extensions in the repo.

platform/app/pluginConfig.json
// Simplified version of the `pluginConfig.json` file
{
"extensions": [
{
"packageName": "@ohif/extension-cornerstone",
"version": "3.4.0"
},
{
"packageName": "@ohif/extension-measurement-tracking",
"version": "3.4.0"
},
// ...
],
"modes": [
{
"packageName": "@ohif/mode-longitudinal",
"version": "3.4.0"
}
]
}
Important

You SHOULD NOT directly register extensions in the pluginConfig.json file. Use the provided cli to add/remove/install/uninstall extensions. Read more here

The final registration and import of the extensions happen inside a non-tracked file pluginImport.js (this file is also for internal use only).

After an extension gets registered within the viewer, each module defined by the extension becomes available to the modes via the ExtensionManager by requesting it via its id. Read more about Extension Manager

Lifecycle Hooks

Currently, there are three lifecycle hook for extensions:

preRegistration This hook is called once on initialization of the entire viewer application, used to initialize the extensions state, and consume user defined extension configuration. If an extension defines the preRegistration lifecycle hook, it is called before any modules are registered in the ExtensionManager. It's most commonly used to wire up extensions to services and commands, and to bootstrap 3rd party libraries.

onModeEnter: This hook is called whenever a new mode is entered, or a mode’s data or datasource is switched. This hook can be used to initialize data.

onModeExit: Similarly to onModeEnter, this hook is called when navigating away from a mode, or before a mode’s data or datasource is changed. This can be used to cache data for reuse later, but since it isn't known which mode will be entered next, the state after exiting should be clean, that is, the same as the state on a clean start. This is called BEFORE service clean up, and after mode specific onModeExit handling.

Modules

Modules are the meat of extensions, the blocks that we have been talking about a lot. They provide "definitions", components, and filtering/mapping logic that are then made available to modes and services.

Each module type has a special purpose, and is consumed by our viewer differently.

TypesDescription
LayoutTemplateControl Layout of a route
DataSourceControl the mapping from DICOM metadata to OHIF-metadata
SOPClassHandlerDetermines how retrieved study data is split into "DisplaySets"
PanelAdds left or right hand side panels
ViewportAdds a component responsible for rendering a "DisplaySet"
CommandsAdds named commands, scoped to a context, to the CommandsManager
ToolbarAdds buttons or custom components to the toolbar
ContextShared state for a workflow or set of extension module definitions
HangingProtocolAdds hanging protocol rules
UtilityExpose utility functions to the outside of extensions
Tbl. Module types with abridged descriptions and examples. Each module links to a dedicated documentation page.

Contexts

The @ohif/app 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 rotation behavior.