fix(viewports): The display of linked viewports during drag and drop has a race (#3286)
* fix: The display of linked viewports during drag and drop has a race * PR review comments * fix: Segmentation display two up * Removing console logs * Fix the blank viewport can have stuff added to it * Fix the null name on HP module * Fix the navigate to initial image * Fix the nth interleave loader * Fix the unit tests * PR comments - docs mostly * fix: Exception thrown on change displayset after double click
This commit is contained in:
parent
ca3b83b2b6
commit
5e42a42b5f
@ -31,8 +31,6 @@ async function _hydrateSEGDisplaySet({
|
|||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
|
||||||
|
|
||||||
// Todo: fix this after we have a better way for stack viewport segmentations
|
// Todo: fix this after we have a better way for stack viewport segmentations
|
||||||
|
|
||||||
// check every viewport in the viewports to see if the displaySetInstanceUID
|
// check every viewport in the viewports to see if the displaySetInstanceUID
|
||||||
@ -50,7 +48,7 @@ async function _hydrateSEGDisplaySet({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (shouldDisplaySeg) {
|
if (shouldDisplaySeg) {
|
||||||
viewportGridService.setDisplaySetsForViewport({
|
updatedViewports.push({
|
||||||
viewportIndex: index,
|
viewportIndex: index,
|
||||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
||||||
viewportOptions: {
|
viewportOptions: {
|
||||||
@ -62,6 +60,9 @@ async function _hydrateSEGDisplaySet({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Do the entire update at once
|
||||||
|
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
utilities as csUtils,
|
utilities as csUtils,
|
||||||
CONSTANTS,
|
CONSTANTS,
|
||||||
} from '@cornerstonejs/core';
|
} from '@cornerstonejs/core';
|
||||||
import { Services } from '@ohif/core';
|
|
||||||
|
|
||||||
import { setEnabledElement } from '../state';
|
import { setEnabledElement } from '../state';
|
||||||
|
|
||||||
@ -23,9 +22,6 @@ import {
|
|||||||
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
|
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
|
||||||
|
|
||||||
import { CornerstoneViewportService } from '../services/ViewportService/CornerstoneViewportService';
|
|
||||||
import Presentation from '../types/Presentation';
|
|
||||||
|
|
||||||
const STACK = 'stack';
|
const STACK = 'stack';
|
||||||
|
|
||||||
function areEqual(prevProps, nextProps) {
|
function areEqual(prevProps, nextProps) {
|
||||||
@ -407,7 +403,6 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
lutPresentation:
|
lutPresentation:
|
||||||
lutPresentationStore[presentationIds?.lutPresentationId],
|
lutPresentationStore[presentationIds?.lutPresentationId],
|
||||||
};
|
};
|
||||||
console.log('Using presentations', presentations);
|
|
||||||
|
|
||||||
cornerstoneViewportService.setViewportData(
|
cornerstoneViewportService.setViewportData(
|
||||||
viewportIndex,
|
viewportIndex,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const mpr: Types.HangingProtocol.Protocol = {
|
|||||||
// Unknown number of priors referenced - so just match any study
|
// Unknown number of priors referenced - so just match any study
|
||||||
numberOfPriorsReferenced: 0,
|
numberOfPriorsReferenced: 0,
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
imageLoadStrategy: 'interleaveTopToBottom',
|
imageLoadStrategy: 'nth',
|
||||||
callbacks: {
|
callbacks: {
|
||||||
// Switches out of MPR mode when the layout change button is used
|
// Switches out of MPR mode when the layout change button is used
|
||||||
onLayoutChange: [
|
onLayoutChange: [
|
||||||
@ -303,11 +303,11 @@ const mprAnd3DVolumeViewport = {
|
|||||||
function getHangingProtocolModule() {
|
function getHangingProtocolModule() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'mpr',
|
name: 'mpr',
|
||||||
protocol: mpr,
|
protocol: mpr,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: mprAnd3DVolumeViewport.id,
|
name: mprAnd3DVolumeViewport.id,
|
||||||
protocol: mprAnd3DVolumeViewport,
|
protocol: mprAnd3DVolumeViewport,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -155,9 +155,14 @@ class CornerstoneViewportService extends PubSubService
|
|||||||
/**
|
/**
|
||||||
* Disables the viewport inside the renderingEngine, if no viewport is left
|
* Disables the viewport inside the renderingEngine, if no viewport is left
|
||||||
* it destroys the renderingEngine.
|
* it destroys the renderingEngine.
|
||||||
|
*
|
||||||
|
* This is called when the element goes away entirely - with new viewportId's
|
||||||
|
* created for every new viewport, this will be called whenever the set of
|
||||||
|
* viewports is changed, but NOT when the viewport position changes only.
|
||||||
|
*
|
||||||
* @param viewportIndex
|
* @param viewportIndex
|
||||||
*/
|
*/
|
||||||
public disableElement(viewportIndex: number) {
|
public disableElement(viewportIndex: number): void {
|
||||||
const viewportInfo = this.viewportsInfo.get(viewportIndex);
|
const viewportInfo = this.viewportsInfo.get(viewportIndex);
|
||||||
if (!viewportInfo) {
|
if (!viewportInfo) {
|
||||||
return;
|
return;
|
||||||
@ -254,12 +259,6 @@ class CornerstoneViewportService extends PubSubService
|
|||||||
viewportInfo.setDisplaySetOptions(displaySetOptions);
|
viewportInfo.setDisplaySetOptions(displaySetOptions);
|
||||||
viewportInfo.setViewportData(viewportData);
|
viewportInfo.setViewportData(viewportData);
|
||||||
|
|
||||||
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
|
||||||
viewportData,
|
|
||||||
viewportIndex,
|
|
||||||
viewportId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const element = viewportInfo.getElement();
|
const element = viewportInfo.getElement();
|
||||||
const type = viewportInfo.getViewportType();
|
const type = viewportInfo.getViewportType();
|
||||||
const background = viewportInfo.getBackground();
|
const background = viewportInfo.getBackground();
|
||||||
@ -283,6 +282,15 @@ class CornerstoneViewportService extends PubSubService
|
|||||||
|
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
|
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
|
||||||
|
|
||||||
|
// The broadcast event here ensures that listeners have a valid, up to date
|
||||||
|
// viewport to access. Doing it too early can result in exceptions or
|
||||||
|
// invalid data.
|
||||||
|
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
||||||
|
viewportData,
|
||||||
|
viewportIndex,
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCornerstoneViewport(
|
public getCornerstoneViewport(
|
||||||
@ -386,11 +394,7 @@ class CornerstoneViewportService extends PubSubService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is a bug in CS3D that the setStack does not
|
viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||||
// navigate to the desired image.
|
|
||||||
viewport.setStack(imageIds, 0).then(() => {
|
|
||||||
// The scroll, however, works fine in CS3D
|
|
||||||
viewport.scroll(initialImageIndexToUse);
|
|
||||||
viewport.setProperties(properties);
|
viewport.setProperties(properties);
|
||||||
const camera = presentations.positionPresentation?.camera;
|
const camera = presentations.positionPresentation?.camera;
|
||||||
if (camera) viewport.setCamera(camera);
|
if (camera) viewport.setCamera(camera);
|
||||||
@ -621,6 +625,10 @@ class CornerstoneViewportService extends PubSubService
|
|||||||
|
|
||||||
const viewportInfo = this.getViewportInfo(viewport.id);
|
const viewportInfo = this.getViewportInfo(viewport.id);
|
||||||
|
|
||||||
|
if (!viewportInfo) {
|
||||||
|
console.warn('Viewport info not defined for', viewport.id);
|
||||||
|
}
|
||||||
|
|
||||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id);
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id);
|
||||||
csToolsUtils.segmentation.triggerSegmentationRender(toolGroup.id);
|
csToolsUtils.segmentation.triggerSegmentationRender(toolGroup.id);
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@ const viewportIdVolumeInputArrayMap = new Map<string, unknown[]>();
|
|||||||
export default function interleaveNthLoader({
|
export default function interleaveNthLoader({
|
||||||
data: { viewportId, volumeInputArray },
|
data: { viewportId, volumeInputArray },
|
||||||
displaySetsMatchDetails,
|
displaySetsMatchDetails,
|
||||||
viewportMatchDetails: matchDetails,
|
|
||||||
}) {
|
}) {
|
||||||
viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray);
|
viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray);
|
||||||
|
|
||||||
@ -29,6 +28,7 @@ export default function interleaveNthLoader({
|
|||||||
const volume = cache.getVolume(volumeId);
|
const volume = cache.getVolume(volumeId);
|
||||||
|
|
||||||
if (!volume) {
|
if (!volume) {
|
||||||
|
console.log("interleaveNthLoader::No volume, can't load it");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,33 +39,6 @@ export default function interleaveNthLoader({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The following is checking if all the viewports that were matched in the HP has been
|
|
||||||
* successfully created their cornerstone viewport or not. Todo: This can be
|
|
||||||
* improved by not checking it, and as soon as the matched DisplaySets have their
|
|
||||||
* volume loaded, we start the loading, but that comes at the cost of viewports
|
|
||||||
* not being created yet (e.g., in a 10 viewport ptCT fusion, when one ct viewport and one
|
|
||||||
* pt viewport are created we have a guarantee that the volumes are created in the cache
|
|
||||||
* but the rest of the viewports (fusion, mip etc.) are not created yet. So
|
|
||||||
* we can't initiate setting the volumes for those viewports. One solution can be
|
|
||||||
* to add an event when a viewport is created (not enabled element event) and then
|
|
||||||
* listen to it and as the other viewports are created we can set the volumes for them
|
|
||||||
* since volumes are already started loading.
|
|
||||||
*/
|
|
||||||
if (matchDetails.size !== viewportIdVolumeInputArrayMap.size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all the matched volumes are loaded
|
|
||||||
for (const [_, details] of displaySetsMatchDetails.entries()) {
|
|
||||||
const { SeriesInstanceUID } = details;
|
|
||||||
|
|
||||||
// HangingProtocol has matched, but don't have all the volumes created yet, so return
|
|
||||||
if (!Array.from(volumeIdMapsToLoad.values()).includes(SeriesInstanceUID)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice();
|
const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice();
|
||||||
// get volumes from cache
|
// get volumes from cache
|
||||||
const volumes = volumeIds.map(volumeId => {
|
const volumes = volumeIds.map(volumeId => {
|
||||||
|
|||||||
@ -290,7 +290,7 @@ const defaultProtocol = {
|
|||||||
function getHangingProtocolModule() {
|
function getHangingProtocolModule() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: defaultProtocol.id,
|
name: defaultProtocol.id,
|
||||||
protocol: defaultProtocol,
|
protocol: defaultProtocol,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import hpMN from './hpMN';
|
|||||||
|
|
||||||
const hangingProtocols = [
|
const hangingProtocols = [
|
||||||
{
|
{
|
||||||
id: '@ohif/hp-extension.mn',
|
name: '@ohif/hp-extension.mn',
|
||||||
protocol: hpMN,
|
protocol: hpMN,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -323,7 +323,7 @@ const ptCT = {
|
|||||||
function getHangingProtocolModule() {
|
function getHangingProtocolModule() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: ptCT.id,
|
name: ptCT.id,
|
||||||
protocol: ptCT,
|
protocol: ptCT,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -206,40 +206,40 @@ describe('ExtensionManager.ts', () => {
|
|||||||
const extension = {
|
const extension = {
|
||||||
id: 'hello-world',
|
id: 'hello-world',
|
||||||
getViewportModule: () => {
|
getViewportModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getSopClassHandlerModule: () => {
|
getSopClassHandlerModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getPanelModule: () => {
|
getPanelModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getToolbarModule: () => {
|
getToolbarModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getCommandsModule: () => {
|
getCommandsModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getLayoutTemplateModule: () => {
|
getLayoutTemplateModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getDataSourcesModule: () => {
|
getDataSourcesModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getHangingProtocolModule: () => {
|
getHangingProtocolModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getContextModule: () => {
|
getContextModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getUtilityModule: () => {
|
getUtilityModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getCustomizationModule: () => {
|
getCustomizationModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
getStateSyncModule: () => {
|
getStateSyncModule: () => {
|
||||||
return [{}];
|
return [{ name: 'test' }];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -283,6 +283,11 @@ export default class ExtensionManager {
|
|||||||
// Default for most extension points,
|
// Default for most extension points,
|
||||||
// Just adds each entry ready for consumption by mode.
|
// Just adds each entry ready for consumption by mode.
|
||||||
extensionModule.forEach(element => {
|
extensionModule.forEach(element => {
|
||||||
|
if (!element.name) {
|
||||||
|
throw new Error(
|
||||||
|
`Extension ID ${extensionId} module ${moduleType} element has no name`
|
||||||
|
);
|
||||||
|
}
|
||||||
const id = `${extensionId}.${moduleType}.${element.name}`;
|
const id = `${extensionId}.${moduleType}.${element.name}`;
|
||||||
element.id = id;
|
element.id = id;
|
||||||
this.modulesMap[id] = element;
|
this.modulesMap[id] = element;
|
||||||
@ -366,10 +371,10 @@ export default class ExtensionManager {
|
|||||||
|
|
||||||
_initHangingProtocolsModule = (extensionModule, extensionId) => {
|
_initHangingProtocolsModule = (extensionModule, extensionId) => {
|
||||||
const { hangingProtocolService } = this._servicesManager.services;
|
const { hangingProtocolService } = this._servicesManager.services;
|
||||||
extensionModule.forEach(({ id, protocol }) => {
|
extensionModule.forEach(({ name, protocol }) => {
|
||||||
if (protocol) {
|
if (protocol) {
|
||||||
// Only auto-register if protocol specified, otherwise let mode register
|
// Only auto-register if protocol specified, otherwise let mode register
|
||||||
hangingProtocolService.addProtocol(id, protocol);
|
hangingProtocolService.addProtocol(name, protocol);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,7 +26,6 @@ class ViewportGridService extends PubSubService {
|
|||||||
public setServiceImplementation({
|
public setServiceImplementation({
|
||||||
getState: getStateImplementation,
|
getState: getStateImplementation,
|
||||||
setActiveViewportIndex: setActiveViewportIndexImplementation,
|
setActiveViewportIndex: setActiveViewportIndexImplementation,
|
||||||
setDisplaySetsForViewport: setDisplaySetsForViewportImplementation,
|
|
||||||
setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation,
|
setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation,
|
||||||
setLayout: setLayoutImplementation,
|
setLayout: setLayoutImplementation,
|
||||||
reset: resetImplementation,
|
reset: resetImplementation,
|
||||||
@ -40,9 +39,6 @@ class ViewportGridService extends PubSubService {
|
|||||||
if (setActiveViewportIndexImplementation) {
|
if (setActiveViewportIndexImplementation) {
|
||||||
this.serviceImplementation._setActiveViewportIndex = setActiveViewportIndexImplementation;
|
this.serviceImplementation._setActiveViewportIndex = setActiveViewportIndexImplementation;
|
||||||
}
|
}
|
||||||
if (setDisplaySetsForViewportImplementation) {
|
|
||||||
this.serviceImplementation._setDisplaySetsForViewport = setDisplaySetsForViewportImplementation;
|
|
||||||
}
|
|
||||||
if (setDisplaySetsForViewportsImplementation) {
|
if (setDisplaySetsForViewportsImplementation) {
|
||||||
this.serviceImplementation._setDisplaySetsForViewports = setDisplaySetsForViewportsImplementation;
|
this.serviceImplementation._setDisplaySetsForViewports = setDisplaySetsForViewportsImplementation;
|
||||||
}
|
}
|
||||||
@ -77,22 +73,13 @@ class ViewportGridService extends PubSubService {
|
|||||||
return this.serviceImplementation._getState();
|
return this.serviceImplementation._getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setDisplaySetsForViewport({
|
public setDisplaySetsForViewport(props) {
|
||||||
viewportIndex,
|
// Just update a single viewport, but use the multi-viewport update for it.
|
||||||
displaySetInstanceUIDs,
|
this.serviceImplementation._setDisplaySetsForViewports([props]);
|
||||||
viewportOptions,
|
|
||||||
displaySetOptions,
|
|
||||||
}) {
|
|
||||||
this.serviceImplementation._setDisplaySetsForViewport({
|
|
||||||
viewportIndex,
|
|
||||||
displaySetInstanceUIDs,
|
|
||||||
viewportOptions,
|
|
||||||
displaySetOptions,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setDisplaySetsForViewports(viewports) {
|
public setDisplaySetsForViewports(props) {
|
||||||
this.serviceImplementation._setDisplaySetsForViewports(viewports);
|
this.serviceImplementation._setDisplaySetsForViewports(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -34,54 +34,85 @@ const DEFAULT_STATE = {
|
|||||||
|
|
||||||
export const ViewportGridContext = createContext(DEFAULT_STATE);
|
export const ViewportGridContext = createContext(DEFAULT_STATE);
|
||||||
|
|
||||||
|
/** A viewport is reuseable if it is the same size as the old
|
||||||
|
* one and has the same display sets, in the same position.
|
||||||
|
* It SHOULD be possible to re-use them at different positions, but
|
||||||
|
* this causes problems with segmentation.
|
||||||
|
*/
|
||||||
|
const isReuseableViewport = (oldViewport, newViewport) => {
|
||||||
|
const sameDiplaySets = isEqual(
|
||||||
|
oldViewport.displaySetInstanceUIDs,
|
||||||
|
newViewport.displaySetInstanceUIDs
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
oldViewport.viewportIndex === newViewport.viewportIndex &&
|
||||||
|
sameDiplaySets &&
|
||||||
|
oldViewport.height === newViewport.height &&
|
||||||
|
oldViewport.width === newViewport.width
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Holds a global viewport counter - used to assign new id's to viewports
|
||||||
|
// Starts at a value above zero so that any of the old viewport id's are
|
||||||
|
// immediately obvious and if we get any index generated viewports, they are
|
||||||
|
// definitely distinct from these ones.
|
||||||
|
let viewportCounter = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a viewport to re-use, and then set the viewportId
|
* Find a viewportId to re-use if possible, preserving the existing
|
||||||
|
* viewport information, OR create a new one if the viewport given isn't
|
||||||
|
* compatible with what was there before.
|
||||||
*
|
*
|
||||||
* @param idSet
|
* @param viewportIdSet
|
||||||
* @param viewport
|
* @param viewport
|
||||||
* @param stateViewports
|
* @param stateViewports
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const reuseViewport = (idSet, viewport, stateViewports) => {
|
const reuseViewportId = (viewportIdSet: Set, viewport, stateViewports) => {
|
||||||
const oldIds = {};
|
|
||||||
for (const oldViewport of stateViewports) {
|
for (const oldViewport of stateViewports) {
|
||||||
const { viewportId: oldId } = oldViewport;
|
const { viewportId: oldId } = oldViewport;
|
||||||
oldIds[oldId] = true;
|
if (!oldId) {
|
||||||
if (!oldId || idSet[oldId]) continue;
|
// This occurs on startup, so skip re-using it
|
||||||
if (
|
|
||||||
!isEqual(
|
|
||||||
oldViewport.displaySetInstanceUIDs,
|
|
||||||
viewport.displaySetInstanceUIDs
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
idSet[oldId] = true;
|
if (viewportIdSet.has(oldId)) {
|
||||||
// TODO re-use viewports once the flickering/wrong size redraw is fixed
|
// oldId is already used - we can't reuse it
|
||||||
// return {
|
continue;
|
||||||
// ...oldViewport,
|
|
||||||
// ...viewport,
|
|
||||||
// viewportOptions: {
|
|
||||||
// ...oldViewport.viewportOptions,
|
|
||||||
|
|
||||||
// viewportId: oldViewport.viewportId,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
// Find a viewport instance number different from earlier viewports having
|
if (isReuseableViewport(oldViewport, viewport)) {
|
||||||
// the same presentationIds as this one would - will be less than 10k
|
viewportIdSet.add(oldId);
|
||||||
// viewports hopefully :-)
|
// This means the old and the new viewport are compatible, and
|
||||||
for (let i = 0; i < 10000; i++) {
|
// since we have gotten here, the viewport ID isn't used, so we
|
||||||
const viewportId = 'viewport-' + i;
|
// are good to reuse it.
|
||||||
if (idSet[viewportId] || oldIds[viewportId]) continue;
|
// This will remember the old viewport options, assuming they are unchanging.
|
||||||
idSet[viewportId] = true;
|
|
||||||
return {
|
return {
|
||||||
|
...oldViewport,
|
||||||
...viewport,
|
...viewport,
|
||||||
viewportId,
|
id: oldId,
|
||||||
viewportOptions: { ...viewport.viewportOptions, viewportId },
|
viewportId: oldId,
|
||||||
|
viewportOptions: {
|
||||||
|
// Update any viewport options from new
|
||||||
|
...viewport.viewportOptions,
|
||||||
|
viewportId: oldId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error('No ID found');
|
}
|
||||||
|
|
||||||
|
// There wasn't an old id found to be reused, so create a new one
|
||||||
|
// Find a viewport instance number different from earlier viewports
|
||||||
|
const viewportId = 'viewport-' + viewportCounter;
|
||||||
|
viewportIdSet.add(viewportId);
|
||||||
|
// Loop over viewport counters in case of a really long lived display
|
||||||
|
viewportCounter = (viewportCounter + 1) % 100000;
|
||||||
|
// viewportOptions is already a copy, so can just update direct
|
||||||
|
viewport.viewportOptions.viewportId = viewportId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...viewport,
|
||||||
|
id: viewportId,
|
||||||
|
viewportId,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ViewportGridProvider({ children, service }) {
|
export function ViewportGridProvider({ children, service }) {
|
||||||
@ -90,53 +121,72 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
case 'SET_ACTIVE_VIEWPORT_INDEX': {
|
case 'SET_ACTIVE_VIEWPORT_INDEX': {
|
||||||
return { ...state, ...{ activeViewportIndex: action.payload } };
|
return { ...state, ...{ activeViewportIndex: action.payload } };
|
||||||
}
|
}
|
||||||
case 'SET_DISPLAYSET_FOR_VIEWPORT': {
|
|
||||||
const payload = action.payload;
|
|
||||||
const { viewportIndex, displaySetInstanceUIDs } = payload;
|
|
||||||
|
|
||||||
// Note: there should be no inheritance happening at this level,
|
|
||||||
// we can't assume the new displaySet can inherit the previous
|
|
||||||
// displaySet's or viewportOptions at all. For instance, dragging
|
|
||||||
// and dropping a SEG/RT displaySet without any viewportOptions
|
|
||||||
// or displaySetOptions should not inherit the previous displaySet's
|
|
||||||
// which might have been a PDF Viewport. The viewport itself
|
|
||||||
// will deal with inheritance if required. Here is just a simple
|
|
||||||
// provider.
|
|
||||||
const viewport = state.viewports[viewportIndex] || {};
|
|
||||||
const viewportOptions = { ...payload.viewportOptions };
|
|
||||||
|
|
||||||
const displaySetOptions = payload.displaySetOptions || [];
|
|
||||||
if (displaySetOptions.length === 0) {
|
|
||||||
// Only copy index 0, as that is all that is currently supported by this
|
|
||||||
// method call.
|
|
||||||
displaySetOptions.push({ ...viewport.displaySetOptions?.[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display sets for multiple viewports.
|
||||||
|
* This is a replacement for the older set display set for viewport (single)
|
||||||
|
* because the old one had race conditions wherein the viewports could
|
||||||
|
* render partially in various ways causing exceptions.
|
||||||
|
*/
|
||||||
|
case 'SET_DISPLAYSETS_FOR_VIEWPORTS': {
|
||||||
|
const { payload } = action;
|
||||||
const viewports = state.viewports.slice();
|
const viewports = state.viewports.slice();
|
||||||
|
|
||||||
let newView = {
|
// Have the initial id set contain all viewports not updated here
|
||||||
...viewport,
|
const viewportIdSet = new Set();
|
||||||
|
viewports.forEach((viewport, index) => {
|
||||||
|
if (!viewport.viewportId) return;
|
||||||
|
const isUpdated = payload.find(
|
||||||
|
newViewport => newViewport.viewportIndex === index
|
||||||
|
);
|
||||||
|
if (isUpdated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewportIdSet.add(viewport.viewportId);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const updatedViewport of payload) {
|
||||||
|
// Use the newly provide viewportOptions and display set options
|
||||||
|
// when provided, and otherwise fall back to the previous ones.
|
||||||
|
// That allows for easy updates of just the display set.
|
||||||
|
const { viewportIndex, displaySetInstanceUIDs } = updatedViewport;
|
||||||
|
const previousViewport = viewports[viewportIndex] || {};
|
||||||
|
const viewportOptions = {
|
||||||
|
...(updatedViewport.viewportOptions ||
|
||||||
|
previousViewport.viewportOptions),
|
||||||
|
};
|
||||||
|
|
||||||
|
const displaySetOptions = updatedViewport.displaySetOptions || [];
|
||||||
|
if (!displaySetOptions.length) {
|
||||||
|
// Copy all the display set options, assuming a full set of displa
|
||||||
|
// set UID's is provided.
|
||||||
|
displaySetOptions.push(...previousViewport.displaySetOptions);
|
||||||
|
if (!displaySetOptions.length) {
|
||||||
|
displaySetOptions.push({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newViewport = {
|
||||||
|
...previousViewport,
|
||||||
displaySetInstanceUIDs,
|
displaySetInstanceUIDs,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
displaySetOptions,
|
displaySetOptions,
|
||||||
viewportLabel: viewportLabels[viewportIndex],
|
viewportLabel: viewportLabels[viewportIndex],
|
||||||
};
|
};
|
||||||
viewportOptions.presentationIds = getPresentationIds(
|
viewportOptions.presentationIds = getPresentationIds(
|
||||||
newView,
|
newViewport,
|
||||||
viewports
|
viewports
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure we assign a viewport id
|
newViewport = reuseViewportId(
|
||||||
newView = reuseViewport({}, newView, state.viewports);
|
viewportIdSet,
|
||||||
console.log(
|
newViewport,
|
||||||
'Creating new viewport',
|
state.viewports
|
||||||
viewportIndex,
|
|
||||||
newView.viewportOptions.viewportId,
|
|
||||||
displaySetInstanceUIDs,
|
|
||||||
displaySetOptions
|
|
||||||
);
|
);
|
||||||
|
newViewport.viewportIndex = previousViewport.viewportIndex;
|
||||||
|
|
||||||
viewports[viewportIndex] = newView;
|
viewports[viewportIndex] = newViewport;
|
||||||
|
}
|
||||||
|
|
||||||
return { ...state, viewports };
|
return { ...state, viewports };
|
||||||
}
|
}
|
||||||
@ -203,13 +253,13 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
|
|
||||||
activeViewportIndexToSet = activeViewportIndexToSet ?? 0;
|
activeViewportIndexToSet = activeViewportIndexToSet ?? 0;
|
||||||
|
|
||||||
const viewportIdSet = {};
|
const viewportIdSet = new Set();
|
||||||
for (
|
for (
|
||||||
let viewportIndex = 0;
|
let viewportIndex = 0;
|
||||||
viewportIndex < viewports.length;
|
viewportIndex < viewports.length;
|
||||||
viewportIndex++
|
viewportIndex++
|
||||||
) {
|
) {
|
||||||
const viewport = reuseViewport(
|
const viewport = reuseViewportId(
|
||||||
viewportIdSet,
|
viewportIdSet,
|
||||||
viewports[viewportIndex],
|
viewports[viewportIndex],
|
||||||
state.viewports
|
state.viewports
|
||||||
@ -268,36 +318,15 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDisplaySetsForViewport = useCallback(
|
const setDisplaySetsForViewports = useCallback(
|
||||||
({
|
viewports =>
|
||||||
viewportIndex,
|
|
||||||
displaySetInstanceUIDs,
|
|
||||||
viewportOptions,
|
|
||||||
displaySetSelectors,
|
|
||||||
displaySetOptions,
|
|
||||||
}) =>
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_DISPLAYSET_FOR_VIEWPORT',
|
type: 'SET_DISPLAYSETS_FOR_VIEWPORTS',
|
||||||
payload: {
|
payload: viewports,
|
||||||
viewportIndex,
|
|
||||||
displaySetInstanceUIDs,
|
|
||||||
viewportOptions,
|
|
||||||
displaySetSelectors,
|
|
||||||
displaySetOptions,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDisplaySetsForViewports = useCallback(
|
|
||||||
viewports => {
|
|
||||||
viewports.forEach(data => {
|
|
||||||
setDisplaySetsForViewport(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setDisplaySetsForViewport]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setLayout = useCallback(
|
const setLayout = useCallback(
|
||||||
({
|
({
|
||||||
layoutType,
|
layoutType,
|
||||||
@ -355,7 +384,6 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
service.setServiceImplementation({
|
service.setServiceImplementation({
|
||||||
getState,
|
getState,
|
||||||
setActiveViewportIndex,
|
setActiveViewportIndex,
|
||||||
setDisplaySetsForViewport,
|
|
||||||
setDisplaySetsForViewports,
|
setDisplaySetsForViewports,
|
||||||
setLayout,
|
setLayout,
|
||||||
reset,
|
reset,
|
||||||
@ -368,7 +396,6 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
getState,
|
getState,
|
||||||
service,
|
service,
|
||||||
setActiveViewportIndex,
|
setActiveViewportIndex,
|
||||||
setDisplaySetsForViewport,
|
|
||||||
setDisplaySetsForViewports,
|
setDisplaySetsForViewports,
|
||||||
setLayout,
|
setLayout,
|
||||||
reset,
|
reset,
|
||||||
@ -379,7 +406,6 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
const api = {
|
const api = {
|
||||||
getState,
|
getState,
|
||||||
setActiveViewportIndex: index => service.setActiveViewportIndex(index), // run it through the service itself since we want to publish events
|
setActiveViewportIndex: index => service.setActiveViewportIndex(index), // run it through the service itself since we want to publish events
|
||||||
setDisplaySetsForViewport,
|
|
||||||
setDisplaySetsForViewports,
|
setDisplaySetsForViewports,
|
||||||
setLayout: layout => service.setLayout(layout), // run it through the service itself since we want to publish events
|
setLayout: layout => service.setLayout(layout), // run it through the service itself since we want to publish events
|
||||||
reset,
|
reset,
|
||||||
|
|||||||
@ -42,6 +42,31 @@ window.config = {
|
|||||||
singlepart: 'bulkdata,video,pdf',
|
singlepart: 'bulkdata,video,pdf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
friendlyName: 'dcmjs DICOMWeb Server',
|
||||||
|
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||||
|
sourceName: 'ohif',
|
||||||
|
configuration: {
|
||||||
|
name: 'aws',
|
||||||
|
// old server
|
||||||
|
// 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',
|
||||||
|
// new server
|
||||||
|
wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
||||||
|
qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
||||||
|
wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
||||||
|
qidoSupportsIncludeField: false,
|
||||||
|
supportsReject: false,
|
||||||
|
imageRendering: 'wadors',
|
||||||
|
thumbnailRendering: 'wadors',
|
||||||
|
enableStudyLazyLoad: true,
|
||||||
|
supportsFuzzyMatching: false,
|
||||||
|
supportsWildcard: true,
|
||||||
|
staticWado: true,
|
||||||
|
singlepart: 'bulkdata,video,pdf',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
friendlyName: 'AWS S3 OHIF',
|
friendlyName: 'AWS S3 OHIF',
|
||||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user