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:
parent
b684d80426
commit
c7bcf1134d
@ -7,12 +7,13 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
return Promise.reject('Invalid instance object or ROIContourSequence');
|
return Promise.reject('Invalid instance object or ROIContourSequence');
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = [];
|
const promisesMap = new Map();
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
for (const ROIContour of instance.ROIContourSequence) {
|
for (const ROIContour of instance.ROIContourSequence) {
|
||||||
|
const referencedROINumber = ROIContour.ReferencedROINumber;
|
||||||
if (!ROIContour || !ROIContour.ContourSequence) {
|
if (!ROIContour || !ROIContour.ContourSequence) {
|
||||||
return Promise.reject('Invalid ROIContour or ContourSequence');
|
promisesMap.set(referencedROINumber, [Promise.resolve([])]);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Contour of ROIContour.ContourSequence) {
|
for (const Contour of ROIContour.ContourSequence) {
|
||||||
@ -21,9 +22,15 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const contourData = Contour.ContourData;
|
const contourData = Contour.ContourData;
|
||||||
counter++;
|
|
||||||
if (Array.isArray(contourData)) {
|
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) {
|
} else if (contourData && contourData.BulkDataURI) {
|
||||||
const bulkDataURI = contourData.BulkDataURI;
|
const bulkDataURI = contourData.BulkDataURI;
|
||||||
|
|
||||||
@ -44,53 +51,59 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
SOPInstanceUID: instance.SOPInstanceUID,
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
});
|
});
|
||||||
|
|
||||||
promises.push(bulkDataPromise);
|
promisesMap.has(referencedROINumber)
|
||||||
|
? promisesMap.get(referencedROINumber).push(bulkDataPromise)
|
||||||
|
: promisesMap.set(referencedROINumber, [bulkDataPromise]);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(`Invalid ContourData: ${contourData}`);
|
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
|
const resolvedPromisesMap = new Map();
|
||||||
let index = 0;
|
for (const [key, promiseArray] of promisesMap.entries()) {
|
||||||
instance.ROIContourSequence.forEach((ROIContour, roiIndex) => {
|
resolvedPromisesMap.set(key, await Promise.allSettled(promiseArray));
|
||||||
ROIContour.ContourSequence.forEach((Contour, contourIndex) => {
|
}
|
||||||
const promise = resolvedPromises[index++];
|
|
||||||
|
|
||||||
if (promise.status === 'fulfilled') {
|
instance.ROIContourSequence.forEach(ROIContour => {
|
||||||
const uint8Array = new Uint8Array(promise.value);
|
try {
|
||||||
const textDecoder = new TextDecoder();
|
const referencedROINumber = ROIContour.ReferencedROINumber;
|
||||||
const dataUint8Array = textDecoder.decode(uint8Array);
|
const resolvedPromises = resolvedPromisesMap.get(referencedROINumber);
|
||||||
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 = [];
|
|
||||||
|
|
||||||
while (numbersParsed !== numSlashes + 1) {
|
if (ROIContour.ContourSequence) {
|
||||||
const str = dataUint8Array.substring(startIndex, endIndex);
|
ROIContour.ContourSequence.forEach((Contour, index) => {
|
||||||
let value = parseFloat(str);
|
const promise = resolvedPromises[index];
|
||||||
|
if (promise.status === 'fulfilled') {
|
||||||
ContourData.push(value);
|
if (
|
||||||
startIndex = endIndex + 1;
|
Array.isArray(promise.value) &&
|
||||||
endIndex = dataUint8Array.indexOf('\\', startIndex);
|
promise.value.every(Number.isFinite)
|
||||||
endIndex === -1 ? (endIndex = dataUint8Array.length) : endIndex;
|
) {
|
||||||
numbersParsed++;
|
// 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'
|
'@ohif/extension-cornerstone.utilityModule.common'
|
||||||
);
|
);
|
||||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||||
const { useBulkDataURI } = dataSource.getConfig?.() || {};
|
const { bulkDataURI } = dataSource.getConfig?.() || {};
|
||||||
|
|
||||||
const { dicomLoaderService } = utilityModule.exports;
|
const { dicomLoaderService } = utilityModule.exports;
|
||||||
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
|
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||||
@ -116,7 +129,7 @@ export default async function loadRTStruct(
|
|||||||
rtStructDisplaySet.isLoaded = true;
|
rtStructDisplaySet.isLoaded = true;
|
||||||
let instance = rtStructDisplaySet.instance;
|
let instance = rtStructDisplaySet.instance;
|
||||||
|
|
||||||
if (!useBulkDataURI) {
|
if (!bulkDataURI || !bulkDataURI.enabled) {
|
||||||
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
|
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
|
||||||
rtStructDisplaySet,
|
rtStructDisplaySet,
|
||||||
null,
|
null,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import ContextMenuController from './ContextMenuController';
|
import ContextMenuController from './ContextMenuController';
|
||||||
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
|
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder';
|
||||||
import defaultContextMenu from './defaultContextMenu';
|
import defaultContextMenu from './defaultContextMenu';
|
||||||
import * as CustomizeableContextMenuTypes from './types';
|
import * as CustomizableContextMenuTypes from './types';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ContextMenuController,
|
ContextMenuController,
|
||||||
CustomizeableContextMenuTypes,
|
CustomizableContextMenuTypes,
|
||||||
ContextMenuItemsBuilder,
|
ContextMenuItemsBuilder,
|
||||||
defaultContextMenu,
|
defaultContextMenu,
|
||||||
};
|
};
|
||||||
@ -24,6 +24,7 @@ import {
|
|||||||
} from './retrieveStudyMetadata.js';
|
} from './retrieveStudyMetadata.js';
|
||||||
import StaticWadoClient from './utils/StaticWadoClient';
|
import StaticWadoClient from './utils/StaticWadoClient';
|
||||||
import getDirectURL from '../utils/getDirectURL';
|
import getDirectURL from '../utils/getDirectURL';
|
||||||
|
import { fixBulkDataURI } from './utils/fixBulkDataURI';
|
||||||
|
|
||||||
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
||||||
|
|
||||||
@ -373,13 +374,23 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
|||||||
*/
|
*/
|
||||||
const addRetrieveBulkData = instance => {
|
const addRetrieveBulkData = instance => {
|
||||||
const naturalized = naturalizeDataset(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 => {
|
Object.keys(naturalized).forEach(key => {
|
||||||
const value = naturalized[key];
|
const value = naturalized[key];
|
||||||
|
|
||||||
// The value.Value will be set with the bulkdata read value
|
// The value.Value will be set with the bulkdata read value
|
||||||
// in which case it isn't necessary to re-read this.
|
// in which case it isn't necessary to re-read this.
|
||||||
if (value && value.BulkDataURI && !value.Value) {
|
if (value && value.BulkDataURI && !value.Value) {
|
||||||
// Provide a method to fetch bulkdata
|
// Provide a method to fetch bulkdata
|
||||||
value.retrieveBulkData = () => {
|
value.retrieveBulkData = () => {
|
||||||
|
// handle the scenarios where bulkDataURI is relative path
|
||||||
|
fixBulkDataURI(value, naturalized, dicomWebConfig);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
// The bulkdata fetches work with either multipart or
|
// The bulkdata fetches work with either multipart or
|
||||||
// singlepart, so set multipart to false to let the server
|
// singlepart, so set multipart to false to let the server
|
||||||
|
|||||||
@ -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 };
|
||||||
3
extensions/default/src/DicomWebDataSource/utils/index.ts
Normal file
3
extensions/default/src/DicomWebDataSource/utils/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { fixBulkDataURI } from './fixBulkDataURI';
|
||||||
|
|
||||||
|
export { fixBulkDataURI };
|
||||||
@ -3,14 +3,14 @@ import { ServicesManager, utils, Types } from '@ohif/core';
|
|||||||
import {
|
import {
|
||||||
ContextMenuController,
|
ContextMenuController,
|
||||||
defaultContextMenu,
|
defaultContextMenu,
|
||||||
} from './CustomizeableContextMenu';
|
} from './CustomizableContextMenu';
|
||||||
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
|
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
|
||||||
import reuseCachedLayouts from './utils/reuseCachedLayouts';
|
import reuseCachedLayouts from './utils/reuseCachedLayouts';
|
||||||
import findViewportsByPosition, {
|
import findViewportsByPosition, {
|
||||||
findOrCreateViewport as layoutFindOrCreate,
|
findOrCreateViewport as layoutFindOrCreate,
|
||||||
} from './findViewportsByPosition';
|
} from './findViewportsByPosition';
|
||||||
|
|
||||||
import { ContextMenuProps } from './CustomizeableContextMenu/types';
|
import { ContextMenuProps } from './CustomizableContextMenu/types';
|
||||||
import { NavigateHistory } from './types/commandModuleTypes';
|
import { NavigateHistory } from './types/commandModuleTypes';
|
||||||
import { history } from '@ohif/viewer';
|
import { history } from '@ohif/viewer';
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,9 @@ import { id } from './id.js';
|
|||||||
import preRegistration from './init';
|
import preRegistration from './init';
|
||||||
import {
|
import {
|
||||||
ContextMenuController,
|
ContextMenuController,
|
||||||
CustomizeableContextMenuTypes,
|
CustomizableContextMenuTypes,
|
||||||
} from './CustomizeableContextMenu';
|
} from './CustomizableContextMenu';
|
||||||
|
import * as dicomWebUtils from './DicomWebDataSource/utils';
|
||||||
|
|
||||||
const defaultExtension: Types.Extensions.Extension = {
|
const defaultExtension: Types.Extensions.Extension = {
|
||||||
/**
|
/**
|
||||||
@ -47,6 +48,7 @@ export default defaultExtension;
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
ContextMenuController,
|
ContextMenuController,
|
||||||
CustomizeableContextMenuTypes,
|
CustomizableContextMenuTypes,
|
||||||
getStudiesForPatientByMRN,
|
getStudiesForPatientByMRN,
|
||||||
|
dicomWebUtils,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { utils } from '@ohif/core';
|
||||||
DicomMetadataStore,
|
|
||||||
IWebApiDataSource,
|
|
||||||
utils,
|
|
||||||
errorHandler,
|
|
||||||
classes,
|
|
||||||
} from '@ohif/core';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a URL that can be used for direct retrieve of the bulkdata
|
* Generates a URL that can be used for direct retrieve of the bulkdata
|
||||||
@ -57,31 +51,19 @@ const getDirectURL = (config, params) => {
|
|||||||
const BulkDataURI =
|
const BulkDataURI =
|
||||||
(value && value.BulkDataURI) ||
|
(value && value.BulkDataURI) ||
|
||||||
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
|
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
|
||||||
const hasQuery = BulkDataURI.indexOf('?') != -1;
|
const hasQuery = BulkDataURI.indexOf('?') !== -1;
|
||||||
const hasAccept = BulkDataURI.indexOf('accept=') != -1;
|
const hasAccept = BulkDataURI.indexOf('accept=') !== -1;
|
||||||
const acceptUri =
|
const acceptUri =
|
||||||
BulkDataURI +
|
BulkDataURI +
|
||||||
(hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
|
(hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
|
||||||
if (BulkDataURI.indexOf('http') === 0) {
|
|
||||||
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
|
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
|
||||||
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
|
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
|
||||||
} else {
|
|
||||||
return acceptUri;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (BulkDataURI.indexOf('/') === 0) {
|
|
||||||
return wadoRoot + acceptUri;
|
// 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.
|
||||||
if (BulkDataURI.indexOf('series/') == 0) {
|
return acceptUri;
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getDirectURL;
|
export default getDirectURL;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
import { LoadingIndicatorProgress } from '@ohif/ui';
|
||||||
|
|
||||||
import './DicomMicroscopyViewport.css';
|
import './DicomMicroscopyViewport.css';
|
||||||
import ViewportOverlay from './components/ViewportOverlay';
|
import ViewportOverlay from './components/ViewportOverlay';
|
||||||
@ -10,9 +11,20 @@ import dcmjs from 'dcmjs';
|
|||||||
import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
|
import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
|
||||||
import MicroscopyService from './services/MicroscopyService';
|
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 {
|
class DicomMicroscopyViewport extends Component {
|
||||||
state = {
|
state = {
|
||||||
error: null as any,
|
error: null as any,
|
||||||
|
isLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
microscopyService: MicroscopyService;
|
microscopyService: MicroscopyService;
|
||||||
@ -132,6 +144,10 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
// );
|
// );
|
||||||
// m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID;
|
// 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({
|
// const image = new metadataUtils.VLWholeSlideMicroscopyImage({
|
||||||
// metadata: m,
|
// metadata: m,
|
||||||
// });
|
// });
|
||||||
@ -143,8 +159,20 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
metadata.forEach(m => {
|
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(
|
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']) {
|
if (!inst['00480105']) {
|
||||||
// Optical Path Sequence, no OpticalPathIdentifier?
|
// Optical Path Sequence, no OpticalPathIdentifier?
|
||||||
@ -165,13 +193,7 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
metadata: inst,
|
metadata: inst,
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: depending on different data source, image.ImageType sometimes
|
const imageFlavor = image.ImageType[2];
|
||||||
// is a string, not a string array.
|
|
||||||
const imageType =
|
|
||||||
typeof image.ImageType === 'string'
|
|
||||||
? image.ImageType.split('\\')
|
|
||||||
: image.ImageType;
|
|
||||||
const imageFlavor = imageType[2];
|
|
||||||
if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
|
if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
|
||||||
volumeImages.push(image);
|
volumeImages.push(image);
|
||||||
}
|
}
|
||||||
@ -182,7 +204,7 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
client,
|
client,
|
||||||
metadata: volumeImages,
|
metadata: volumeImages,
|
||||||
retrieveRendered: false,
|
retrieveRendered: false,
|
||||||
controls: ['overview', 'position', 'zoom'],
|
controls: ['overview', 'position'],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.viewer = new microscopyViewer(options);
|
this.viewer = new microscopyViewer(options);
|
||||||
@ -237,7 +259,11 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { displaySets, viewportIndex } = this.props;
|
const { displaySets, viewportIndex } = this.props;
|
||||||
const displaySet = displaySets[viewportIndex];
|
const displaySet = displaySets[viewportIndex];
|
||||||
this.installOpenLayersRenderer(this.container.current, displaySet);
|
this.installOpenLayersRenderer(this.container.current, displaySet).then(
|
||||||
|
() => {
|
||||||
|
this.setState({ isLoaded: true });
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(
|
componentDidUpdate(
|
||||||
@ -316,6 +342,9 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
) : (
|
) : (
|
||||||
<div style={style} ref={this.container} />
|
<div style={style} ref={this.container} />
|
||||||
)}
|
)}
|
||||||
|
{this.state.isLoaded ? null : (
|
||||||
|
<LoadingIndicatorProgress className={'w-full h-full bg-black'} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ConfigPoint from 'config-point';
|
|
||||||
|
|
||||||
import listComponentGenerator from './listComponentGenerator';
|
import listComponentGenerator from './listComponentGenerator';
|
||||||
import './ViewportOverlay.css';
|
import './ViewportOverlay.css';
|
||||||
@ -11,7 +10,7 @@ import {
|
|||||||
formatPN,
|
formatPN,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
interface OverylayItem {
|
interface OverlayItem {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
value?: (props: any) => string;
|
value?: (props: any) => string;
|
||||||
@ -28,17 +27,17 @@ interface OverylayItem {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const generateFromConfig = ({
|
export const generateFromConfig = ({
|
||||||
topLeft,
|
topLeft = [],
|
||||||
topRight,
|
topRight = [],
|
||||||
bottomLeft,
|
bottomLeft = [],
|
||||||
bottomRight,
|
bottomRight = [],
|
||||||
itemGenerator,
|
itemGenerator = () => {},
|
||||||
}: {
|
}: {
|
||||||
topLeft: OverylayItem[];
|
topLeft?: OverlayItem[];
|
||||||
topRight: OverylayItem[];
|
topRight?: OverlayItem[];
|
||||||
bottomLeft: OverylayItem[];
|
bottomLeft?: OverlayItem[];
|
||||||
bottomRight: OverylayItem[];
|
bottomRight?: OverlayItem[];
|
||||||
itemGenerator: (props: any) => any;
|
itemGenerator?: (props: any) => any;
|
||||||
}) => {
|
}) => {
|
||||||
return (props: any) => {
|
return (props: any) => {
|
||||||
const topLeftClass = 'top-viewport left-viewport text-primary-light';
|
const topLeftClass = 'top-viewport left-viewport text-primary-light';
|
||||||
@ -127,29 +126,4 @@ const itemGenerator = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { MicroscopyViewportOverlay } = ConfigPoint.register({
|
export default generateFromConfig({});
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { dicomWebUtils } from '@ohif/extension-default';
|
||||||
|
|
||||||
function isPrimitive(v: any) {
|
function isPrimitive(v: any) {
|
||||||
return !(typeof v == 'object' || Array.isArray(v));
|
return !(typeof v == 'object' || Array.isArray(v));
|
||||||
}
|
}
|
||||||
@ -24,10 +26,17 @@ const vrNumerics = [
|
|||||||
* @param obj
|
* @param obj
|
||||||
* @returns
|
* @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)) {
|
if (Array.isArray(obj)) {
|
||||||
const newAry = obj.map(o =>
|
const newAry = obj.map(o =>
|
||||||
isPrimitive(o) ? o : cleanDenaturalizedDataset(o)
|
isPrimitive(o) ? o : cleanDenaturalizedDataset(o, options)
|
||||||
);
|
);
|
||||||
return newAry;
|
return newAry;
|
||||||
} else if (isPrimitive(obj)) {
|
} else if (isPrimitive(obj)) {
|
||||||
@ -38,7 +47,11 @@ export default function cleanDenaturalizedDataset(obj: any): any {
|
|||||||
delete obj[key].Value;
|
delete obj[key].Value;
|
||||||
} else if (Array.isArray(obj[key].Value) && obj[key].vr) {
|
} else if (Array.isArray(obj[key].Value) && obj[key].vr) {
|
||||||
if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
|
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
|
// prevent mixed-content blockage
|
||||||
if (
|
if (
|
||||||
@ -54,7 +67,9 @@ export default function cleanDenaturalizedDataset(obj: any): any {
|
|||||||
} else if (vrNumerics.includes(obj[key].vr)) {
|
} else if (vrNumerics.includes(obj[key].vr)) {
|
||||||
obj[key].Value = obj[key].Value.map(v => +v);
|
obj[key].Value = obj[key].Value.map(v => +v);
|
||||||
} else {
|
} else {
|
||||||
obj[key].Value = obj[key].Value.map(cleanDenaturalizedDataset);
|
obj[key].Value = obj[key].Value.map(entry =>
|
||||||
|
cleanDenaturalizedDataset(entry, options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import { api } from 'dicomweb-client';
|
import { api } from 'dicomweb-client';
|
||||||
import { errorHandler, DicomMetadataStore } from '@ohif/core';
|
import { errorHandler, DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
const { DICOMwebClient } = api;
|
||||||
|
|
||||||
|
DICOMwebClient._buildMultipartAcceptHeaderFieldValue = () => {
|
||||||
|
return '*/*';
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a DICOMwebClient object to be used by Dicom Microscopy Viewer
|
* create a DICOMwebClient object to be used by Dicom Microscopy Viewer
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HangingProtocolService, utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
import {
|
import {
|
||||||
StudyBrowser,
|
StudyBrowser,
|
||||||
useImageViewer,
|
useImageViewer,
|
||||||
|
|||||||
@ -189,16 +189,26 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
study: [],
|
study: [],
|
||||||
series: [],
|
series: [],
|
||||||
},
|
},
|
||||||
isValidMode: ({ modalities }) => {
|
isValidMode: ({ modalities, study }) => {
|
||||||
const modalities_list = modalities.split('\\');
|
const modalities_list = modalities.split('\\');
|
||||||
const invalidModalities = ['SM'];
|
const invalidModalities = ['SM'];
|
||||||
|
|
||||||
// there should be both CT and PT modalities and the modality should not be SM
|
const isValid =
|
||||||
return (
|
|
||||||
modalities_list.includes('CT') &&
|
modalities_list.includes('CT') &&
|
||||||
modalities_list.includes('PT') &&
|
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: [
|
routes: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -179,6 +179,24 @@ See the [`singlepart`](#singlepart) data source configuration option.
|
|||||||
### DICOM Video
|
### DICOM Video
|
||||||
See the [`singlepart`](#singlepart) data source configuration option.
|
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
|
### Running DCM4CHEE
|
||||||
|
|
||||||
dcm4che is a collection of open source applications for healthcare enterprise
|
dcm4che is a collection of open source applications for healthcare enterprise
|
||||||
|
|||||||
@ -209,6 +209,13 @@ see [custom routes](./platform/services/ui/customization-service.md#customroutes
|
|||||||
</details>
|
</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
|
## LifeCycle Hooks
|
||||||
|
|
||||||
OHIF v2 had `preRegistration` hook for extensions for initialization. In OHIF v3 you have
|
OHIF v2 had `preRegistration` hook for extensions for initialization. In OHIF v3 you have
|
||||||
|
|||||||
@ -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
|
The default cornerstone context menu can be customized by setting the
|
||||||
`cornerstoneContextMenu`. For a full example, see `findingsContextMenu`.
|
`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
|
The behaviour on clicking on the cornerstone viewport can be customized
|
||||||
by setting the `cornerstoneViewportClickCommands`. This is intended to
|
by setting the `cornerstoneViewportClickCommands`. This is intended to
|
||||||
|
|||||||
@ -19,7 +19,7 @@ function LoadingIndicatorProgress({ className, textBlock, progress }) {
|
|||||||
>
|
>
|
||||||
<Icon name="loading-ohif-mark" className="text-white w-12 h-12" />
|
<Icon name="loading-ohif-mark" className="text-white w-12 h-12" />
|
||||||
<div className="w-48">
|
<div className="w-48">
|
||||||
<ProgressLoadingBar></ProgressLoadingBar>
|
<ProgressLoadingBar progress={progress} />
|
||||||
</div>
|
</div>
|
||||||
{textBlock}
|
{textBlock}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -65,7 +65,6 @@
|
|||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"config-point": "^0.4.8",
|
|
||||||
"core-js": "^3.16.1",
|
"core-js": "^3.16.1",
|
||||||
"cornerstone-math": "^0.1.9",
|
"cornerstone-math": "^0.1.9",
|
||||||
"@cornerstonejs/dicom-image-loader": "^0.6.8",
|
"@cornerstonejs/dicom-image-loader": "^0.6.8",
|
||||||
|
|||||||
@ -48,9 +48,9 @@ window.config = {
|
|||||||
// wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
// wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||||
|
|
||||||
// new server
|
// new server
|
||||||
wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
|
||||||
qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
|
||||||
wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
|
||||||
|
|
||||||
qidoSupportsIncludeField: false,
|
qidoSupportsIncludeField: false,
|
||||||
supportsReject: false,
|
supportsReject: false,
|
||||||
@ -60,8 +60,14 @@ window.config = {
|
|||||||
supportsFuzzyMatching: false,
|
supportsFuzzyMatching: false,
|
||||||
supportsWildcard: true,
|
supportsWildcard: true,
|
||||||
staticWado: true,
|
staticWado: true,
|
||||||
singlepart: 'bulkdata,video,pdf',
|
singlepart: 'bulkdata,video',
|
||||||
useBulkDataURI: false,
|
// 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',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -22,7 +22,9 @@ window.config = {
|
|||||||
qidoSupportsIncludeField: true,
|
qidoSupportsIncludeField: true,
|
||||||
imageRendering: 'wadors',
|
imageRendering: 'wadors',
|
||||||
enableStudyLazyLoad: true,
|
enableStudyLazyLoad: true,
|
||||||
useBulkDataURI: false,
|
bulkDataURI: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -33,6 +33,12 @@ window.config = {
|
|||||||
},
|
},
|
||||||
dicomUploadEnabled: true,
|
dicomUploadEnabled: true,
|
||||||
singlepart: 'pdf,video',
|
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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -29,10 +29,12 @@ window.config = {
|
|||||||
imageRendering: 'wadors',
|
imageRendering: 'wadors',
|
||||||
thumbnailRendering: 'wadors',
|
thumbnailRendering: 'wadors',
|
||||||
enableStudyLazyLoad: true,
|
enableStudyLazyLoad: true,
|
||||||
useBulkDataURI: false,
|
|
||||||
supportsFuzzyMatching: true,
|
supportsFuzzyMatching: true,
|
||||||
supportsWildcard: true,
|
supportsWildcard: true,
|
||||||
dicomUploadEnabled: true,
|
dicomUploadEnabled: true,
|
||||||
|
bulkDataURI: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -343,6 +343,7 @@ function WorkList({
|
|||||||
|
|
||||||
const isValidMode = mode.isValidMode({
|
const isValidMode = mode.isValidMode({
|
||||||
modalities: modalitiesToCheck,
|
modalities: modalitiesToCheck,
|
||||||
|
study,
|
||||||
});
|
});
|
||||||
// TODO: Modes need a default/target route? We mostly support a single one for now.
|
// 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
|
// We should also be using the route path, but currently are not
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@ -1302,7 +1302,7 @@
|
|||||||
core-js-pure "^3.25.1"
|
core-js-pure "^3.25.1"
|
||||||
regenerator-runtime "^0.13.11"
|
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"
|
version "7.21.0"
|
||||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||||
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||||
@ -7461,14 +7461,6 @@ config-chain@^1.1.11:
|
|||||||
ini "^1.3.4"
|
ini "^1.3.4"
|
||||||
proto-list "~1.2.1"
|
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:
|
configstore@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user