fix(staticwado): SM and RT and update the server with new data (#3422)

* fix bugs for the RT for the new demo

* fix SM with the static-wado server

* remove thumbnail from tmtv

* migration guide

* fix stability for the rt struct

* apply review comments

* add loading indicator to SM

* pdf works

* try to fix relative bulkData

* fix the rest

* fix preflight for the SM

* fix typo

* apply review comments

* yarn lock
This commit is contained in:
Alireza 2023-05-29 09:04:49 -04:00 committed by GitHub
parent b684d80426
commit c7bcf1134d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 290 additions and 156 deletions

View File

@ -7,12 +7,13 @@ async function checkAndLoadContourData(instance, datasource) {
return Promise.reject('Invalid instance object or ROIContourSequence');
}
const promises = [];
let counter = 0;
const promisesMap = new Map();
for (const ROIContour of instance.ROIContourSequence) {
const referencedROINumber = ROIContour.ReferencedROINumber;
if (!ROIContour || !ROIContour.ContourSequence) {
return Promise.reject('Invalid ROIContour or ContourSequence');
promisesMap.set(referencedROINumber, [Promise.resolve([])]);
continue;
}
for (const Contour of ROIContour.ContourSequence) {
@ -21,9 +22,15 @@ async function checkAndLoadContourData(instance, datasource) {
}
const contourData = Contour.ContourData;
counter++;
if (Array.isArray(contourData)) {
promises.push(Promise.resolve(contourData));
promisesMap.has(referencedROINumber)
? promisesMap
.get(referencedROINumber)
.push(Promise.resolve(contourData))
: promisesMap.set(referencedROINumber, [
Promise.resolve(contourData),
]);
} else if (contourData && contourData.BulkDataURI) {
const bulkDataURI = contourData.BulkDataURI;
@ -44,53 +51,59 @@ async function checkAndLoadContourData(instance, datasource) {
SOPInstanceUID: instance.SOPInstanceUID,
});
promises.push(bulkDataPromise);
promisesMap.has(referencedROINumber)
? promisesMap.get(referencedROINumber).push(bulkDataPromise)
: promisesMap.set(referencedROINumber, [bulkDataPromise]);
} else {
return Promise.reject(`Invalid ContourData: ${contourData}`);
}
}
}
const flattenedPromises = promises.flat();
const resolvedPromises = await Promise.allSettled(flattenedPromises);
// Modify contourData and replace it in its corresponding ROIContourSequence's Contour's contourData
let index = 0;
instance.ROIContourSequence.forEach((ROIContour, roiIndex) => {
ROIContour.ContourSequence.forEach((Contour, contourIndex) => {
const promise = resolvedPromises[index++];
const resolvedPromisesMap = new Map();
for (const [key, promiseArray] of promisesMap.entries()) {
resolvedPromisesMap.set(key, await Promise.allSettled(promiseArray));
}
if (promise.status === 'fulfilled') {
const uint8Array = new Uint8Array(promise.value);
const textDecoder = new TextDecoder();
const dataUint8Array = textDecoder.decode(uint8Array);
if (
typeof dataUint8Array === 'string' &&
dataUint8Array.includes('\\')
) {
const numSlashes = (dataUint8Array.match(/\\/g) || []).length;
let startIndex = 0;
let endIndex = dataUint8Array.indexOf('\\', startIndex);
let numbersParsed = 0;
const ContourData = [];
instance.ROIContourSequence.forEach(ROIContour => {
try {
const referencedROINumber = ROIContour.ReferencedROINumber;
const resolvedPromises = resolvedPromisesMap.get(referencedROINumber);
while (numbersParsed !== numSlashes + 1) {
const str = dataUint8Array.substring(startIndex, endIndex);
let value = parseFloat(str);
ContourData.push(value);
startIndex = endIndex + 1;
endIndex = dataUint8Array.indexOf('\\', startIndex);
endIndex === -1 ? (endIndex = dataUint8Array.length) : endIndex;
numbersParsed++;
if (ROIContour.ContourSequence) {
ROIContour.ContourSequence.forEach((Contour, index) => {
const promise = resolvedPromises[index];
if (promise.status === 'fulfilled') {
if (
Array.isArray(promise.value) &&
promise.value.every(Number.isFinite)
) {
// If promise.value is already an array of numbers, use it directly
Contour.ContourData = promise.value;
} else {
// If the resolved promise value is a byte array (Blob), it needs to be decoded
const uint8Array = new Uint8Array(promise.value);
const textDecoder = new TextDecoder();
const dataUint8Array = textDecoder.decode(uint8Array);
if (
typeof dataUint8Array === 'string' &&
dataUint8Array.includes('\\')
) {
Contour.ContourData = dataUint8Array
.split('\\')
.map(parseFloat);
} else {
Contour.ContourData = [];
}
}
} else {
console.error(promise.reason);
}
Contour.ContourData = ContourData;
} else {
Contour.ContourData = [];
}
} else {
console.error(promise.reason);
});
}
});
} catch (error) {
console.error(error);
}
});
}
@ -104,7 +117,7 @@ export default async function loadRTStruct(
'@ohif/extension-cornerstone.utilityModule.common'
);
const dataSource = extensionManager.getActiveDataSource()[0];
const { useBulkDataURI } = dataSource.getConfig?.() || {};
const { bulkDataURI } = dataSource.getConfig?.() || {};
const { dicomLoaderService } = utilityModule.exports;
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
@ -116,7 +129,7 @@ export default async function loadRTStruct(
rtStructDisplaySet.isLoaded = true;
let instance = rtStructDisplaySet.instance;
if (!useBulkDataURI) {
if (!bulkDataURI || !bulkDataURI.enabled) {
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
rtStructDisplaySet,
null,

View File

@ -1,11 +1,11 @@
import ContextMenuController from './ContextMenuController';
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
import defaultContextMenu from './defaultContextMenu';
import * as CustomizeableContextMenuTypes from './types';
import * as CustomizableContextMenuTypes from './types';
export {
ContextMenuController,
CustomizeableContextMenuTypes,
CustomizableContextMenuTypes,
ContextMenuItemsBuilder,
defaultContextMenu,
};

View File

@ -24,6 +24,7 @@ import {
} from './retrieveStudyMetadata.js';
import StaticWadoClient from './utils/StaticWadoClient';
import getDirectURL from '../utils/getDirectURL';
import { fixBulkDataURI } from './utils/fixBulkDataURI';
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
@ -373,13 +374,23 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
*/
const addRetrieveBulkData = instance => {
const naturalized = naturalizeDataset(instance);
// if we konw the server doesn't use bulkDataURI, then don't
if (!dicomWebConfig.bulkDataURI?.enabled) {
return naturalized;
}
Object.keys(naturalized).forEach(key => {
const value = naturalized[key];
// The value.Value will be set with the bulkdata read value
// in which case it isn't necessary to re-read this.
if (value && value.BulkDataURI && !value.Value) {
// Provide a method to fetch bulkdata
value.retrieveBulkData = () => {
// handle the scenarios where bulkDataURI is relative path
fixBulkDataURI(value, naturalized, dicomWebConfig);
const options = {
// The bulkdata fetches work with either multipart or
// singlepart, so set multipart to false to let the server

View File

@ -0,0 +1,56 @@
/**
* Modifies a bulkDataURI to ensure it is absolute based on the DICOMWeb configuration and
* instance data. The modification is in-place.
*
* If the bulkDataURI is relative to the series or study (according to the DICOM standard),
* it is made absolute by prepending the relevant paths.
*
* In scenarios where the bulkDataURI is a server-relative path (starting with '/'), the function
* handles two cases:
*
* 1. If the wado root is absolute (starts with 'http'), it prepends the wado root to the bulkDataURI.
* 2. If the wado root is relative, no changes are needed as the bulkDataURI is already correctly relative to the server root.
*
* @param value - The object containing BulkDataURI to be fixed.
* @param instance - The object (DICOM instance data) containing StudyInstanceUID and SeriesInstanceUID.
* @param dicomWebConfig - The DICOMWeb configuration object, containing wadoRoot and potentially bulkDataURI.relativeResolution.
* @returns The function modifies `value` in-place, it does not return a value.
*/
function fixBulkDataURI(value, instance, dicomWebConfig) {
// in case of the relative path, make it absolute. The current DICOM standard says
// the bulkdataURI is relative to the series. However, there are situations where
// it can be relative to the study too
if (
!value.BulkDataURI.startsWith('http') &&
!value.BulkDataURI.startsWith('/')
) {
if (dicomWebConfig.bulkDataURI?.relativeResolution === 'studies') {
value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${instance.StudyInstanceUID}/${value.BulkDataURI}`;
} else if (
dicomWebConfig.bulkDataURI?.relativeResolution === 'series' ||
!dicomWebConfig.bulkDataURI?.relativeResolution
) {
value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${instance.StudyInstanceUID}/series/${instance.SeriesInstanceUID}/${value.BulkDataURI}`;
}
return;
}
// in case it is relative path but starts at the server (e.g., /bulk/1e, note the missing http
// in the beginning and the first character is /) There are two scenarios, whether the wado root
// is absolute or relative. In case of absolute, we need to prepend the wado root to the bulkdata
// uri (e.g., bulkData: /bulk/1e, wado root: http://myserver.com/dicomweb, output: http://myserver.com/bulk/1e)
// and in case of relative wado root, we need to prepend the bulkdata uri to the wado root (e.g,. bulkData: /bulk/1e
// wado root: /dicomweb, output: /bulk/1e)
if (value.BulkDataURI[0] === '/') {
if (dicomWebConfig.wadoRoot.startsWith('http')) {
// Absolute wado root
const url = new URL(dicomWebConfig.wadoRoot);
value.BulkDataURI = `${url.origin}${value.BulkDataURI}`;
} else {
// Relative wado root, we don't need to do anything, bulkdata uri is already correct
}
}
}
export { fixBulkDataURI };

View File

@ -0,0 +1,3 @@
import { fixBulkDataURI } from './fixBulkDataURI';
export { fixBulkDataURI };

View File

@ -3,14 +3,14 @@ import { ServicesManager, utils, Types } from '@ohif/core';
import {
ContextMenuController,
defaultContextMenu,
} from './CustomizeableContextMenu';
} from './CustomizableContextMenu';
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
import reuseCachedLayouts from './utils/reuseCachedLayouts';
import findViewportsByPosition, {
findOrCreateViewport as layoutFindOrCreate,
} from './findViewportsByPosition';
import { ContextMenuProps } from './CustomizeableContextMenu/types';
import { ContextMenuProps } from './CustomizableContextMenu/types';
import { NavigateHistory } from './types/commandModuleTypes';
import { history } from '@ohif/viewer';

View File

@ -13,8 +13,9 @@ import { id } from './id.js';
import preRegistration from './init';
import {
ContextMenuController,
CustomizeableContextMenuTypes,
} from './CustomizeableContextMenu';
CustomizableContextMenuTypes,
} from './CustomizableContextMenu';
import * as dicomWebUtils from './DicomWebDataSource/utils';
const defaultExtension: Types.Extensions.Extension = {
/**
@ -47,6 +48,7 @@ export default defaultExtension;
export {
ContextMenuController,
CustomizeableContextMenuTypes,
CustomizableContextMenuTypes,
getStudiesForPatientByMRN,
dicomWebUtils,
};

View File

@ -1,10 +1,4 @@
import {
DicomMetadataStore,
IWebApiDataSource,
utils,
errorHandler,
classes,
} from '@ohif/core';
import { utils } from '@ohif/core';
/**
* Generates a URL that can be used for direct retrieve of the bulkdata
@ -57,31 +51,19 @@ const getDirectURL = (config, params) => {
const BulkDataURI =
(value && value.BulkDataURI) ||
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
const hasQuery = BulkDataURI.indexOf('?') != -1;
const hasAccept = BulkDataURI.indexOf('accept=') != -1;
const hasQuery = BulkDataURI.indexOf('?') !== -1;
const hasAccept = BulkDataURI.indexOf('accept=') !== -1;
const acceptUri =
BulkDataURI +
(hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
if (BulkDataURI.indexOf('http') === 0) {
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
} else {
return acceptUri;
}
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
}
if (BulkDataURI.indexOf('/') === 0) {
return wadoRoot + acceptUri;
}
if (BulkDataURI.indexOf('series/') == 0) {
return `${wadoRoot}/studies/${StudyInstanceUID}/${acceptUri}`;
}
if (BulkDataURI.indexOf('instances/') === 0) {
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/${acceptUri}`;
}
if (BulkDataURI.indexOf('bulkdata/') === 0) {
return `${wadoRoot}/studies/${StudyInstanceUID}/${acceptUri}`;
}
throw new Error('BulkDataURI in unknown format:' + BulkDataURI);
// The DICOMweb standard states that the default is multipart related, and then
// separately states that the accept parameter is the URL parameter equivalent of the accept header.
return acceptUri;
};
export default getDirectURL;

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { LoadingIndicatorProgress } from '@ohif/ui';
import './DicomMicroscopyViewport.css';
import ViewportOverlay from './components/ViewportOverlay';
@ -10,9 +11,20 @@ import dcmjs from 'dcmjs';
import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
import MicroscopyService from './services/MicroscopyService';
function transformImageTypeUnnaturalized(entry) {
if (entry.vr === 'CS') {
return {
vr: 'US',
Value: entry.Value[0].split('\\'),
};
}
return entry;
}
class DicomMicroscopyViewport extends Component {
state = {
error: null as any,
isLoaded: false,
};
microscopyService: MicroscopyService;
@ -132,6 +144,10 @@ class DicomMicroscopyViewport extends Component {
// );
// m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID;
// }
// NOTE: depending on different data source, image.ImageType sometimes
// is a string, not a string array.
// m['00080008'] = transformImageTypeUnnaturalized(m['00080008']);
// const image = new metadataUtils.VLWholeSlideMicroscopyImage({
// metadata: m,
// });
@ -143,8 +159,20 @@ class DicomMicroscopyViewport extends Component {
// }
metadata.forEach(m => {
// NOTE: depending on different data source, image.ImageType sometimes
// is a string, not a string array.
m.ImageType =
typeof m.ImageType === 'string'
? m.ImageType.split('\\')
: m.ImageType;
const inst = cleanDenaturalizedDataset(
dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m)
dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m),
{
StudyInstanceUID: m.StudyInstanceUID,
SeriesInstanceUID: m.SeriesInstanceUID,
dataSourceConfig: this.props.dataSource.getConfig(),
}
);
if (!inst['00480105']) {
// Optical Path Sequence, no OpticalPathIdentifier?
@ -165,13 +193,7 @@ class DicomMicroscopyViewport extends Component {
metadata: inst,
});
// NOTE: depending on different data source, image.ImageType sometimes
// is a string, not a string array.
const imageType =
typeof image.ImageType === 'string'
? image.ImageType.split('\\')
: image.ImageType;
const imageFlavor = imageType[2];
const imageFlavor = image.ImageType[2];
if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
volumeImages.push(image);
}
@ -182,7 +204,7 @@ class DicomMicroscopyViewport extends Component {
client,
metadata: volumeImages,
retrieveRendered: false,
controls: ['overview', 'position', 'zoom'],
controls: ['overview', 'position'],
};
this.viewer = new microscopyViewer(options);
@ -237,7 +259,11 @@ class DicomMicroscopyViewport extends Component {
componentDidMount() {
const { displaySets, viewportIndex } = this.props;
const displaySet = displaySets[viewportIndex];
this.installOpenLayersRenderer(this.container.current, displaySet);
this.installOpenLayersRenderer(this.container.current, displaySet).then(
() => {
this.setState({ isLoaded: true });
}
);
}
componentDidUpdate(
@ -316,6 +342,9 @@ class DicomMicroscopyViewport extends Component {
) : (
<div style={style} ref={this.container} />
)}
{this.state.isLoaded ? null : (
<LoadingIndicatorProgress className={'w-full h-full bg-black'} />
)}
</div>
);
}

View File

@ -1,6 +1,5 @@
import React from 'react';
import classnames from 'classnames';
import ConfigPoint from 'config-point';
import listComponentGenerator from './listComponentGenerator';
import './ViewportOverlay.css';
@ -11,7 +10,7 @@ import {
formatPN,
} from './utils';
interface OverylayItem {
interface OverlayItem {
id: string;
title: string;
value?: (props: any) => string;
@ -28,17 +27,17 @@ interface OverylayItem {
* @returns
*/
export const generateFromConfig = ({
topLeft,
topRight,
bottomLeft,
bottomRight,
itemGenerator,
topLeft = [],
topRight = [],
bottomLeft = [],
bottomRight = [],
itemGenerator = () => {},
}: {
topLeft: OverylayItem[];
topRight: OverylayItem[];
bottomLeft: OverylayItem[];
bottomRight: OverylayItem[];
itemGenerator: (props: any) => any;
topLeft?: OverlayItem[];
topRight?: OverlayItem[];
bottomLeft?: OverlayItem[];
bottomRight?: OverlayItem[];
itemGenerator?: (props: any) => any;
}) => {
return (props: any) => {
const topLeftClass = 'top-viewport left-viewport text-primary-light';
@ -127,29 +126,4 @@ const itemGenerator = (props: any) => {
);
};
const { MicroscopyViewportOverlay } = ConfigPoint.register({
MicroscopyViewportOverlay: {
configBase: {
topLeft: [
// {
// id: 'sm-overlay-patient-name',
// title: 'PatientName',
// condition: ({ instance }) =>
// instance && instance.PatientName && instance.PatientName.Alphabetic,
// value: ({ instance }) =>
// instance.PatientName && instance.PatientName.Alphabetic,
// } as OverylayItem,
],
topRight: [] as OverylayItem[],
bottomLeft: [] as OverylayItem[],
bottomRight: [] as OverylayItem[],
itemGenerator,
generateFromConfig,
},
...(window.config?.MicroscopyViewportOverlay || {}),
},
});
export default MicroscopyViewportOverlay.generateFromConfig(
MicroscopyViewportOverlay
);
export default generateFromConfig({});

View File

@ -1,3 +1,5 @@
import { dicomWebUtils } from '@ohif/extension-default';
function isPrimitive(v: any) {
return !(typeof v == 'object' || Array.isArray(v));
}
@ -24,10 +26,17 @@ const vrNumerics = [
* @param obj
* @returns
*/
export default function cleanDenaturalizedDataset(obj: any): any {
export default function cleanDenaturalizedDataset(
obj: any,
options: {
StudyInstanceUID: string;
SeriesInstanceUID: string;
dataSourceConfig: unknown;
}
): any {
if (Array.isArray(obj)) {
const newAry = obj.map(o =>
isPrimitive(o) ? o : cleanDenaturalizedDataset(o)
isPrimitive(o) ? o : cleanDenaturalizedDataset(o, options)
);
return newAry;
} else if (isPrimitive(obj)) {
@ -38,7 +47,11 @@ export default function cleanDenaturalizedDataset(obj: any): any {
delete obj[key].Value;
} else if (Array.isArray(obj[key].Value) && obj[key].vr) {
if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI;
obj[key].Value[0] = dicomWebUtils.fixBulkDataURI(
obj[key].Value[0],
options,
options.dataSourceConfig
);
// prevent mixed-content blockage
if (
@ -54,7 +67,9 @@ export default function cleanDenaturalizedDataset(obj: any): any {
} else if (vrNumerics.includes(obj[key].vr)) {
obj[key].Value = obj[key].Value.map(v => +v);
} else {
obj[key].Value = obj[key].Value.map(cleanDenaturalizedDataset);
obj[key].Value = obj[key].Value.map(entry =>
cleanDenaturalizedDataset(entry, options)
);
}
}
});

View File

@ -1,6 +1,12 @@
import { api } from 'dicomweb-client';
import { errorHandler, DicomMetadataStore } from '@ohif/core';
const { DICOMwebClient } = api;
DICOMwebClient._buildMultipartAcceptHeaderFieldValue = () => {
return '*/*';
};
/**
* create a DICOMwebClient object to be used by Dicom Microscopy Viewer
*

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { HangingProtocolService, utils } from '@ohif/core';
import { utils } from '@ohif/core';
import {
StudyBrowser,
useImageViewer,

View File

@ -189,16 +189,26 @@ function modeFactory({ modeConfiguration }) {
study: [],
series: [],
},
isValidMode: ({ modalities }) => {
isValidMode: ({ modalities, study }) => {
const modalities_list = modalities.split('\\');
const invalidModalities = ['SM'];
// there should be both CT and PT modalities and the modality should not be SM
return (
const isValid =
modalities_list.includes('CT') &&
modalities_list.includes('PT') &&
!invalidModalities.some(modality => modalities_list.includes(modality))
);
!invalidModalities.some(modality =>
modalities_list.includes(modality)
) &&
// This is study is a 4D study with PT and CT and not a 3D study for the tmtv
// mode, until we have a better way to identify 4D studies we will use the
// StudyInstanceUID to identify the study
// Todo: when we add the 4D mode which comes with a mechanism to identify
// 4D studies we can use that
study.studyInstanceUid !==
'1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849';
// there should be both CT and PT modalities and the modality should not be SM
return isValid;
},
routes: [
{

View File

@ -179,6 +179,24 @@ See the [`singlepart`](#singlepart) data source configuration option.
### DICOM Video
See the [`singlepart`](#singlepart) data source configuration option.
### BulkDataURI
The `bulkDataURI` configuration option allows the datasource to use the
bulkdata end points for retrieving metadata if originally was not included in the
response from the server. This is useful for the metadata information that
are big and can/should be retrieved in a separate request. In case the bulkData URI
is relative (instead of absolute) the `relativeResolution` option can be used to
specify the resolution of the relative URI. The possible values are `studies`, `series` and `instances`.
Certainly the knowledge of how the server is configured is required to use this option.
```js
bulkDataURI: {
enabled: true,
relativeResolution: 'series',
},
```
### Running DCM4CHEE
dcm4che is a collection of open source applications for healthcare enterprise

View File

@ -209,6 +209,13 @@ see [custom routes](./platform/services/ui/customization-service.md#customroutes
</details>
## DICOM Endpoints
In OHIF v3 there is a new end point that your DICOM server should be able to respond to
`WADO-RS GET studies/{studyInstanceUid}/series`
This is used in the viewer for fetching the series list for a study to use for the hanging protocol.
## LifeCycle Hooks
OHIF v2 had `preRegistration` hook for extensions for initialization. In OHIF v3 you have

View File

@ -490,7 +490,7 @@ are specific to the context used for where the menu is displayed.
The default cornerstone context menu can be customized by setting the
`cornerstoneContextMenu`. For a full example, see `findingsContextMenu`.
## Customizeable Cornerstone Viewport Click Behaviour
## Customizable Cornerstone Viewport Click Behaviour
The behaviour on clicking on the cornerstone viewport can be customized
by setting the `cornerstoneViewportClickCommands`. This is intended to

View File

@ -19,7 +19,7 @@ function LoadingIndicatorProgress({ className, textBlock, progress }) {
>
<Icon name="loading-ohif-mark" className="text-white w-12 h-12" />
<div className="w-48">
<ProgressLoadingBar></ProgressLoadingBar>
<ProgressLoadingBar progress={progress} />
</div>
{textBlock}
</div>

View File

@ -65,7 +65,6 @@
"@ohif/ui": "^2.0.0",
"@types/react": "^17.0.38",
"classnames": "^2.3.2",
"config-point": "^0.4.8",
"core-js": "^3.16.1",
"cornerstone-math": "^0.1.9",
"@cornerstonejs/dicom-image-loader": "^0.6.8",

View File

@ -48,9 +48,9 @@ window.config = {
// 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',
wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
@ -60,8 +60,14 @@ window.config = {
supportsFuzzyMatching: false,
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
useBulkDataURI: false,
singlepart: 'bulkdata,video',
// whether the data source should use retrieveBulkData to grab metadata,
// and in case of relative path, what would it be relative to, options
// are in the series level or study level (some servers like series some study)
bulkDataURI: {
enabled: true,
relativeResolution: 'studies',
},
},
},
{

View File

@ -22,7 +22,9 @@ window.config = {
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
enableStudyLazyLoad: true,
useBulkDataURI: false,
bulkDataURI: {
enabled: false,
},
},
},
],

View File

@ -33,6 +33,12 @@ window.config = {
},
dicomUploadEnabled: true,
singlepart: 'pdf,video',
// whether the data source should use retrieveBulkData to grab metadata,
// and in case of relative path, what would it be relative to, options
// are in the series level or study level (some servers like series some study)
bulkDataURI: {
enabled: true,
},
},
},
{

View File

@ -29,10 +29,12 @@ window.config = {
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
useBulkDataURI: false,
supportsFuzzyMatching: true,
supportsWildcard: true,
dicomUploadEnabled: true,
bulkDataURI: {
enabled: false,
},
},
},
{

View File

@ -343,6 +343,7 @@ function WorkList({
const isValidMode = mode.isValidMode({
modalities: modalitiesToCheck,
study,
});
// TODO: Modes need a default/target route? We mostly support a single one for now.
// We should also be using the route path, but currently are not

View File

@ -1302,7 +1302,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.21.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@ -7461,14 +7461,6 @@ config-chain@^1.1.11:
ini "^1.3.4"
proto-list "~1.2.1"
config-point@^0.4.8:
version "0.4.9"
resolved "https://registry.npmjs.org/config-point/-/config-point-0.4.9.tgz#ec4594d04235438d5852e5ab33bde60aeffd18db"
integrity sha512-2QyqD2eb2iURURZNW6MAcNcT2e1Pr1VXwCaMgEQM0f/i+WaUCLZOdPjFtqF7oIT1rzPXNoMfl3KK2hbNcy5dXw==
dependencies:
"@babel/runtime" "^7.14.6"
json5 "^2.2.0"
configstore@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"