feat(findingSite): Allow customizing the measurement panel and show site/finding (#3155)
* feat: Allow customizing the measurements panel. Also includes default display of the site/finding values from the DICOM SR object. Update fixes PR review comments - move the recordInteraction to COmmandsManager.run PR fixes fix: Dependency updates to allow right click to be recognized PR fixes * Use newer CSWIL * Make both the base measurements and the tracking measurements show codes * Fix the right click context menu bringing up menu
This commit is contained in:
parent
dd22e37e4c
commit
8e9d7bd2bb
@ -35,7 +35,7 @@ function getFilteredCornerstoneToolState(
|
|||||||
);
|
);
|
||||||
const toolData = imageIdSpecificToolState[toolType].data;
|
const toolData = imageIdSpecificToolState[toolType].data;
|
||||||
|
|
||||||
let finding;
|
let { finding } = measurementDataI;
|
||||||
const findingSites = [];
|
const findingSites = [];
|
||||||
|
|
||||||
// NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have
|
// NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have
|
||||||
@ -56,6 +56,10 @@ function getFilteredCornerstoneToolState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (measurementDataI.findingSites) {
|
||||||
|
findingSites.push(...measurementDataI.findingSites);
|
||||||
|
}
|
||||||
|
|
||||||
const measurement = Object.assign({}, annotation, {
|
const measurement = Object.assign({}, annotation, {
|
||||||
finding,
|
finding,
|
||||||
findingSites,
|
findingSites,
|
||||||
@ -73,7 +77,7 @@ function getFilteredCornerstoneToolState(
|
|||||||
for (let i = 0; i < framesOfReference.length; i++) {
|
for (let i = 0; i < framesOfReference.length; i++) {
|
||||||
const frameOfReference = framesOfReference[i];
|
const frameOfReference = framesOfReference[i];
|
||||||
|
|
||||||
const frameOfReferenceAnnotations = annotationManager.getFrameOfReferenceAnnotations(
|
const frameOfReferenceAnnotations = annotationManager.getAnnotations(
|
||||||
frameOfReference
|
frameOfReference
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,25 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||||
|
|
||||||
|
const convertCode = (codingValues, code) => {
|
||||||
|
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
|
||||||
|
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
|
||||||
|
const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning };
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertSites = (codingValues, sites) => {
|
||||||
|
if (!sites || !sites.length) return;
|
||||||
|
const ret = [];
|
||||||
|
// Do as a loop to convert away from Proxy instances
|
||||||
|
for (let i = 0; i < sites.length; i++) {
|
||||||
|
// Deal with irregular conversion from dcmjs
|
||||||
|
const site = convertCode(codingValues, sites[i][0] || sites[i]);
|
||||||
|
if (site) ret.push(site);
|
||||||
|
}
|
||||||
|
return (ret.length && ret) || undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hydrates a structured report, for default viewports.
|
* Hydrates a structured report, for default viewports.
|
||||||
*
|
*
|
||||||
@ -20,8 +39,16 @@ export default function hydrateStructuredReport(
|
|||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||||
const { measurementService, displaySetService } = servicesManager.services;
|
const {
|
||||||
|
measurementService,
|
||||||
|
displaySetService,
|
||||||
|
customizationService,
|
||||||
|
} = servicesManager.services;
|
||||||
|
|
||||||
|
const codingValues = customizationService.getCustomization(
|
||||||
|
'codingValues',
|
||||||
|
{}
|
||||||
|
);
|
||||||
const displaySet = displaySetService.getDisplaySetByUID(
|
const displaySet = displaySetService.getDisplaySetByUID(
|
||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
@ -171,6 +198,15 @@ export default function hydrateStructuredReport(
|
|||||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||||
);
|
);
|
||||||
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
||||||
|
annotation.data.finding = convertCode(
|
||||||
|
codingValues,
|
||||||
|
toolData.finding?.[0]
|
||||||
|
);
|
||||||
|
annotation.data.findingSites = convertSites(
|
||||||
|
codingValues,
|
||||||
|
toolData.findingSites
|
||||||
|
);
|
||||||
|
annotation.data.site = annotation.data.findingSites?.[0];
|
||||||
|
|
||||||
const matchingMapping = mappings.find(
|
const matchingMapping = mappings.find(
|
||||||
m => m.annotationType === annotationType
|
m => m.annotationType === annotationType
|
||||||
|
|||||||
@ -222,16 +222,6 @@ function commandsModule({ servicesManager, commandsManager }) {
|
|||||||
updatedMeasurement.findingSites = [code];
|
updatedMeasurement.findingSites = [code];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO - remove this once measurement items customization is ready
|
|
||||||
const allCodes = [];
|
|
||||||
if (textLabel) allCodes.push(textLabel);
|
|
||||||
if (updatedMeasurement.finding) {
|
|
||||||
allCodes.push(updatedMeasurement.finding.CodeMeaning);
|
|
||||||
}
|
|
||||||
(updatedMeasurement.findingSites || []).forEach(it =>
|
|
||||||
allCodes.push(it.CodeMeaning)
|
|
||||||
);
|
|
||||||
updatedMeasurement.label = allCodes.join(', ');
|
|
||||||
}
|
}
|
||||||
measurementService.update(
|
measurementService.update(
|
||||||
updatedMeasurement.uid,
|
updatedMeasurement.uid,
|
||||||
|
|||||||
@ -218,6 +218,7 @@ export default function PanelMeasurementTable({
|
|||||||
>
|
>
|
||||||
<MeasurementTable
|
<MeasurementTable
|
||||||
title="Measurements"
|
title="Measurements"
|
||||||
|
servicesManager={servicesManager}
|
||||||
data={displayMeasurements}
|
data={displayMeasurements}
|
||||||
onClick={jumpToImage}
|
onClick={jumpToImage}
|
||||||
onEdit={onMeasurementItemEditHandler}
|
onEdit={onMeasurementItemEditHandler}
|
||||||
@ -248,14 +249,46 @@ function _getMappedMeasurements(measurementService) {
|
|||||||
return mappedMeasurements;
|
return mappedMeasurements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the measurements to the display text.
|
||||||
|
* Adds finding and site inforamtion to the displayText and/or label,
|
||||||
|
* and provides as 'displayText' and 'label', while providing the original
|
||||||
|
* values as baseDisplayText and baseLabel
|
||||||
|
*/
|
||||||
function _mapMeasurementToDisplay(measurement, index, types) {
|
function _mapMeasurementToDisplay(measurement, index, types) {
|
||||||
const { displayText, uid, label, type, selected } = measurement;
|
const {
|
||||||
|
displayText: baseDisplayText,
|
||||||
|
uid,
|
||||||
|
label: baseLabel,
|
||||||
|
type,
|
||||||
|
selected,
|
||||||
|
findingSites,
|
||||||
|
finding,
|
||||||
|
} = measurement;
|
||||||
|
|
||||||
|
const firstSite = findingSites?.[0];
|
||||||
|
const label = baseLabel || finding?.text || firstSite?.text || '(empty)';
|
||||||
|
let displayText = baseDisplayText || [];
|
||||||
|
if (findingSites) {
|
||||||
|
const siteText = [];
|
||||||
|
findingSites.forEach(site => {
|
||||||
|
if (site?.text !== label) siteText.push(site.text);
|
||||||
|
});
|
||||||
|
displayText = [...siteText, ...displayText];
|
||||||
|
}
|
||||||
|
if (finding && finding?.text !== label) {
|
||||||
|
displayText = [finding.text, ...displayText];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid,
|
uid,
|
||||||
label: label || '(empty)',
|
label,
|
||||||
|
baseLabel,
|
||||||
measurementType: type,
|
measurementType: type,
|
||||||
displayText: displayText || [],
|
displayText,
|
||||||
|
baseDisplayText,
|
||||||
isActive: selected,
|
isActive: selected,
|
||||||
|
finding,
|
||||||
|
findingSites,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,7 +129,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
|||||||
setMeasurementsUpdated(Date.now().toString());
|
setMeasurementsUpdated(Date.now().toString());
|
||||||
if (evt === added) {
|
if (evt === added) {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
measurementsPanelRef.current.scrollTop = measurementsPanelRef.current.scrollHeight;
|
measurementsPanelRef.current.scrollTop =
|
||||||
|
measurementsPanelRef.current.scrollHeight;
|
||||||
}, 300)();
|
}, 300)();
|
||||||
}
|
}
|
||||||
}).unsubscribe
|
}).unsubscribe
|
||||||
@ -261,6 +262,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
|||||||
<MeasurementTable
|
<MeasurementTable
|
||||||
title="Measurements"
|
title="Measurements"
|
||||||
data={displayMeasurementsWithoutFindings}
|
data={displayMeasurementsWithoutFindings}
|
||||||
|
servicesManager={servicesManager}
|
||||||
onClick={jumpToImage}
|
onClick={jumpToImage}
|
||||||
onEdit={onMeasurementItemEditHandler}
|
onEdit={onMeasurementItemEditHandler}
|
||||||
/>
|
/>
|
||||||
@ -268,6 +270,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
|||||||
<MeasurementTable
|
<MeasurementTable
|
||||||
title="Additional Findings"
|
title="Additional Findings"
|
||||||
data={additionalFindings}
|
data={additionalFindings}
|
||||||
|
servicesManager={servicesManager}
|
||||||
onClick={jumpToImage}
|
onClick={jumpToImage}
|
||||||
onEdit={onMeasurementItemEditHandler}
|
onEdit={onMeasurementItemEditHandler}
|
||||||
/>
|
/>
|
||||||
@ -326,13 +329,40 @@ function _mapMeasurementToDisplay(measurement, types, displaySetService) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { displayText } = measurement;
|
const {
|
||||||
|
displayText: baseDisplayText,
|
||||||
|
uid,
|
||||||
|
label: baseLabel,
|
||||||
|
type,
|
||||||
|
selected,
|
||||||
|
findingSites,
|
||||||
|
finding,
|
||||||
|
} = measurement;
|
||||||
|
|
||||||
|
const firstSite = findingSites?.[0];
|
||||||
|
const label = baseLabel || finding?.text || firstSite?.text || '(empty)';
|
||||||
|
let displayText = baseDisplayText || [];
|
||||||
|
if (findingSites) {
|
||||||
|
const siteText = [];
|
||||||
|
findingSites.forEach(site => {
|
||||||
|
if (site?.text !== label) siteText.push(site.text);
|
||||||
|
});
|
||||||
|
displayText = [...siteText, ...displayText];
|
||||||
|
}
|
||||||
|
if (finding && finding?.text !== label) {
|
||||||
|
displayText = [finding.text, ...displayText];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: measurement.uid,
|
uid,
|
||||||
label: measurement.label || '(empty)',
|
label,
|
||||||
measurementType: measurement.type,
|
baseLabel,
|
||||||
displayText: displayText || [],
|
measurementType: type,
|
||||||
isActive: measurement.selected,
|
displayText,
|
||||||
|
baseDisplayText,
|
||||||
|
isActive: selected,
|
||||||
|
finding,
|
||||||
|
findingSites,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ const findingsContextMenu = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
id: 'orientationSelectionSubMenu',
|
id: 'orientationSelectionSubMenu',
|
||||||
|
selector: ({ nearbyToolData }) => false,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
customizationType: '@ohif/contextMenuAnnotationCode',
|
customizationType: '@ohif/contextMenuAnnotationCode',
|
||||||
@ -66,7 +67,7 @@ const findingsContextMenu = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
id: 'findingSelectionSubMenu',
|
id: 'findingSelectionSubMenu',
|
||||||
selector: ({ nearbyToolData }) => !!nearbyToolData,
|
selector: ({ nearbyToolData }) => false,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
customizationType: '@ohif/contextMenuAnnotationCode',
|
customizationType: '@ohif/contextMenuAnnotationCode',
|
||||||
|
|||||||
@ -459,7 +459,12 @@ class MeasurementService extends PubSubService {
|
|||||||
log.warn(`Measurement ID not found. Generating UID: ${internalUID}`);
|
log.warn(`Measurement ID not found. Generating UID: ${internalUID}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const annotationData = data.annotation.data;
|
||||||
|
|
||||||
const newMeasurement = {
|
const newMeasurement = {
|
||||||
|
finding: annotationData.finding,
|
||||||
|
findingSites: annotationData.findingSites,
|
||||||
|
site: annotationData.findingSites?.[0],
|
||||||
...measurement,
|
...measurement,
|
||||||
modifiedTimestamp: Math.floor(Date.now() / 1000),
|
modifiedTimestamp: Math.floor(Date.now() / 1000),
|
||||||
uid: internalUID,
|
uid: internalUID,
|
||||||
@ -472,7 +477,7 @@ class MeasurementService extends PubSubService {
|
|||||||
measurement: newMeasurement,
|
measurement: newMeasurement,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info(`Measurement added.`, newMeasurement);
|
log.info('Measurement added', newMeasurement);
|
||||||
this.measurements[internalUID] = newMeasurement;
|
this.measurements[internalUID] = newMeasurement;
|
||||||
this._broadcastEvent(this.EVENTS.RAW_MEASUREMENT_ADDED, {
|
this._broadcastEvent(this.EVENTS.RAW_MEASUREMENT_ADDED, {
|
||||||
source,
|
source,
|
||||||
@ -553,26 +558,27 @@ class MeasurementService extends PubSubService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldMeasurement = this.measurements[internalUID];
|
||||||
|
|
||||||
const newMeasurement = {
|
const newMeasurement = {
|
||||||
|
...oldMeasurement,
|
||||||
...measurement,
|
...measurement,
|
||||||
modifiedTimestamp: Math.floor(Date.now() / 1000),
|
modifiedTimestamp: Math.floor(Date.now() / 1000),
|
||||||
uid: internalUID,
|
uid: internalUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.measurements[internalUID]) {
|
if (oldMeasurement) {
|
||||||
// TODO: Ultimately, each annotation should have a selected flag right from the soure.
|
// TODO: Ultimately, each annotation should have a selected flag right from the soure.
|
||||||
// For now, it is just added in OHIF here and in setMeasurementSelected.
|
// For now, it is just added in OHIF here and in setMeasurementSelected.
|
||||||
newMeasurement.selected = this.measurements[internalUID].selected;
|
|
||||||
this.measurements[internalUID] = newMeasurement;
|
this.measurements[internalUID] = newMeasurement;
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
|
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
|
||||||
source,
|
source,
|
||||||
measurement: newMeasurement,
|
measurement: newMeasurement,
|
||||||
notYetUpdatedAtSource: false,
|
notYetUpdatedAtSource: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info('Measurement added.', newMeasurement);
|
log.info('Measurement added.', newMeasurement);
|
||||||
this.measurements[internalUID] = newMeasurement;
|
|
||||||
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
|
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
|
||||||
source,
|
source,
|
||||||
measurement: newMeasurement,
|
measurement: newMeasurement,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const MeasurementItem = ({
|
|||||||
isActive,
|
isActive,
|
||||||
onClick,
|
onClick,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
item,
|
||||||
}) => {
|
}) => {
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ServicesManager } from '@ohif/core';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import MeasurementItem from './MeasurementItem';
|
import MeasurementItem from './MeasurementItem';
|
||||||
|
|
||||||
const MeasurementTable = ({ data, title, onClick, onEdit }) => {
|
const MeasurementTable = ({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
onClick,
|
||||||
|
onEdit,
|
||||||
|
servicesManager,
|
||||||
|
}) => {
|
||||||
|
servicesManager = servicesManager as ServicesManager;
|
||||||
|
const { customizationService } = servicesManager.services;
|
||||||
const { t } = useTranslation('MeasurementTable');
|
const { t } = useTranslation('MeasurementTable');
|
||||||
const amount = data.length;
|
const amount = data.length;
|
||||||
|
|
||||||
|
const itemCustomization = customizationService.getCustomization(
|
||||||
|
'MeasurementItem',
|
||||||
|
{
|
||||||
|
content: MeasurementItem,
|
||||||
|
contentProps: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const CustomMeasurementItem = itemCustomization.content;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between px-2 py-1 bg-secondary-main">
|
<div className="flex justify-between px-2 py-1 bg-secondary-main">
|
||||||
@ -19,13 +37,14 @@ const MeasurementTable = ({ data, title, onClick, onEdit }) => {
|
|||||||
<div className="overflow-hidden ohif-scrollbar max-h-112">
|
<div className="overflow-hidden ohif-scrollbar max-h-112">
|
||||||
{data.length !== 0 &&
|
{data.length !== 0 &&
|
||||||
data.map((measurementItem, index) => (
|
data.map((measurementItem, index) => (
|
||||||
<MeasurementItem
|
<CustomMeasurementItem
|
||||||
key={measurementItem.uid}
|
key={measurementItem.uid}
|
||||||
uid={measurementItem.uid}
|
uid={measurementItem.uid}
|
||||||
index={index + 1}
|
index={index + 1}
|
||||||
label={measurementItem.label}
|
label={measurementItem.label}
|
||||||
isActive={measurementItem.isActive}
|
isActive={measurementItem.isActive}
|
||||||
displayText={measurementItem.displayText}
|
displayText={measurementItem.displayText}
|
||||||
|
item={measurementItem}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
/>
|
/>
|
||||||
|
|||||||
18
yarn.lock
18
yarn.lock
@ -8519,23 +8519,7 @@ cornerstone-math@^0.1.9:
|
|||||||
resolved "https://registry.npmjs.org/cornerstone-math/-/cornerstone-math-0.1.10.tgz#a3f99db64d73c5adee61ae0d570128eca1682d07"
|
resolved "https://registry.npmjs.org/cornerstone-math/-/cornerstone-math-0.1.10.tgz#a3f99db64d73c5adee61ae0d570128eca1682d07"
|
||||||
integrity sha512-23XSAyP7t70ANvhFyqwvva+zFd1bQ2d5GL7tg9qKE932WmImjA2Y9tiy5n0iTtnf51W/78Png8Lia2o4dCdJaQ==
|
integrity sha512-23XSAyP7t70ANvhFyqwvva+zFd1bQ2d5GL7tg9qKE932WmImjA2Y9tiy5n0iTtnf51W/78Png8Lia2o4dCdJaQ==
|
||||||
|
|
||||||
cornerstone-wado-image-loader@^4.10.0:
|
cornerstone-wado-image-loader@^4.10.0, cornerstone-wado-image-loader@^4.10.2:
|
||||||
version "4.10.2"
|
|
||||||
resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.10.2.tgz#139956654324fd2b01fe5b4900d0f4ed8c52633d"
|
|
||||||
integrity sha512-qj9dThELqYCm3jAZfg9qnUl8d76gngOl55kYJabY5lh/dFeVIxno/hYxy3ydE7RtG2c/TUGXb+EMUl0CJSqKBQ==
|
|
||||||
dependencies:
|
|
||||||
"@babel/eslint-parser" "^7.19.1"
|
|
||||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
|
||||||
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
|
||||||
"@cornerstonejs/codec-openjph" "^2.4.2"
|
|
||||||
coverage-istanbul-loader "^3.0.5"
|
|
||||||
date-format "^4.0.14"
|
|
||||||
dicom-parser "^1.8.9"
|
|
||||||
pako "^2.0.4"
|
|
||||||
uuid "^9.0.0"
|
|
||||||
|
|
||||||
cornerstone-wado-image-loader@^4.10.2:
|
|
||||||
version "4.10.2"
|
version "4.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.10.2.tgz#139956654324fd2b01fe5b4900d0f4ed8c52633d"
|
resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.10.2.tgz#139956654324fd2b01fe5b4900d0f4ed8c52633d"
|
||||||
integrity sha512-qj9dThELqYCm3jAZfg9qnUl8d76gngOl55kYJabY5lh/dFeVIxno/hYxy3ydE7RtG2c/TUGXb+EMUl0CJSqKBQ==
|
integrity sha512-qj9dThELqYCm3jAZfg9qnUl8d76gngOl55kYJabY5lh/dFeVIxno/hYxy3ydE7RtG2c/TUGXb+EMUl0CJSqKBQ==
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user