fix(DICOM Overlay): The overlay data wasn't being refreshed on change (#3793)
This commit is contained in:
parent
c4e22c251f
commit
00e751933a
@ -29,6 +29,7 @@ import { id } from './id';
|
|||||||
import * as csWADOImageLoader from './initWADOImageLoader.js';
|
import * as csWADOImageLoader from './initWADOImageLoader.js';
|
||||||
import { measurementMappingUtils } from './utils/measurementServiceMappings';
|
import { measurementMappingUtils } from './utils/measurementServiceMappings';
|
||||||
import type { PublicViewportOptions } from './services/ViewportService/Viewport';
|
import type { PublicViewportOptions } from './services/ViewportService/Viewport';
|
||||||
|
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport');
|
return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport');
|
||||||
@ -140,5 +141,6 @@ export {
|
|||||||
CornerstoneExtensionTypes as Types,
|
CornerstoneExtensionTypes as Types,
|
||||||
toolNames,
|
toolNames,
|
||||||
getActiveViewportEnabledElement,
|
getActiveViewportEnabledElement,
|
||||||
|
ImageOverlayViewerTool,
|
||||||
};
|
};
|
||||||
export default cornerstoneExtension;
|
export default cornerstoneExtension;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { VolumeViewport, metaData } from '@cornerstonejs/core';
|
import { VolumeViewport, metaData, utilities } from '@cornerstonejs/core';
|
||||||
import { utilities } from '@cornerstonejs/core';
|
|
||||||
import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types';
|
import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types';
|
||||||
import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools';
|
import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools';
|
||||||
import { guid } from '@ohif/core/src/utils';
|
import { guid } from '@ohif/core/src/utils';
|
||||||
|
import OverlayPlaneModuleProvider from './OverlayPlaneModuleProvider';
|
||||||
|
|
||||||
interface CachedStat {
|
interface CachedStat {
|
||||||
color: number[]; // [r, g, b, a]
|
color: number[]; // [r, g, b, a]
|
||||||
@ -27,8 +27,12 @@ interface CachedStat {
|
|||||||
*/
|
*/
|
||||||
class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
||||||
static toolName = 'ImageOverlayViewer';
|
static toolName = 'ImageOverlayViewer';
|
||||||
private _cachedOverlayMetadata: Map<string, any[]> = new Map();
|
|
||||||
private _cachedStats: { [key: string]: CachedStat } = {};
|
/**
|
||||||
|
* The overlay plane module provider add method is exposed here to be used
|
||||||
|
* when updating the overlay for this tool to use for displaying data.
|
||||||
|
*/
|
||||||
|
public static addOverlayPlaneModule = OverlayPlaneModuleProvider.add;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
toolProps = {},
|
toolProps = {},
|
||||||
@ -42,10 +46,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
super(toolProps, defaultToolProps);
|
super(toolProps, defaultToolProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetToolDisabled = (): void => {
|
onSetToolDisabled = (): void => { };
|
||||||
this._cachedStats = {};
|
|
||||||
this._cachedOverlayMetadata = new Map();
|
|
||||||
};
|
|
||||||
|
|
||||||
protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string {
|
protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string {
|
||||||
if (viewport instanceof VolumeViewport) {
|
if (viewport instanceof VolumeViewport) {
|
||||||
@ -64,9 +65,8 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlays =
|
const overlayMetadata = metaData.get('overlayPlaneModule', imageId);
|
||||||
this._cachedOverlayMetadata.get(imageId) ??
|
const overlays = overlayMetadata?.overlays;
|
||||||
metaData.get('overlayPlaneModule', imageId)?.overlays;
|
|
||||||
|
|
||||||
// no overlays
|
// no overlays
|
||||||
if (!overlays?.length) {
|
if (!overlays?.length) {
|
||||||
@ -79,9 +79,10 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
overlay.y ||= 0;
|
overlay.y ||= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
this._cachedOverlayMetadata.set(imageId, overlays);
|
// Will clear cached stat data when the overlay data changes
|
||||||
|
ImageOverlayViewerTool.addOverlayPlaneModule(imageId, overlayMetadata);
|
||||||
|
|
||||||
this._getCachedStat(imageId, overlays, this.configuration.fillColor).then(cachedStat => {
|
this._getCachedStat(imageId, overlayMetadata, this.configuration.fillColor).then(cachedStat => {
|
||||||
cachedStat.overlays.forEach(overlay => {
|
cachedStat.overlays.forEach(overlay => {
|
||||||
this._renderOverlay(enabledElement, svgDrawingHelper, overlay);
|
this._renderOverlay(enabledElement, svgDrawingHelper, overlay);
|
||||||
});
|
});
|
||||||
@ -152,15 +153,18 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
|
|
||||||
private async _getCachedStat(
|
private async _getCachedStat(
|
||||||
imageId: string,
|
imageId: string,
|
||||||
overlayMetadata: any[],
|
overlayMetadata,
|
||||||
color: number[]
|
color: number[]
|
||||||
): Promise<CachedStat> {
|
): Promise<CachedStat> {
|
||||||
if (this._cachedStats[imageId] && this._isSameColor(this._cachedStats[imageId].color, color)) {
|
const missingOverlay = overlayMetadata.overlays.filter(
|
||||||
return this._cachedStats[imageId];
|
overlay => overlay.pixelData && !overlay.dataUrl
|
||||||
|
);
|
||||||
|
if (missingOverlay.length === 0) {
|
||||||
|
return overlayMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlays = await Promise.all(
|
const overlays = await Promise.all(
|
||||||
overlayMetadata
|
overlayMetadata.overlays
|
||||||
.filter(overlay => overlay.pixelData)
|
.filter(overlay => overlay.pixelData)
|
||||||
.map(async (overlay, idx) => {
|
.map(async (overlay, idx) => {
|
||||||
let pixelData = null;
|
let pixelData = null;
|
||||||
@ -178,7 +182,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
|
|
||||||
const dataUrl = this._renderOverlayToDataUrl(
|
const dataUrl = this._renderOverlayToDataUrl(
|
||||||
{ width: overlay.columns, height: overlay.rows },
|
{ width: overlay.columns, height: overlay.rows },
|
||||||
color,
|
overlay.color || color,
|
||||||
pixelData
|
pixelData
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -190,13 +194,9 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
overlayMetadata.overlays = overlays;
|
||||||
|
|
||||||
this._cachedStats[imageId] = {
|
return overlayMetadata;
|
||||||
color: color,
|
|
||||||
overlays: overlays.filter(overlay => overlay),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this._cachedStats[imageId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { metaData } from '@cornerstonejs/core';
|
||||||
|
|
||||||
|
const _cachedOverlayMetadata: Map<string, any[]> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Overlay Viewer tool is not a traditional tool that requires user interactin.
|
||||||
|
* But it is used to display Pixel Overlays. And it will provide toggling capability.
|
||||||
|
*
|
||||||
|
* The documentation for Overlay Plane Module of DICOM can be found in [C.9.2 of
|
||||||
|
* Part-3 of DICOM standard](https://dicom.nema.org/medical/dicom/2018b/output/chtml/part03/sect_C.9.2.html)
|
||||||
|
*
|
||||||
|
* Image Overlay rendered by this tool can be toggled on and off using
|
||||||
|
* toolGroup.setToolEnabled() and toolGroup.setToolDisabled()
|
||||||
|
*/
|
||||||
|
const OverlayPlaneModuleProvider = {
|
||||||
|
/** Adds the metadata for overlayPlaneModule */
|
||||||
|
add: (imageId, metadata) => {
|
||||||
|
if (_cachedOverlayMetadata.get(imageId) === metadata) {
|
||||||
|
// This is a no-op here as the tool re-caches the data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_cachedOverlayMetadata.set(imageId, metadata);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Standard getter for metadata */
|
||||||
|
get: (type: string, query: string | string[]) => {
|
||||||
|
if (Array.isArray(query)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type !== 'overlayPlaneModule') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return _cachedOverlayMetadata.get(query);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Needs to be higher priority than default provider
|
||||||
|
metaData.addProvider(OverlayPlaneModuleProvider.get, 10_000);
|
||||||
|
|
||||||
|
export default OverlayPlaneModuleProvider;
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"preinstall": "node preinstall.js",
|
"preinstall": "node preinstall.js",
|
||||||
"start": "yarn run dev",
|
"start": "yarn run dev",
|
||||||
"test": "yarn run test:unit",
|
"test": "yarn run test:unit",
|
||||||
"test:data": "git submodule update --init",
|
"test:data": "git submodule update --init -r",
|
||||||
"test-watch": "jest --collectCoverage --watchAll",
|
"test-watch": "jest --collectCoverage --watchAll",
|
||||||
"test:unit": "jest --collectCoverage",
|
"test:unit": "jest --collectCoverage",
|
||||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||||
|
|||||||
@ -154,7 +154,7 @@ module.exports = (env, argv) => {
|
|||||||
{
|
{
|
||||||
directory: '../../testdata',
|
directory: '../../testdata',
|
||||||
staticOptions: {
|
staticOptions: {
|
||||||
extensions: ['gz', 'br'],
|
extensions: ['gz', 'br', 'mht'],
|
||||||
index: ['index.json.gz', 'index.mht.gz'],
|
index: ['index.json.gz', 'index.mht.gz'],
|
||||||
redirect: true,
|
redirect: true,
|
||||||
setHeaders,
|
setHeaders,
|
||||||
|
|||||||
2
testdata
2
testdata
@ -1 +1 @@
|
|||||||
Subproject commit 4d59660c2883ed749a680e5fb6d4624ab54c9422
|
Subproject commit c9892af83d5dc4b06fac6ff1ca77246467571ec0
|
||||||
Loading…
Reference in New Issue
Block a user