From 246ebab6ebf5431a704a1861a5804045b9644ba4 Mon Sep 17 00:00:00 2001 From: Leonardo Campos Date: Tue, 23 Jul 2024 12:28:48 -0300 Subject: [PATCH] feat(SR): text structured report (TEXT, CODE, NUM, PNAME, DATE, TIME and DATETIME) (#4287) --- .../components/OHIFCornerstoneSRContainer.tsx | 78 +++++++++++++++++++ .../OHIFCornerstoneSRContentItem.tsx | 63 +++++++++++++++ .../OHIFCornerstoneSRMeasurementViewport.tsx} | 22 +++--- .../OHIFCornerstoneSRTextViewport.tsx | 32 ++++++++ .../components/OHIFCornerstoneSRViewport.tsx | 30 +++++++ .../constants/CodeNameCodeSequenceValues.ts | 18 +++++ .../src/getSopClassHandlerModule.ts | 58 ++++++-------- extensions/cornerstone-dicom-sr/src/index.tsx | 2 +- .../src/utils/formatContentItem.ts | 67 ++++++++++++++++ modes/longitudinal/src/index.ts | 4 +- platform/core/src/utils/formatTime.ts | 12 +++ platform/core/src/utils/index.test.js | 1 + platform/core/src/utils/index.ts | 2 + 13 files changed, 341 insertions(+), 48 deletions(-) create mode 100644 extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx create mode 100644 extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx rename extensions/cornerstone-dicom-sr/src/{viewports/OHIFCornerstoneSRViewport.tsx => components/OHIFCornerstoneSRMeasurementViewport.tsx} (97%) create mode 100644 extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx create mode 100644 extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx create mode 100644 extensions/cornerstone-dicom-sr/src/constants/CodeNameCodeSequenceValues.ts create mode 100644 extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts create mode 100644 platform/core/src/utils/formatTime.ts diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx new file mode 100644 index 000000000..461eba1ac --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContainer.tsx @@ -0,0 +1,78 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { OHIFCornerstoneSRContentItem } from './OHIFCornerstoneSRContentItem'; + +export function OHIFCornerstoneSRContainer(props) { + const { container, nodeIndexesTree = [0], containerNumberedTree = [1] } = props; + const { ContinuityOfContent, ConceptNameCodeSequence } = container; + const { CodeMeaning } = ConceptNameCodeSequence ?? {}; + let childContainerIndex = 1; + const contentItems = container.ContentSequence?.map((contentItem, i) => { + const { ValueType } = contentItem; + const childNodeLevel = [...nodeIndexesTree, i]; + const key = childNodeLevel.join('.'); + + let Component; + let componentProps; + + if (ValueType === 'CONTAINER') { + const childContainerNumberedTree = [...containerNumberedTree, childContainerIndex++]; + + Component = OHIFCornerstoneSRContainer; + componentProps = { + container: contentItem, + nodeIndexesTree: childNodeLevel, + containerNumberedTree: childContainerNumberedTree, + }; + } else { + Component = OHIFCornerstoneSRContentItem; + componentProps = { + contentItem, + nodeIndexesTree: childNodeLevel, + continuityOfContent: ContinuityOfContent, + }; + } + + return ( + + ); + }); + + return ( +
+
+ {containerNumberedTree.join('.')}.  + {CodeMeaning} +
+
{contentItems}
+
+ ); +} + +OHIFCornerstoneSRContainer.propTypes = { + /** + * A tree node that may contain another container or one or more content items + * (text, code, uidref, pname, etc.) + */ + container: PropTypes.object, + /** + * A 0-based index list + */ + nodeIndexesTree: PropTypes.arrayOf(PropTypes.number), + /** + * A 1-based index list that represents a container in a multi-level numbered + * list (tree). + * + * Example: + * 1. History + * 1.1. Chief Complaint + * 1.2. Present Illness + * 1.3. Past History + * 1.4. Family History + * 2. Findings + * */ + containerNumberedTree: PropTypes.arrayOf(PropTypes.number), +}; diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx new file mode 100644 index 000000000..b12aafb7b --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRContentItem.tsx @@ -0,0 +1,63 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import CodeNameCodeSequenceValues from '../constants/CodeNameCodeSequenceValues'; +import formatContentItemValue from '../utils/formatContentItem'; + +const EMPTY_TAG_VALUE = '[empty]'; + +function OHIFCornerstoneSRContentItem(props) { + const { contentItem, nodeIndexesTree, continuityOfContent } = props; + const { ConceptNameCodeSequence } = contentItem; + const { CodeValue, CodeMeaning } = ConceptNameCodeSequence; + const isChildFirstNode = nodeIndexesTree[nodeIndexesTree.length - 1] === 0; + const formattedValue = formatContentItemValue(contentItem) ?? EMPTY_TAG_VALUE; + const startWithAlphaNumCharRegEx = /^[a-zA-Z0-9]/; + const isContinuous = continuityOfContent === 'CONTINUOUS'; + const isFinding = CodeValue === CodeNameCodeSequenceValues.Finding; + const addExtraSpace = + isContinuous && !isChildFirstNode && startWithAlphaNumCharRegEx.test(formattedValue?.[0]); + + // Collapse sequences of white space preserving newline characters + let className = 'whitespace-pre-line'; + + if (CodeValue === CodeNameCodeSequenceValues.Finding) { + // Preserve spaces because it is common to see tabular text in a + // "Findings" ConceptNameCodeSequence + className = 'whitespace-pre-wrap'; + } + + if (isContinuous) { + return ( + <> + + {addExtraSpace ? ' ' : ''} + {formattedValue} + + + ); + } + + return ( + <> +
+ {CodeMeaning}: + {isFinding ? ( +
{formattedValue}
+ ) : ( + {formattedValue} + )} +
+ + ); +} + +OHIFCornerstoneSRContentItem.propTypes = { + contentItem: PropTypes.object, + nodeIndexesTree: PropTypes.arrayOf(PropTypes.number), + continuityOfContent: PropTypes.string, +}; + +export { OHIFCornerstoneSRContentItem }; diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx similarity index 97% rename from extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx rename to extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx index 6ceb7787c..e6f50fe22 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx @@ -14,7 +14,7 @@ const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking' const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; -function OHIFCornerstoneSRViewport(props: withAppTypes) { +function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } = props; @@ -278,14 +278,14 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) { */ useEffect(() => { const updateSR = async () => { - if (!srDisplaySet.isLoaded) { - await srDisplaySet.load(); - } - if (!element || !srDisplaySet.isLoaded) { - return; - } - setTrackingIdentifiers(measurementSelected); - } + if (!srDisplaySet.isLoaded) { + await srDisplaySet.load(); + } + if (!element || !srDisplaySet.isLoaded) { + return; + } + setTrackingIdentifiers(measurementSelected); + }; updateSR(); }, [measurementSelected, element, setTrackingIdentifiers, srDisplaySet]); @@ -367,7 +367,7 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) { ); } -OHIFCornerstoneSRViewport.propTypes = { +OHIFCornerstoneSRMeasurementViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, @@ -508,4 +508,4 @@ function _getStatusComponent({ ); } -export default OHIFCornerstoneSRViewport; +export default OHIFCornerstoneSRMeasurementViewport; diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx new file mode 100644 index 000000000..78d4929b7 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRTextViewport.tsx @@ -0,0 +1,32 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { ExtensionManager } from '@ohif/core'; +import { OHIFCornerstoneSRContainer } from './OHIFCornerstoneSRContainer'; + +function OHIFCornerstoneSRTextViewport(props: withAppTypes) { + const { displaySets } = props; + const displaySet = displaySets[0]; + const instance = displaySet.instances[0]; + + return ( +
+
+ {/* The root level is always a container */} + +
+
+ ); +} + +OHIFCornerstoneSRTextViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object), + viewportId: PropTypes.string.isRequired, + dataSource: PropTypes.object, + children: PropTypes.node, + viewportLabel: PropTypes.string, + viewportOptions: PropTypes.object, + servicesManager: PropTypes.object.isRequired, + extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired, +}; + +export default OHIFCornerstoneSRTextViewport; diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx new file mode 100644 index 000000000..3bbb73a5b --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRViewport.tsx @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { ExtensionManager } from '@ohif/core'; + +import OHIFCornerstoneSRMeasurementViewport from './OHIFCornerstoneSRMeasurementViewport'; +import OHIFCornerstoneSRTextViewport from './OHIFCornerstoneSRTextViewport'; + +function OHIFCornerstoneSRViewport(props: withAppTypes) { + const { displaySets } = props; + const { isImagingMeasurementReport } = displaySets[0]; + + if (isImagingMeasurementReport) { + return ; + } + + return ; +} + +OHIFCornerstoneSRViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object), + viewportId: PropTypes.string.isRequired, + dataSource: PropTypes.object, + children: PropTypes.node, + viewportLabel: PropTypes.string, + viewportOptions: PropTypes.object, + servicesManager: PropTypes.object.isRequired, + extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired, +}; + +export default OHIFCornerstoneSRViewport; diff --git a/extensions/cornerstone-dicom-sr/src/constants/CodeNameCodeSequenceValues.ts b/extensions/cornerstone-dicom-sr/src/constants/CodeNameCodeSequenceValues.ts new file mode 100644 index 000000000..cba848cbe --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/constants/CodeNameCodeSequenceValues.ts @@ -0,0 +1,18 @@ +import { adaptersSR } from '@cornerstonejs/adapters'; + +const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D; + +const CodeNameCodeSequenceValues = { + ImagingMeasurementReport: '126000', + ImageLibrary: '111028', + ImagingMeasurements: '126010', + MeasurementGroup: '125007', + ImageLibraryGroup: '126200', + TrackingUniqueIdentifier: '112040', + TrackingIdentifier: '112039', + Finding: '121071', + FindingSite: 'G-C0E3', // SRT + CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT, +}; + +export { CodeNameCodeSequenceValues as default }; diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index 73789049f..9c035faa2 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -3,6 +3,7 @@ import { utils, classes, DisplaySetService, Types } from '@ohif/core'; import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation'; import isRehydratable from './utils/isRehydratable'; import { adaptersSR } from '@cornerstonejs/adapters'; +import CodeNameCodeSequenceValues from './constants/CodeNameCodeSequenceValues'; type InstanceMetadata = Types.InstanceMetadata; @@ -16,10 +17,11 @@ const { ImageSet, MetadataProvider: metadataProvider } = classes; // Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport. const sopClassUids = [ - '1.2.840.10008.5.1.4.1.1.88.11', //BASIC_TEXT_SR: - '1.2.840.10008.5.1.4.1.1.88.22', //ENHANCED_SR: - '1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR: - '1.2.840.10008.5.1.4.1.1.88.34', //COMPREHENSIVE_3D_SR: + '1.2.840.10008.5.1.4.1.1.88.11', // BASIC TEXT SR + '1.2.840.10008.5.1.4.1.1.88.22', // ENHANCED SR + '1.2.840.10008.5.1.4.1.1.88.33', // COMPREHENSIVE SR + '1.2.840.10008.5.1.4.1.1.88.34', // Comprehensive 3D SR + // '1.2.840.10008.5.1.4.1.1.88.50', // Mammography CAD SR ]; const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; @@ -34,19 +36,6 @@ const validateSameStudyUID = (uid: string, instances): void => { }); }; -const CodeNameCodeSequenceValues = { - ImagingMeasurementReport: '126000', - ImageLibrary: '111028', - ImagingMeasurements: '126010', - MeasurementGroup: '125007', - ImageLibraryGroup: '126200', - TrackingUniqueIdentifier: '112040', - TrackingIdentifier: '112039', - Finding: '121071', - FindingSite: 'G-C0E3', // SRT - CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT, // -}; - const CodingSchemeDesignators = { SRT: 'SRT', CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'], @@ -113,22 +102,10 @@ function _getDisplaySetsFromSeries( } = instance; validateSameStudyUID(instance.StudyInstanceUID, instances); - if ( - !ConceptNameCodeSequence || - ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport - ) { - servicesManager.services.uiNotificationService.show({ - title: 'DICOM SR', - message: - 'OHIF only supports TID1500 Imaging Measurement Report Structured Reports. The SR you’re trying to view is not supported.', - type: 'warning', - duration: 6000, - }); - return []; - } + const isImagingMeasurementReport = + ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport; const displaySet = { - //plugin: id, Modality: 'SR', displaySetInstanceUID: utils.guid(), SeriesDescription, @@ -144,6 +121,7 @@ function _getDisplaySetsFromSeries( measurements: null, isDerivedDisplaySet: true, isLoaded: false, + isImagingMeasurementReport, sopClassUids, instance, addInstances, @@ -158,7 +136,6 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte const { displaySetService, measurementService } = servicesManager.services; const dataSources = extensionManager.getDataSources(); const dataSource = dataSources[0]; - const { ContentSequence } = displaySet.instance; async function retrieveBulkData(obj, parentObj = null, key = null) { @@ -185,8 +162,13 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte await retrieveBulkData(ContentSequence); } - displaySet.referencedImages = _getReferencedImagesList(ContentSequence); - displaySet.measurements = _getMeasurements(ContentSequence); + if (displaySet.isImagingMeasurementReport) { + displaySet.referencedImages = _getReferencedImagesList(ContentSequence); + displaySet.measurements = _getMeasurements(ContentSequence); + } else { + displaySet.referencedImages = []; + displaySet.measurements = []; + } const mappings = measurementService.getSourceMappings( CORNERSTONE_3D_TOOLS_SOURCE_NAME, @@ -371,6 +353,10 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) { item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements ); + if (!ImagingMeasurements) { + return []; + } + const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter( item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup ); @@ -625,6 +611,10 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary ); + if (!ImageLibrary) { + return []; + } + const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find( item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup ); diff --git a/extensions/cornerstone-dicom-sr/src/index.tsx b/extensions/cornerstone-dicom-sr/src/index.tsx index b0b5e7abb..e479307d2 100644 --- a/extensions/cornerstone-dicom-sr/src/index.tsx +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -10,7 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport'; import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet'; const Component = React.lazy(() => { - return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport'); + return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport'); }); const OHIFCornerstoneSRViewport = props => { diff --git a/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts b/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts new file mode 100644 index 000000000..144607755 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts @@ -0,0 +1,67 @@ +import { utils } from '@ohif/core'; + +/** + * Formatters used to format each of the content items (SR "nodes") which can be + * text, code, UID ref, number, person name, date, time and date time. Each + * formatter must be a function with the following signature: + * + * [VALUE_TYPE]: (contentItem) => string + * + */ +const contentItemFormatters = { + TEXT: contentItem => contentItem.TextValue, + CODE: contentItem => contentItem.ConceptCodeSequence?.[0]?.CodeMeaning, + UIDREF: contentItem => contentItem.UID, + NUM: contentItem => { + const measuredValue = contentItem.MeasuredValueSequence?.[0]; + + if (!measuredValue) { + return; + } + + const { NumericValue, MeasurementUnitsCodeSequence } = measuredValue; + const { CodeValue } = MeasurementUnitsCodeSequence; + + return `${NumericValue} ${CodeValue}`; + }, + PNAME: contentItem => { + const personName = contentItem.PersonName?.[0]?.Alphabetic; + return personName ? utils.formatPN(personName) : undefined; + }, + DATE: contentItem => { + const { Date } = contentItem; + return Date ? utils.formatDate(Date) : undefined; + }, + TIME: contentItem => { + const { Time } = contentItem; + return Time ? utils.formatTime(Time) : undefined; + }, + DATETIME: contentItem => { + const { DateTime } = contentItem; + + if (typeof DateTime !== 'string') { + return; + } + + // 14 characters because it should be something like 20180614113714 + if (DateTime.length < 14) { + return DateTime; + } + + const dicomDate = DateTime.substring(0, 8); + const dicomTime = DateTime.substring(8, 14); + const formattedDate = utils.formatDate(dicomDate); + const formattedTime = utils.formatTime(dicomTime); + + return `${formattedDate} ${formattedTime}`; + }, +}; + +function formatContentItemValue(contentItem) { + const { ValueType } = contentItem; + const fnFormat = contentItemFormatters[ValueType]; + + return fnFormat ? fnFormat(contentItem) : `[${ValueType} is not supported]`; +} + +export { formatContentItemValue as default, formatContentItemValue }; diff --git a/modes/longitudinal/src/index.ts b/modes/longitudinal/src/index.ts index 0da0a69cc..08362cafe 100644 --- a/modes/longitudinal/src/index.ts +++ b/modes/longitudinal/src/index.ts @@ -7,7 +7,7 @@ import moreTools from './moreTools'; // Allow this mode by excluding non-imaging modalities such as SR, SEG // Also, SM is not a simple imaging modalities, so exclude it. -const NON_IMAGE_MODALITIES = ['SM', 'ECG', 'SR', 'SEG', 'RTSTRUCT']; +const NON_IMAGE_MODALITIES = ['SM', 'ECG', 'SEG', 'RTSTRUCT']; const ohif = { layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', @@ -169,7 +169,7 @@ function modeFactory({ modeConfiguration }) { valid: !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1) .length, description: - 'The mode does not support studies that ONLY include the following modalities: SM, ECG, SR, SEG, RTSTRUCT', + 'The mode does not support studies that ONLY include the following modalities: SM, ECG, SEG, RTSTRUCT', }; }, routes: [ diff --git a/platform/core/src/utils/formatTime.ts b/platform/core/src/utils/formatTime.ts new file mode 100644 index 000000000..5d46ee047 --- /dev/null +++ b/platform/core/src/utils/formatTime.ts @@ -0,0 +1,12 @@ +import moment from 'moment'; + +/** + * Format time in HHmmss.SSS format (24h time) into HH:mm:ss + * + * @param time - Time to be formatted + * @param format - Desired time format + * @returns Formatted time + */ +export default function formatTime(time: string, format = 'HH:mm:ss') { + return moment(time, 'HH:mm:ss').format(format); +} diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index 9ac5e103f..f72714b47 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -24,6 +24,7 @@ describe('Top level exports', () => { 'b64toBlob', 'createStudyBrowserTabs', 'formatDate', + 'formatTime', 'formatPN', 'generateAcceptHeader', 'isEqualWithin', diff --git a/platform/core/src/utils/index.ts b/platform/core/src/utils/index.ts index a22f8a7ef..63518961c 100644 --- a/platform/core/src/utils/index.ts +++ b/platform/core/src/utils/index.ts @@ -13,6 +13,7 @@ import hotkeys from './hotkeys'; import Queue from './Queue'; import isDicomUid from './isDicomUid'; import formatDate from './formatDate'; +import formatTime from './formatTime'; import formatPN from './formatPN'; import generateAcceptHeader from './generateAcceptHeader'; import resolveObjectPath from './resolveObjectPath'; @@ -56,6 +57,7 @@ const utils = { seriesSortCriteria, writeScript, formatDate, + formatTime, formatPN, b64toBlob, urlUtil,