feat: cornerstone3D stack and volume viewports (#2787)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: cs3d tools and toolGroups (#20)

* add more tools to work with cs3d mode

* fix hotkeys

* add stack manager usage for stach viewports

* add image scrollbar

* wip viewport overlay

* fix toAnnotation schema for tools

* fix the unnecessary size change that triggered resize

* hanging protocol improvement to allow unmatched errors

* study description matching for hanging protocol

* fix the displaysetOptions to work

* fix handle the active tool when a new viewport is added

* fix separate toolGroups for mode

* apply review comments

* apply review comments

* yarn lock

* feat: overlay component (#21)

* fix default displayset options

* add viewport overlay

* apply review comments

* feat: loading and orientation indicators (#22)

* add loading indicator

* add orientation marker initial work

* apply review comments

* fix: orientation markers (#23)

* finished the orientation markers

* fix various broken cypress tests

* apply review comments

* update yarn lock

* feat: re-working measurement tracking mode with cornerstone3d (#2805)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: Add Measurement tracking mode with cs3D (#2789)

* feat: first render for cornerstone3d tracked viewport

* make tool active work

* wip for SR extension

* renamed dicom sr to cornerstone dicom sr

* remove cornerstone from dicom pdf and video

* move dicom sr logic to sr extension

* feat: Add hydration for the length tool

* fix SR display tool for length using cs3d

* fix default config

* fix: various bugs with sr viewport and tracking

* fix promptying to continue tracking for when SR is created

* feat: add keep trackign of unique identifiers

* fix hydration for same imageIds

* feat: Add SR toolGroup creation on modeEnter

* feat: remove the need for separate mapper for SR hydration

* add SR display for ellipse

* handle hydration of elliptical ROI tool

* remove cornerstone extension

* add arrow mapping

* feat: Add ArrowAnnotate SR display and hydration

* apply review comments

* apply review comments

* move viewport labels to the viewportData

* apply review comments

* fix: integration cypress tests with cornerstone3D and add CINE tool (#2795)

* fix: integration cypress tests with cornerstone3D

* revert to addOrUpdate as it makes more sense

* fix local drag and drop

* fix tests

* move dicomLoaderService to cornerstone extension

* fix various import bugs

* fix bug for local PT series

* fix various unit tests

* bump cs3d versions

* add angle and magnify tool

* bump dependencies to avoid broken peerDeps

* feat: add initial work for capture using cs3d

* feat: show annotations on the image capture

* feat: add svg layer export

* feat: Add CINE Tool

* feat: remove unnecessary viewport rendering for cine state changes

Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>

* docs: modify and improve documentation (#2800)

* cleanup docs versionings

* feat: Add all version explanations

* version docs for 3.0

* wip for changing docs

* wip for updated docs

* add utility module documentation

* fix demo with nohoisting of history

* add slides and video to resources

* apply review comments

* fix: drag and drop SR into SR viewport (#2803)

* update yarn lock

Co-authored-by: Erik <erik.sweed@gmail.com>

* update pathnames to match v3-stable

* update the e2e pathname

* fix: various bugs with regard to tracking workflow (#2811)

* fix: various issues with measurement panel

* fix: update default tool style for annotations

* fix: annotatoin label getting removed

* feat: Add backward compatibility for SR hydration with legacy cornerstone

* fix: cursors and ellipse ROI max style

* fix: ArrowAnnotate SRDisplay

* apply review comments

* bump package versions

* fix: bugin rehydration of SR

* fix: e2e tests

* fix active viewport thickness and arrowTool ui

* add readme for measurement tracking

* use uploaded image for readme

* add back images

* try to fix e2e test

* fix: window level presets hotkeys

* Update README.md

* update yarn lock

* feat: volume api and TMTV mode (#2817)

* feat: volumeAPI and TMTV mode

* feat: use cs3d cache service to obtain viewportData

* wip for volume api

* wip: fusion viewport

* feat: add blend mode option

* wip for image scrollbar

* fix drag and drop thumbnail

* wip for image scrollbar for voluems

* fix: element mismatch bug for scroll

* feat: Add image scrollbar to volumes

* feat: add syncGroups to volume api

* feat: add tmtv mode initial setup

* feat: add initial image options for the stack viewports

* feat: Add custom load strategy for volume viewports via HP

* apply review comments

fix: Jump presets cs3d (#2812)

* feat: Add JumpPreset to OHIF for Cornerstone3D

* fix: accessing viewport service from servicesManager

feat: volume API and TMTV mode (#2814)

* fix: do not display overlays on mip viewports

* feat: add optional disableCommands for toggle buttons

* fix: toggleCrossharis

* feat: config the crosshairs

* feat: add PetSUV Panel for changing metadata

* feat: initial work for the rectangleROIThreshold panel

* wip: measurement service

* roi threshold working

* feat: Add displayText to segmentations

* feat: add remove segmentation

* feat: add csv export

* feat: add RT export for annotations in tmtv mode

* fix: fusion to use pt in tmtv mode and measuremet mappings

* fix: various bugs

* apply review comments

* apply review comments

* feat: add fusion color maps

* add readme to tmtv mode

* Update README.md

* fix: try to fix build

* fix unit tests

* Update README.md

* feat: add about to the panel

* fix: changing strategy in roi panel

* apply review comments

* update package versions

* wip for stackPrefetch

* fix: cornerstone3d hydration and renaming (#2818)

* update readme

* renamed cornerstone extension

* wip for renaming cornerstone3D variables

* wip for renaming cornerstone3D variables

* wip for fixing bugs for SR viewport

* fix: jumpToMeasurement and initial label after hydration

* fix: fileName capitalization

* feat: use the new prefetch stack in the cs3d (#2820)

* feat: use the new prefetch stack in the cs3d

* use viewport scroll api for stack viewport

* fix cine stop when scrollbar changes

* feat: use new prefetch events

* fix loading state to not show repeatedly

* fix: various bugs for tmtv mode thresholding and new icons (#2823)

* feat: make tmtv mode available in worklist

* feat: add new icons for tmtv mode

* feat: add fusion color icon

* fix: parallel scale calculation

* fix: bump Cornerstone to get large image support working

* fix: Fix issues with CPU viewport flipping, including config files

* fix: bump cornerstone to fix magnify tool

* fix: Bump Cornerstone version to fix resetCamera issue in StackViewport

* ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys

* fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors

* feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838)

Also works with the compile time add that the existing cli uses, so that both build types work.
Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes.

* bump: dependency versions to fix hydration bugs (#2848)

* bump: dcmjs version to fix hydration bugs

* try to fix tests

* bump dependency versions

Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
authored and GitHub committed 2022-07-27 12:39:04 -04:00
1 parent 86dcaf6293
commit bd1c38c00c
668 files changed
+41669 -13410

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ Key features:
- Designed to load large radiology studies as quickly as possible. Retrieves
metadata ahead of time and streams in imaging pixel data as needed.
- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding,
- Leverages [Cornerstone3D](https://github.com/cornerstonejs/cornerstone3D-beta) for decoding,
rendering, and annotating medical images.
- Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web].
Offers a Data Source API for communicating with archives over proprietary API
@@ -1,83 +0,0 @@
---
sidebar_position: 3
sidebar_label: Configuration
---
# Viewer: Configuration
The OHIF Viewer Platform strives to be highly configurable and extensible. This
makes it easier for our community members to keep their "secret sauce" private,
and incentives contributions back to the platform. The `@ohif/viewer` project of
the platform is the lynchpin that combines everything to create our application.
There are two configuration mechanisms, one for run-time configuration, allowing
for changes to be decided based on including different configuration files as
specified by the 'theme' URL parameters. This mechanism is intended for
modifications of data exposed as configurable items by the existing code. See
sections below on configuring this type of value.
The other mechanism is the code-configuration mechanism that specifies load
time configuration. This is intended to load things that require code level
changes to OHIF such as adding a new viewer configuration. This is also used
for base definitions that are shared site-wide such as the data sources. This
was the original configuration mechanism provided, and some of the configurations
specified there are better suited to the run time loading, but are currently
left alone as there hasn't been time to move them.
We maintain a number of common viewer application configurations at
[`<root>/platform/viewer/public/configs`][config-dir].
You can take a look at how to use different configs in the
[Environment Variables](../platform/environment-variables)
```js title="<root>/platform/viewer/public/configs"
window.config = {
routerBasename: '/',
/**
* "White Labeling" is used to change the branding, look, and feel of the OHIF
* Viewer. These settings, and the color variables that are used by our components,
* are the easiest way to rebrand the application.
*
* More extensive changes are made possible through swapping out the UI library,
* Viewer project, or extensions.
*/
whiteLabeling: {
/** ... **/
},
httpErrorHandler: {
/** coming soon **/
},
extensions: [],
showStudyList: true,
filterQueryParam: false,
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
sourceName: 'dicomweb',
configuration: {
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,
supportsReject: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
supportsWildcard: true,
},
},
],
};
```
<!--
LINKS
-->
<!-- prettier-ignore-start -->
[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config
<!-- prettier-ignore-end -->
@@ -24,15 +24,16 @@ you'll see the following:
```bash
│
├── extensions
│ ├── _example # Skeleton of example extension
│ ├── default # default functionalities
│ ├── cornerstone # 2D images w/ Cornerstone.js
│ ├── measurement-tracking # measurement tracking
│ ├── dicom-sr # Structured reports
│ └── dicom-pdf # View DICOM wrapped PDFs in viewport
│ ├── _example # Skeleton of example extension
│ ├── default # default functionalities
│ ├── cornerstone # 2D/3D images w/ Cornerstonejs
│ ├── cornerstone-dicom-sr # Structured reports
│ ├── measurement-tracking # measurement tracking
│ └── dicom-pdf # View DICOM wrapped PDFs in viewport
│
├── modes
│ └── longitudinal # longitudinal measurement tracking mode
| └── basic-dev-mode # basic viewer with Cornerstone (a developer focused mode)
│
├── platform
│ ├── core # Business Logic
@@ -1,11 +1,11 @@
---
sidebar_position: 7
sidebar_label: Continous Integration
sidebar_label: Continuos Integration
---
# Continous Integration (CI)
# Continuos Integration (CI)
This repository uses `CircleCI` and `Netlify` for continous integration.
This repository uses `CircleCI` and `Netlify` for Continuos integration.
## Deploy Previews
@@ -24,6 +24,11 @@ up-to-date with the upstream (original) repository. This is called a "Triangular
Workflow" and is common for Open Source projects. The GitHub blog has a [good
graphic that illustrates this setup][triangular-workflow].
### `v3-stable` branch
Currently the stable branch for OHIF-v3 is `v3-stable`. Once the v3-stable branch has
feature parity with the master branch, `v3-stable` will be pushed to the master branch.
You can read more about the roadmap timeline [here](https://ohif.org/roadmap).
### Private
Alternatively, if you intend to use the OHIF Viewer as a starting point, and you
@@ -106,6 +111,6 @@ yarn run build
[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-repo]: https://github.com/OHIF/Viewers/tree/v3-stable
[ohif-viewers]: https://github.com/OHIF/Viewers
<!-- prettier-ignore-end -->
@@ -65,6 +65,7 @@ export default {
getContextModule() { /* */ },
getToolbarModule() { /* */ },
getHangingProtocolModule() { /* */ },
getUtilityModule() { /* */ },
}
```
@@ -86,7 +87,7 @@ the top level [`extensions/`][ext-source] directory.
<tr>
<td>
<a href="">
Default
default
</a>
</td>
<td>
@@ -97,18 +98,18 @@ the top level [`extensions/`][ext-source] directory.
</tr>
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-cornerstone">
Cornerstone
<a href="">
cornerstone
</a>
</td>
<td>
Provides rendering functionalities for 2D images.
Provides 2d and 3d rendering functionalities
</td>
<td>ViewportModule, CommandsModule</td>
<td>ViewportModule, CommandsModule, UtilityModule</td>
</tr>
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-pdf">DICOM PDF</a>
<a href="">dicom-pdf</a>
</td>
<td>
Renders PDFs for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6">specific SopClassUID</a>.
@@ -117,7 +118,16 @@ the top level [`extensions/`][ext-source] directory.
</tr>
<tr>
<td>
<a href="">DICOM SR</a>
<a href="">dicom-video</a>
</td>
<td>
Renders DICOM Video files.
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<tr>
<td>
<a href="">cornerstone-dicom-sr</a>
</td>
<td>
Maintained extensions for cornerstone and visualization of DICOM Structured Reports
@@ -126,7 +136,7 @@ the top level [`extensions/`][ext-source] directory.
</tr>
<tr>
<td>
<a href="">Measurement tracking</a>
<a href="">measurement-tracking</a>
</td>
<td>
Tracking measurements in the measurement panel
@@ -290,6 +300,14 @@ differently.
</td>
<td align="left">Adds hanging protocol rules</td>
</tr>
<tr>
<td align="left">
<a href="./modules/hpModule">
Utility
</a>
</td>
<td align="left">Expose utility functions to the outside of extensions</td>
</tr>
</tbody>
</table>
@@ -16,8 +16,8 @@ Extensions can implement specific lifecycle methods.
## 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:
before any modules are registered in the `ExtensionManager`. This hook is an
`async` function that can be used to perform:
- initialize 3rd party libraries
- register event listeners
@@ -61,7 +61,7 @@ export default {
* @param {CommandsManager} params.commandsManager
* @returns void
*/
preRegistration({ servicesManager, commandsManager, configuration }) {
async preRegistration({ servicesManager, commandsManager, configuration }) {
console.log('Wiring up important stuff.');
window.importantStuff = () => {
@@ -90,7 +90,7 @@ _Example `onModeEnter` hook implementation_
```js
export default {
id: '@ohif/extension-dicom-sr',
id: '@ohif/extension-cornerstone-dicom-sr',
onModeEnter({ servicesManager }) {
const { DisplaySetService } = servicesManager.services;
@@ -22,26 +22,40 @@ const deafultProtocol = {
availableTo: {},
editableBy: {},
protocolMatchingRules: [],
toolGroupIds: ['default'],
stages: [
{
id: 'nwzau7jDkEkL8djfr',
name: 'oneByOne',
id: 'hYbmMy3b7pz7GLiaT',
name: 'default',
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 1,
columns: 2,
},
},
viewports: [
displaySets: [
{
viewportSettings: [],
id: 'displaySet',
imageMatchingRules: [],
seriesMatchingRules: [],
studyMatchingRules: [],
},
],
createdDate: '2021-02-23T19:22:08.894Z',
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
options: [],
id: 'displaySet',
},
],
},
],
createdDate: '2021-02-23T18:32:42.850Z',
},
],
numberOfPriorsReferenced: -1,
@@ -51,8 +65,385 @@ function getHangingProtocolModule() {
return [
{
name: hangingProtocolName,
protocols: [deafultProtocol],
protocols: [defaultProtocol],
},
];
}
```
## Skeleton of A Hanging Protocol
The skeleton of a hanging protocol is as follows:
```js
const testProtocol = {
id: 'test',
locked: true,
hasUpdatedPriorsInformation: false,
name: 'Default',
createdDate: '2021-02-23T19:22:08.894Z',
modifiedDate: '2021-02-23T19:22:08.894Z',
availableTo: {},
editableBy: {},
toolGroupIds: ['default'],
protocolMatchingRules: [
{
id: 'wauZK2QNEfDPwcAQo',
weight: 1,
attribute: 'StudyDescription',
constraint: {
contains: {
value: 'PETCT',
},
},
required: false,
},
{
id: 'wauZK2QNEfDPwcAQo',
weight: 1,
attribute: 'StudyDescription',
constraint: {
contains: {
value: 'PET/CT',
},
},
required: false,
},
],
stages: [
{
id: 'hYbmMy3b7pz7GLiaT',
name: 'default',
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 1,
viewportOptions: [],
},
},
displaySets: [
{
id: 'ctDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'Modality',
constraint: {
equals: {
value: 'CT',
},
},
required: true,
},
],
studyMatchingRules: [],
},
{
id: 'ptACDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'Modality',
constraint: {
equals: {
value: 'PT',
},
},
required: true,
},
],
studyMatchingRules: [],
},
{
id: 'ptNACDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'SeriesDescription',
constraint: {
contains: {
value: 'Uncorrected',
},
},
required: false,
},
],
studyMatchingRules: [],
},
],
viewports: [
{
viewportOptions: {
viewportId: 'ctAxial',
viewportType: 'stack',
background: [0, 0, 0],
orientation: 'AXIAL',
toolGroupId: 'default',
},
displaySets: [
{
id: 'ctDisplaySet',
},
],
},
{
viewportOptions: {
viewportId: 'ptAxial',
viewportType: 'stack',
background: [1, 1, 1],
orientation: 'AXIAL',
toolGroupId: 'default',
},
displaySets: [
{
options: {
voi: {
windowWidth: 5,
windowCenter: 2.5,
},
voiInverted: true,
},
id: 'ptACDisplaySet',
},
],
},
],
createdDate: '2021-02-23T18:32:42.850Z',
},
],
numberOfPriorsReferenced: -1,
};
```
Let's discuss each property in depth.
### Id
unique identifier for the protocol
### protocolMatchingRules
A list of criteria for the protocol along with the provided points for ranking.
- `weight`: weight for the matching rule. Eventually, all the registered
protocols get sorted based on the weights, and the winning protocol gets
applied to the viewer.
- `attribute`: tag that needs to be matched against. This can be either
Study-level metadata or a custom attribute.
[Learn more about custom attribute matching](#custom-attribute)
- `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be
[`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`]
A sample of the matching rule is above which matches against the study description to be PETCT
```js
{
id: 'wauZK2QNEfDPwcAQo',
weight: 1,
attribute: 'StudyDescription',
constraint: {
contains: {
value: 'PETCT',
},
},
required: false,
},
```
### stages
Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules.
Therefore, the `stages` property is array of objects, each object being one stage.
### viewportStructure
Defines the layout of the viewer. You can define the number of `rows` and `columns`. There should be `rows * columns` number of
viewport configuration in the `viewports` property. Note that order of viewports are rows first then columns.
```js
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 2,
viewportOptions: [],
},
},
```
In addition to the equal viewport sizes, you can define viewports to span multiple rows or columns.
```js
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 2,
viewportOptions: [
{
x: 0,
y: 0,
width: 1 / 4,
height: 1,
},
{
x: 1 / 4,
y: 0,
width: 3 / 4,
height: 1,
},
],
},
},
```
### displaySets
Defines the display sets that are available for the stage. Each
include an `id` and some `seriesMatchingRules`
```js
displaySets: [
{
id: 'ctDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'Modality',
constraint: {
equals: {
value: 'CT',
},
},
required: true,
},
],
studyMatchingRules: [],
},
{
id: 'ptACDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'Modality',
constraint: {
equals: {
value: 'PT',
},
},
required: true,
},
],
studyMatchingRules: [],
},
{
id: 'ptNACDisplaySet',
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'Modality',
constraint: {
equals: {
value: 'PT',
},
},
required: true,
},
{
id: 'GPEYqFLv2dwzCM322',
weight: 1,
attribute: 'SeriesDescription',
constraint: {
contains: {
value: 'Uncorrected',
},
},
required: false,
},
],
studyMatchingRules: [],
},
],
```
As you see in the above `displaySets` entry in the example above, there are
three defined displaySets that __will__ become available later on in the `viewports`
section of the protocol.
1. `ctDisplaySet` which is matched against the CT modality
2. `ptACDisplaySet` which is matched against the PT modality
3. `ptNACDisplaySet` which is matched against the PT modality and also contains the `Uncorrected` series description
### viewports
This field includes the viewports that will get hung on the viewer.
```js
viewports: [
{
viewportOptions: {
viewportId: 'ctAxial',
viewportType: 'stack',
background: [0, 0, 0],
orientation: 'AXIAL',
toolGroupId: 'default',
},
displaySets: [
{
id: 'ctDisplaySet',
},
],
},
{
viewportOptions: {
viewportId: 'ptAxial',
viewportType: 'stack',
background: [1, 1, 1],
orientation: 'AXIAL',
toolGroupId: 'default',
},
displaySets: [
{
options: {
voi: {
windowWidth: 5,
windowCenter: 2.5,
},
voiInverted: true,
},
id: 'ptACDisplaySet',
},
],
},
],
```
As you can see there are two viewports defined. The first one is a CT viewport and the second one is a PT viewport.
Each viewport has two properties:
1. `viewportOptions`: defines the viewport properties such as
- `viewportId`: unique identifier for the viewport (optional)
- `viewportType`: type of the viewport (required - currently only stack)
- `background`: background color of the viewport (optional)
- `orientation`: orientation of the viewport (optional)
- `toolGroupId`: tool group that will be used for the viewport (optional)
2. `displaySets`: defines the display sets that are available for the viewport. It is an array of objects, each object being one display set.
- `id`: id of the display set (required)
- `options` (optional): options for the display set
- voi: windowing options for the display set
- voiInverted: whether the VOI is inverted or not (optional)
@@ -72,7 +72,7 @@ const extensionDependencies = {
'@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0',
'@ohif/extension-measurement-tracking': '^3.0.0',
'@ohif/extension-dicom-sr': '^3.0.0',
'@ohif/extension-cornerstone-dicom-sr': '^3.0.0',
};
const id = 'viewer'
@@ -89,25 +89,32 @@ The simplest toolbarButtons definition has the following properties:
```js
{
id: 'Zoom',
type: 'ohif.radioGroup',
props: {
type: 'tool',
icon: 'tool-zoom',
label: 'Zoom',
commandOptions: { toolName: 'Zoom' },
},
},
"id": "Zoom",
"type": "ohif.radioGroup",
"props": {
"type": "tool",
"icon": "tool-zoom",
"label": "Zoom",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Zoom"
},
"context": "CORNERSTONE"
}
]
}
}
```
| 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 behaviour | "tool", "toggle", "action" |
| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` |
| `commandOptions` | (optional) Options to pass the target `commandName` | \* |
| `icon` | A string name for an icon supported by the consuming application. | \* |
| `label` | User/display friendly to show in UI | \* |
| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` |
There are three main types of toolbar buttons:
@@ -133,59 +140,92 @@ to create `MeasurementTools` nested button
```js title="modes/longitudinal/src/toolbarButtons.js"
{
id: 'MeasurementTools',
type: 'ohif.splitButton',
props: {
groupId: 'MeasurementTools',
isRadio: true,
primary: {
id: 'Length',
icon: 'tool-length',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Length',
}
"id": "MeasurementTools",
"type": "ohif.splitButton",
"props": {
"groupId": "MeasurementTools",
"isRadio": true,
"primary": {
"id": "Length",
"icon": "tool-length",
"label": "Length",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Length"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRLength",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Length"
},
secondary: {
icon: 'chevron-down',
label: '',
isActive: true,
tooltip: 'More Measure Tools',
"secondary": {
"icon": "chevron-down",
"label": "",
"isActive": true,
"tooltip": "More Measure Tools"
},
items: [
// Length tool
"items": [
{
id: 'Length',
icon: 'tool-length',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Length',
}
"id": "Bidirectional",
"icon": "tool-bidirectional",
"label": "Bidirectional",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Bidirectional"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRBidirectional",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Bidirectional Tool"
},
// Bidirectional tool
{
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Bidirectional',
}
"id": "ArrowAnnotate",
"icon": "tool-annotate",
"label": "Annotation",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "ArrowAnnotate"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRArrowAnnotate",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Arrow Annotate"
},
// Ellipse tool
{
id: 'EllipticalRoi',
icon: 'tool-elipse',
label: 'Ellipse',
type: 'tool',
commandOptions: {
toolName: 'EllipticalRoi',
}
},
],
},
]
}
}
```
@@ -0,0 +1,57 @@
---
sidebar_position: 9
sidebar_label: Utility
---
# Module: Utility
## Overview
Often, an extension will need to expose some useful functionality to the other
extensions, or modes that consume the extension. For example, the `cornerstone`
extension, uses its `utility` module to expose methods via
```js
getUtilityModule({ servicesManager }) {
return [
{
name: 'common',
exports: {
getCornerstoneLibraries: () => {
return { cornerstone, cornerstoneTools };
},
getEnabledElement,
CornerstoneViewportService,
dicomLoaderService,
},
},
{
name: 'core',
exports: {
Enums: cs3DEnums,
CONSTANTS,
},
},
{
name: 'tools',
exports: {
toolNames,
Enums: cs3DToolsEnums,
},
},
];
},
};
```
Then a consuming extension can use `getModuleEntry` to access the methods
Below, which is a code from `TrackedCornerstoneViewport` use the `getUtilityModule` method to get the internal `CornerstoneViewportService` which handles the `Cornerstone` viewport.
```js title="extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx"
const utilityModule = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.common'
);
const { CornerstoneViewportService } = utilityModule.exports;
const viewportId = CornerstoneViewportService.getViewportId(viewportIndex);
```
@@ -14,10 +14,7 @@ add support for:
- 2D Medical Image Viewing (cornerstone ext.)
- Structured Reports as SR (DICOM SR 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 that a mode can define which `Viewport` to use for which
specific `SOPClassHandlerUID`, so if you want to fork just a single Viewport
@@ -43,44 +40,53 @@ const getViewportModule = () => {
## Example Viewport Component
A simplified version of the tracked CornerstoneViewport is shown below, which
creates a cornerstone viewport and action bar on top of it.
A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which
creates a cornerstone viewport:
:::note Tip
Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to
the previous version (3.0) which uses `displaySet`. This is due to the fact that
we are moving to a new data model that can render fused images in a single viewport.
:::
```jsx
function TrackedCornerstoneViewport({
children,
dataSource,
displaySet,
displaySets,
viewportIndex,
servicesManager,
extensionManager,
commandsManager,
}) {
const renderViewport = () => {
const { component: Component } = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.viewportModule.cornerstone'
);
return (
<Component
onElementEnabled={onElementEnabled}
element={element}
{...props}
></Component>
);
};
return (
<>
<ViewportActionBar onDoubleClick onSeriesChange />
<div className="relative flex flex-row w-full h-full overflow-hidden">
{renderViewport()}
</div>
</>
<div className="viewport-wrapper">
/** Resize Detector */
<ReactResizeDetector
handleWidth
handleHeight
skipOnMount={true} // Todo: make these configurable
refreshMode={'debounce'}
refreshRate={100}
onResize={onResize}
targetRef={elementRef.current}
/>
/** Div For displaying image */
<div
className="cornerstone-viewport-element"
style={{ height: '100%', width: '100%' }}
onContextMenu={e => e.preventDefault()}
onMouseDown={e => e.preventDefault()}
ref={elementRef}
></div>
</div>
);
}
```
![viewportModule](../../../assets/img/viewportModule.png)
### `@ohif/viewer`
+49 -42
View File
@@ -2,24 +2,30 @@
sidebar_position: 3
sidebar_label: Service Manager
---
# Services Manager
## Overview
Services manager is the single point of service registration. Each service needs to implement a `create` method which gets called inside `ServicesManager` to instantiate the service. In the app, you can get access to a registered service via the `services` property of the `ServicesManager`.
Services manager is the single point of service registration. Each service needs
to implement a `create` method which gets called inside `ServicesManager` to
instantiate the service. In the app, you can get access to a registered service
via the `services` property of the `ServicesManager`.
## Skeleton
*Simplified* skeleton of `ServicesManager` is shown below. There are two public methods:
_Simplified_ skeleton of `ServicesManager` is shown below. There are two public
methods:
- `registerService`: registering a new service with/without a configuration
- `registerServices`: registering batch of services
```js
export default class ServicesManager {
constructor(commandsManager) {
this._commandsManager = commandsManager
this.services = {}
this.registeredServiceNames = []
this._commandsManager = commandsManager;
this.services = {};
this.registeredServiceNames = [];
}
registerService(service, configuration = {}) {
@@ -27,10 +33,10 @@ export default class ServicesManager {
this.services[service.name] = service.create({
configuration,
commandsManager: this._commandsManager,
})
});
/* Track service registration */
this.registeredServiceNames.push(service.name)
this.registeredServiceNames.push(service.name);
}
registerServices(services) {
@@ -39,32 +45,32 @@ export default class ServicesManager {
}
```
## Default Registered Services
By default, `OHIF-v3` registers the following services in the `appInit`.
```js title="platform/viewer/src/appInit.js"
servicesManager.registerServices([
UINotificationService,
UIModalService,
UIDialogService,
UIViewportDialogService,
MeasurementService,
DisplaySetService,
ToolBarService,
ViewportGridService,
HangingProtocolService,
CineService,
]);
UINotificationService,
UIModalService,
UIDialogService,
UIViewportDialogService,
MeasurementService,
DisplaySetService,
ToolBarService,
ViewportGridService,
HangingProtocolService,
CineService,
]);
```
## Service Architecture
If you take a look at the folder of each service implementation above, you will
find out that services need to be exported as an object with `name` and `create` method.
For instance, `ToolbarService` is exported as:
If you take a look at the folder of each service implementation above, you will
find out that services need to be exported as an object with `name` and `create`
method.
For instance, `ToolBarService` is exported as:
```js title="platform/core/src/services/ToolBarService/index.js"
import ToolBarService from './ToolBarService';
@@ -77,43 +83,45 @@ export default {
};
```
and the implementation of `ToolbarService` lies in the same folder at `./ToolbarSerivce.js`.
and the implementation of `ToolBarService` lies in the same folder at
`./ToolbarSerivce.js`.
> Note, the create method is critical for any custom service that you write and
> want to add to the list of services
## Accessing Services
Throughout the app you can use `services` property of the service manager to access
the desired service.
For instance in the `PanelMeasurementTableTracking` which is the right panel in the
`longitudinal` mode, we have the *simplified code below* for downloading the drawn measurements.
Throughout the app you can use `services` property of the service manager to
access the desired service.
For instance in the `PanelMeasurementTableTracking` which is the right panel in
the `longitudinal` mode, we have the _simplified code below_ for downloading the
drawn measurements.
```js
function PanelMeasurementTableTracking({ servicesManager }) {
const { MeasurementService } = servicesManager.services
const { MeasurementService } = servicesManager.services;
/** ... **/
async function exportReport() {
const measurements = MeasurementService.getMeasurements()
const measurements = MeasurementService.getMeasurements();
/** ... **/
downloadCSVReport(measurements, MeasurementService)
downloadCSVReport(measurements, MeasurementService);
}
/** ... **/
return <> /** ... **/ </>
return <> /** ... **/ </>;
}
```
## Registering Custom Services
You might need to write you own custom service in an extension. `preRegistration` hook inside your extension is the place for registering your custom service.
You might need to write you own custom service in an extension.
`preRegistration` hook inside your extension is the place for registering your
custom service.
```js title="extensions/customExtension/src/index.js"
import WrappedBackEndService from './services/backEndService'
import WrappedBackEndService from './services/backEndService';
export default {
// ID of the extension
@@ -121,12 +129,11 @@ export default {
preRegistration({ servicesManager }) {
servicesManager.registerService(WrappedBackEndService(servicesManager));
},
}
};
```
and the logic for your service shall be
```js title="extensions/customExtension/src/services/backEndService/index.js"
import backEndService from './backEndService';
+13 -10
View File
@@ -22,6 +22,15 @@ For instance, in `longitudinal` mode we are using this hook to initialize the
`ToolBarService` and set the window level/width tool to be active and add
buttons to the toolbar.
:::note Tip
In OHIF Version 3.1, there is a new service `ToolGroupService` that is used to
define and manage tools for the group of viewports. This is a new concept
borrowed from the Cornerstone ToolGroup, and you can read more
[here](https://www.cornerstonejs.org/docs/concepts/cornerstone-tools/toolgroups/)
:::
```js
function modeFactory() {
return {
@@ -29,16 +38,10 @@ function modeFactory() {
version: '',
displayName: '',
onModeEnter: ({ servicesManager, extensionManager }) => {
const { ToolBarService } = servicesManager.services;
const { ToolBarService, ToolGroupService } = servicesManager.services;
const interaction = {
groupId: 'primary',
itemId: 'Wwwc',
interactionType: 'tool',
commandOptions: undefined,
};
ToolBarService.recordInteraction(interaction);
// Init Default and SR ToolGroups
initToolGroups(extensionManager, ToolGroupService);
ToolBarService.init(extensionManager);
ToolBarService.addButtons(toolbarButtons);
@@ -65,7 +68,7 @@ This hook is called when the viewer navigate away from the route in the url.
This is the place for cleaning up data, and services by unsubscribing to the
events.
For instance, it can be used to reset the `ToolbarService` which reset the
For instance, it can be used to reset the `ToolBarService` which reset the
toggled buttons.
```js
+2 -5
View File
@@ -58,9 +58,9 @@ function modeFactory() {
],
},
{
namespace: '@ohif/extension-dicom-sr.viewportModule.dicom-sr',
namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
displaySetsToDisplay: [
'@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr',
'@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
],
},
],
@@ -204,9 +204,6 @@ init: async ({
const instance = displaySet.images.find(
image => image.SOPInstanceUID === tool.SOPInstanceUID
);
const { SOPInstanceUID, url } = instance;
displaySet.initialImageIdIndex = displaySet.images.indexOf(instance);
});
MeasurementService.addMeasurement(/**...**/);
@@ -2,205 +2,54 @@
sidebar_position: 4
sidebar_label: Hanging Protocol Service
---
# Hanging Protocol Service
## Overview
`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol engine.
This service handles the arrangement of the images in the viewport.
In short, the registered protocols will get matched with the Series that are available
for the series. Each protocol gets a point, and they are ranked. The winning protocol gets
applied and its settings run for the viewports.
You can read more about hanging protocols [here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). In short
with `OHIF-v3` hanging protocols you can:
`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol
engine. This service handles the arrangement of the images in the viewport. In
short, the registered protocols will get matched with the Series that are
available for the series. Each protocol gets a point, and they are ranked. The
winning protocol gets applied and its settings run for the viewports.
You can read more about hanging protocols
[here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf).
In short with `OHIF-v3` hanging protocols you can:
- Define what layout of the viewport should the viewer starts with (2x2 layout)
- Define which series gets displayed in which position of the layout
- Apply certain initial viewport settings; e.g., inverting the contrast
- Enable certain tools based on what series are displayed: link prostate T2 and ADC MRI.
## Skeleton of A Hanging Protocol
You can find the skeleton of the hanging protocols here:
```js
const deafultProtocol = {
id: 'defaultProtocol',
locked: true,
hasUpdatedPriorsInformation: false,
name: 'Default',
createdDate: '2021-02-23T19:22:08.894Z',
modifiedDate: '2021-02-23T19:22:08.894Z',
availableTo: {},
editableBy: {},
protocolMatchingRules: [],
stages: [
{
id: 'nwzau7jDkEkL8djfr',
name: 'oneByOne',
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 1,
},
},
viewports: [
{
viewportSettings: [],
imageMatchingRules: [],
seriesMatchingRules: [],
studyMatchingRules: [],
},
],
createdDate: '2021-02-23T19:22:08.894Z',
},
],
numberOfPriorsReferenced: -1,
};
```
Let's discuss each property in depth.
- `id`: unique identifier for the protocol
- `protocolMatchingRules`: A list of criteria for the protocol along with the provided points for ranking.
- `weight`: weight for the matching rule. Eventually, all the registered protocols get sorted based on the weights, and the winning
protocol gets applied to the viewer.
- `attriubte`: tag that needs to be matched against. This can be either Study-level metadata or a custom attribute. [Learn more about custom attribute matching](#custom-attribute)
- `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be
[`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`]
A sample of the matching rule is:
```js
{
id: 'wauZK2QNEfDPwcAQo',
weight: 1,
attribute: 'StudyInstanceUID',
constraint: {
equals: {
value: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11',
},
},
required: true,
}
```
- `stages`: Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules.
Therefore, the `stages` property is array of objects, each object being one stage.
- `viewportStructure`: Defines the layout of the viewer. You can define the number of `rows` and `columns`.
There should be `rows * columns` number of viewport configuration in the `viewports` property. Note that order of viewports are rows first then columns.
- `viewportSettings`: custom settings to be applied to the viewport. This can be a `voi` being applied
to the viewer or a tool to get enabled. We will discuss viewport-specific settings [below](#viewport-settings)
- `imageMatchingRules (comming soon)`: setting the image slice for the viewport.
- `seriesMatchingRules`: the most important rule that matches series in the viewport. For instance, the following stage
configuration will create a one-by-two layout and put the series whose description contains `t2` on the left, and a series with
description that contains `adc` on the right. (order of viewports are rows, first then columns)
```js
stages: [
{
id: 'hYbmMy3b7pz7GLiaT',
name: 'oneByThree',
viewportStructure: {
type: 'grid',
properties: {
rows: 1,
columns: 2,
},
},
viewports: [
// viewport 1
{
viewportSettings: [],
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'vSjk7NCYjtdS3XZAw',
weight: 1,
attribute: 'SeriesDescription',
constraint: {
contains: {
value: 't2',
},
},
required: false,
},
],
studyMatchingRules: [],
},
// viewport 2
{
viewportSettings: [],
imageMatchingRules: [],
seriesMatchingRules: [
{
id: 'vSjk7NCYjtdS3XZAw',
weight: 1,
attribute: 'SeriesDescription',
constraint: {
contains: {
value: 'ADC',
},
},
required: true,
},
],
studyMatchingRules: [],
},
],
},
]
```
- Enable certain tools based on what series are displayed: link prostate T2 and
ADC MRI.
## Events
There are two events that get publish in `HangingProtocolService`:
There are two events that get publish in `HangingProtocolService`:
| Event | Description |
| ------------ | -------------------------------------------------------------------- |
| NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` |
| STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols |
## API
- `getState`: returns an array: `[matchDetails, hpAlreadyApplied]`:
- `matchDetails`: matching details for the series
- `hpAlreadyApplied`: An array which tracks whether HPServices has been applied on each viewport.
- `hpAlreadyApplied`: An array which tracks whether HPServices has been
applied on each viewport.
- `addProtocols`: adds provided protocols to the list of registered protocols for matching
- `addProtocols`: adds provided protocols to the list of registered protocols
for matching
- `run(studyMetaData, protocol)`: runs the HPService with the provided studyMetaData and optional protocol.
If protocol is not given, HP Matching engine will search all the registered protocols for the best matching one based on the constraints.
- `run(studyMetaData, protocol)`: runs the HPService with the provided
studyMetaData and optional protocol. If protocol is not given, HP Matching
engine will search all the registered protocols for the best matching one
based on the constraints.
- `addCustomAttribute`: adding a custom attribute for matching. (see below)
- `addCustomViewportSetting`: adding a custom setting to a viewport (initial `voi`). Below, we explain
in detail how to add custom viewport settings via protocol definitions. `addCustomViewportSetting` is another way to set
these settings which is exposed by API
-
Default initialization of the modes handles running the `HangingProtocolService`
## Custom Attribute
@@ -208,9 +57,13 @@ In some situations, you might want to match based on a custom attribute and not
if you have assigned a `timepointId` to each study, and you want to match based on it.
Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching.
There are various ways that you can let `HangingProtocolService` know of you custom attribute.
We will show how to add it inside the mode configuration.
In some situations, you might want to match based on a custom attribute and not
the DICOM tags. For instance, if you have assigned a `timepointId` to each study
and you want to match based on it. Good news is that, in `OHIF-v3` you can
define you custom attribute and use it for matching.
There are various ways that you can let `HangingProtocolService` know of you
custom attribute. We will show how to add it inside the mode configuration.
```js
const deafultProtocol = {
@@ -233,13 +86,13 @@ const deafultProtocol = {
/** ... **/
],
numberOfPriorsReferenced: -1,
}
};
// Custom function for custom attribute
const getTimePointUID = (metaData) => {
const getTimePointUID = metaData => {
// requesting the timePoint Id
return myBackEndAPI(metaData)
}
return myBackEndAPI(metaData);
};
function modeFactory() {
return {
@@ -249,49 +102,51 @@ function modeFactory() {
{
path: 'myModeRoute',
init: async ({}) => {
const { DicomMetadataStore, HangingProtocolService } =
servicesManager.services
const {
DicomMetadataStore,
HangingProtocolService,
} = servicesManager.services;
const onSeriesAdded = ({
StudyInstanceUID,
madeInClient = false,
}) => {
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID)
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
// Adding custom attribute to the hangingprotocol
HangingProtocolService.addCustomAttribute(
'timepoint',
'timepoint',
(metaData) => getFirstMeasurementSeriesInstanceUID(metaData)
)
metaData => getFirstMeasurementSeriesInstanceUID(metaData)
);
HangingProtocolService.run(studyMetadata)
}
HangingProtocolService.run(studyMetadata);
};
DicomMetadataStore.subscribe(
DicomMetadataStore.EVENTS.SERIES_ADDED,
onSeriesAdded
)
);
},
},
],
/** ... **/
}
};
}
```
## Viewport Settings
You can define custom settings to be applied to each viewport. There
are two types of settings:
You can define custom settings to be applied to each viewport. There are two
types of settings:
- `viewport settings`: Currently we support two viewport settings
- `voi`: applying an initial `voi` by setting the windowWidth and windowCenter
- `inverted`: inverting the viewport color (e.g., for PET images)
- `props settings`: Running commands after the first render; e.g., enabling the link tool
- `props settings`: Running commands after the first render; e.g., enabling the
link tool
Examples of each settings are :
@@ -307,7 +162,7 @@ viewportSettings: [
commandName: 'setToolActive',
type: 'props',
},
]
];
```
and
@@ -324,5 +179,5 @@ viewportSettings: [
commandName: '',
type: 'viewport',
},
]
];
```
@@ -7,7 +7,7 @@ sidebar_label: Toolbar Service
## Overview
`ToolbarService` handles the toolbar section buttons, and what happens when a
`ToolBarService` handles the toolbar section buttons, and what happens when a
button is clicked by the user.
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
@@ -19,12 +19,12 @@ button is clicked by the user.
| Event | Description |
| ----------------------- | ---------------------------------------------------------------------- |
| TOOL_BAR_MODIFIED | Fires when a button is added/removed to the toolbar |
| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolbarService state is modified |
| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolBarService state is modified |
## API
- `recordInteraction(interaction)`: executes the provided interaction which is
an object providing the following properties to the ToolbarService:
an object providing the following properties to the ToolBarService:
- `interactionType`: can be `tool`, `toggle` and `action`. We will discuss
more each type below.
@@ -47,7 +47,7 @@ button is clicked by the user.
## State
ToolbarService has an internal state that gets updated per tool interaction and
ToolBarService has an internal state that gets updated per tool interaction and
tracks the active toolId, state of the buttons that have toggled state, and the
group buttons and which tool in each group is active.
@@ -70,10 +70,10 @@ interaction object.
- `tool`: setting a tool to be active; e.g., measurement tools
- `toggle`: toggling state of a tool; e.g., viewport link (sync)
- `action`: performs a registered action outside of the ToolbarService; e.g.,
- `action`: performs a registered action outside of the ToolBarService; e.g.,
capture
A _simplified_ implementation of the ToolbarService is:
A _simplified_ implementation of the ToolBarService is:
```js
export default class ToolBarService {
@@ -115,25 +115,32 @@ The simplest toolbarButtons definition has the following properties:
```js
{
id: 'Zoom',
type: 'ohif.radioGroup',
props: {
type: 'tool',
icon: 'tool-zoom',
label: 'Zoom',
commandOptions: { toolName: 'Zoom' },
},
},
"id": "Zoom",
"type": "ohif.radioGroup",
"props": {
"type": "tool",
"icon": "tool-zoom",
"label": "Zoom",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Zoom"
},
"context": "CORNERSTONE"
}
]
}
}
```
| 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 behaviour | "tool", "toggle", "action" |
| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` |
| `commandOptions` | (optional) Options to pass the target `commandName` | \* |
| `icon` | A string name for an icon supported by the consuming application. | \* |
| `label` | User/display friendly to show in UI | \* |
| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` |
There are three main types of toolbar buttons:
@@ -159,58 +166,91 @@ to create `MeasurementTools` nested button
```js title="modes/longitudinal/src/toolbarButtons.js"
{
id: 'MeasurementTools',
type: 'ohif.splitButton',
props: {
groupId: 'MeasurementTools',
isRadio: true,
primary: {
id: 'Length',
icon: 'tool-length',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Length',
}
"id": "MeasurementTools",
"type": "ohif.splitButton",
"props": {
"groupId": "MeasurementTools",
"isRadio": true,
"primary": {
"id": "Length",
"icon": "tool-length",
"label": "Length",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Length"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRLength",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Length"
},
secondary: {
icon: 'chevron-down',
label: '',
isActive: true,
tooltip: 'More Measure Tools',
"secondary": {
"icon": "chevron-down",
"label": "",
"isActive": true,
"tooltip": "More Measure Tools"
},
items: [
// Length tool
"items": [
{
id: 'Length',
icon: 'tool-length',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Length',
}
"id": "Bidirectional",
"icon": "tool-bidirectional",
"label": "Bidirectional",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "Bidirectional"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRBidirectional",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Bidirectional Tool"
},
// Bidirectional tool
{
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Length',
type: 'tool',
commandOptions: {
toolName: 'Bidirectional',
}
"id": "ArrowAnnotate",
"icon": "tool-annotate",
"label": "Annotation",
"type": "tool",
"commands": [
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "ArrowAnnotate"
},
"context": "CORNERSTONE"
},
{
"commandName": "setToolActive",
"commandOptions": {
"toolName": "SRArrowAnnotate",
"toolGroupId": "SRToolGroup"
},
"context": "CORNERSTONE"
}
],
"tooltip": "Arrow Annotate"
},
// Ellipse tool
{
id: 'EllipticalRoi',
icon: 'tool-elipse',
label: 'Ellipse',
type: 'tool',
commandOptions: {
toolName: 'EllipticalRoi',
}
},
],
},
]
}
}
```
@@ -17,7 +17,7 @@ We maintain the following non-ui Services:
- [DicomMetadata Store](./../data/DicomMetadataStore.md)
- [DisplaySet Service](./../data/DisplaySetService.md)
- [Hanging Protocol Service](../data/HangingProtocolService.md)
- [Toolbar Service](../data/ToolbarService.md)
- [Toolbar Service](../data/ToolBarService.md)
- [Measurement Service](../data/MeasurementService.md)
## Service Architecture
@@ -83,7 +83,7 @@ The following services is available in the `OHIF-v3`.
</tr>
<tr>
<td>
<a href="./data/ToolbarService">
<a href="./data/ToolBarService">
ToolBarService (NEW)
</a>
</td>
@@ -2,27 +2,25 @@
sidebar_position: 6
sidebar_label: Viewport Grid Service
---
# Viewport Grid Service
## Overview
This is a new UI service, that handles the grid layout of the viewer.
## Interface
For a more detailed look on the options and return values each of these methods
is expected to support, [check out it's interface in `@ohif/core`][interface]
| API Member | Description |
| -------------------------------------------------------------------- | --------------------------------------------------- |
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) |
| `setDisplaysetForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
| `setLayout({numCols, numRows})` | Sets rows and columns |
| `reset()` | Resets the default states |
| API Member | Description |
| --------------------------------------------------------------------- | --------------------------------------------------- |
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) |
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
| `setLayout({numCols, numRows})` | Sets rows and columns |
| `reset()` | Resets the default states |
## Implementations
@@ -32,7 +30,6 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
`*` - Denotes maintained by OHIF
## State
```js
@@ -49,5 +46,5 @@ const DEFAULT_STATE = {
*/
],
activeViewportIndex: 0,
}
};
```
+8 -8
View File
@@ -25,11 +25,12 @@ of our medical image web viewers from the start. The summary of changes includes
- Our vision is that technical people focus of developing extensions which
provides core functionalities, and experts to build modes by picking the
appropriate functionalities from each extension.
- Migrated all the `cornerstone-core` and `cornerstone-tools` usage to the newly released `Cornerstone3D`.
* UI has been completely redesigned with modularity and workflow modes in mind.
* New UI components have been built with Tailwind CSS
* Redux store has been removed from the viewer in favour of services backed by
React's Context API
React's Context **API**
Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
@@ -86,6 +87,10 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
<td align="left">White-labelling: Easily replace the OHIF Logo with your logo</td>
<td align="center">✅</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">DICOM PDF support</td>
<td align="center">✅</td>
</tr>
<tr>
<td align="left">DICOM Whole-slide imaging viewport</td>
@@ -97,11 +102,6 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
<td align="center">🔜</td>
<td align="center">Not Started</td>
</tr>
<tr>
<td align="left">DICOM PDF support</td>
<td align="center">🔜</td>
<td align="center">Not Started</td>
</tr>
<tr>
<td align="left">Displaying non-renderable DICOM as HTML</td>
<td align="center">🔜</td>
@@ -129,8 +129,8 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
</tr>
<tr>
<td align="left">VTK Extension + MIP / MPR layout</td>
<td align="center">❌</td>
<td align="center">Other plans that involves amazing news soon!</td>
<td align="center">🔜</td>
<td align="center">3D rendering and 3D annotation tools via Cornerstone3D</td>
</tr>
<tr>
<td align="left">UMD Build (Embedded Viewer). </td>
+9
View File
@@ -11,6 +11,15 @@ and other resources that we have provided to the community in the past:
## 2022
### The Imaging Network Ontario - Remote
The Imaging Network Ontario (ImNO) is an annual symposium that brings together
medical imaging researchers and scientists from across Canada to share
knowledge, ideas, and experiences.
[[Slides]](https://docs.google.com/presentation/d/18XZDon4-Sitc2a70V5sFyhyUVZI_mIgfXHGtdxhZMjE/edit?usp=sharing)
[[Video]](https://vimeo.com/691134870/ad7d308a44)
### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md)
The Project Week is a week-long hackathon of hands-on activity in which medical