feat(SR): text structured report (TEXT, CODE, NUM, PNAME, DATE, TIME and DATETIME) (#4287)
This commit is contained in:
parent
58da0324cd
commit
246ebab6eb
@ -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 (
|
||||||
|
<Component
|
||||||
|
key={key}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="font-bold">
|
||||||
|
{containerNumberedTree.join('.')}.
|
||||||
|
{CodeMeaning}
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 mb-2">{contentItems}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
@ -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 (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={className}
|
||||||
|
title={CodeMeaning}
|
||||||
|
>
|
||||||
|
{addExtraSpace ? ' ' : ''}
|
||||||
|
{formattedValue}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="font-bold">{CodeMeaning}: </span>
|
||||||
|
{isFinding ? (
|
||||||
|
<pre>{formattedValue}</pre>
|
||||||
|
) : (
|
||||||
|
<span className={className}>{formattedValue}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIFCornerstoneSRContentItem.propTypes = {
|
||||||
|
contentItem: PropTypes.object,
|
||||||
|
nodeIndexesTree: PropTypes.arrayOf(PropTypes.number),
|
||||||
|
continuityOfContent: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { OHIFCornerstoneSRContentItem };
|
||||||
@ -14,7 +14,7 @@ const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'
|
|||||||
|
|
||||||
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
||||||
|
|
||||||
function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
||||||
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
@ -278,14 +278,14 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
|||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateSR = async () => {
|
const updateSR = async () => {
|
||||||
if (!srDisplaySet.isLoaded) {
|
if (!srDisplaySet.isLoaded) {
|
||||||
await srDisplaySet.load();
|
await srDisplaySet.load();
|
||||||
}
|
}
|
||||||
if (!element || !srDisplaySet.isLoaded) {
|
if (!element || !srDisplaySet.isLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTrackingIdentifiers(measurementSelected);
|
setTrackingIdentifiers(measurementSelected);
|
||||||
}
|
};
|
||||||
updateSR();
|
updateSR();
|
||||||
}, [measurementSelected, element, setTrackingIdentifiers, srDisplaySet]);
|
}, [measurementSelected, element, setTrackingIdentifiers, srDisplaySet]);
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OHIFCornerstoneSRViewport.propTypes = {
|
OHIFCornerstoneSRMeasurementViewport.propTypes = {
|
||||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
viewportId: PropTypes.string.isRequired,
|
viewportId: PropTypes.string.isRequired,
|
||||||
dataSource: PropTypes.object,
|
dataSource: PropTypes.object,
|
||||||
@ -508,4 +508,4 @@ function _getStatusComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OHIFCornerstoneSRViewport;
|
export default OHIFCornerstoneSRMeasurementViewport;
|
||||||
@ -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 (
|
||||||
|
<div className="relative flex h-full w-full flex-col overflow-auto p-4 text-white">
|
||||||
|
<div>
|
||||||
|
{/* The root level is always a container */}
|
||||||
|
<OHIFCornerstoneSRContainer container={instance} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@ -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 <OHIFCornerstoneSRMeasurementViewport {...props}></OHIFCornerstoneSRMeasurementViewport>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <OHIFCornerstoneSRTextViewport {...props}></OHIFCornerstoneSRTextViewport>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@ -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 };
|
||||||
@ -3,6 +3,7 @@ import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
|||||||
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
||||||
import isRehydratable from './utils/isRehydratable';
|
import isRehydratable from './utils/isRehydratable';
|
||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
|
import CodeNameCodeSequenceValues from './constants/CodeNameCodeSequenceValues';
|
||||||
|
|
||||||
type InstanceMetadata = Types.InstanceMetadata;
|
type InstanceMetadata = Types.InstanceMetadata;
|
||||||
|
|
||||||
@ -16,10 +17,11 @@ const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
|||||||
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
||||||
|
|
||||||
const sopClassUids = [
|
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.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.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.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.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';
|
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 = {
|
const CodingSchemeDesignators = {
|
||||||
SRT: 'SRT',
|
SRT: 'SRT',
|
||||||
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||||
@ -113,22 +102,10 @@ function _getDisplaySetsFromSeries(
|
|||||||
} = instance;
|
} = instance;
|
||||||
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
||||||
|
|
||||||
if (
|
const isImagingMeasurementReport =
|
||||||
!ConceptNameCodeSequence ||
|
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;
|
||||||
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 displaySet = {
|
const displaySet = {
|
||||||
//plugin: id,
|
|
||||||
Modality: 'SR',
|
Modality: 'SR',
|
||||||
displaySetInstanceUID: utils.guid(),
|
displaySetInstanceUID: utils.guid(),
|
||||||
SeriesDescription,
|
SeriesDescription,
|
||||||
@ -144,6 +121,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
measurements: null,
|
measurements: null,
|
||||||
isDerivedDisplaySet: true,
|
isDerivedDisplaySet: true,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
|
isImagingMeasurementReport,
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
instance,
|
instance,
|
||||||
addInstances,
|
addInstances,
|
||||||
@ -158,7 +136,6 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
|||||||
const { displaySetService, measurementService } = servicesManager.services;
|
const { displaySetService, measurementService } = servicesManager.services;
|
||||||
const dataSources = extensionManager.getDataSources();
|
const dataSources = extensionManager.getDataSources();
|
||||||
const dataSource = dataSources[0];
|
const dataSource = dataSources[0];
|
||||||
|
|
||||||
const { ContentSequence } = displaySet.instance;
|
const { ContentSequence } = displaySet.instance;
|
||||||
|
|
||||||
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
||||||
@ -185,8 +162,13 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
|||||||
await retrieveBulkData(ContentSequence);
|
await retrieveBulkData(ContentSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
if (displaySet.isImagingMeasurementReport) {
|
||||||
displaySet.measurements = _getMeasurements(ContentSequence);
|
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
||||||
|
displaySet.measurements = _getMeasurements(ContentSequence);
|
||||||
|
} else {
|
||||||
|
displaySet.referencedImages = [];
|
||||||
|
displaySet.measurements = [];
|
||||||
|
}
|
||||||
|
|
||||||
const mappings = measurementService.getSourceMappings(
|
const mappings = measurementService.getSourceMappings(
|
||||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||||
@ -371,6 +353,10 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|||||||
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements
|
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!ImagingMeasurements) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(
|
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(
|
||||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup
|
||||||
);
|
);
|
||||||
@ -625,6 +611,10 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|||||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!ImageLibrary) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
||||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
|||||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport');
|
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
||||||
});
|
});
|
||||||
|
|
||||||
const OHIFCornerstoneSRViewport = props => {
|
const OHIFCornerstoneSRViewport = props => {
|
||||||
|
|||||||
@ -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 };
|
||||||
@ -7,7 +7,7 @@ import moreTools from './moreTools';
|
|||||||
|
|
||||||
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
||||||
// Also, SM is not a simple imaging modalities, so exclude it.
|
// 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 = {
|
const ohif = {
|
||||||
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
|
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
|
||||||
@ -169,7 +169,7 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
valid: !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
|
valid: !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1)
|
||||||
.length,
|
.length,
|
||||||
description:
|
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: [
|
routes: [
|
||||||
|
|||||||
12
platform/core/src/utils/formatTime.ts
Normal file
12
platform/core/src/utils/formatTime.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ describe('Top level exports', () => {
|
|||||||
'b64toBlob',
|
'b64toBlob',
|
||||||
'createStudyBrowserTabs',
|
'createStudyBrowserTabs',
|
||||||
'formatDate',
|
'formatDate',
|
||||||
|
'formatTime',
|
||||||
'formatPN',
|
'formatPN',
|
||||||
'generateAcceptHeader',
|
'generateAcceptHeader',
|
||||||
'isEqualWithin',
|
'isEqualWithin',
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import hotkeys from './hotkeys';
|
|||||||
import Queue from './Queue';
|
import Queue from './Queue';
|
||||||
import isDicomUid from './isDicomUid';
|
import isDicomUid from './isDicomUid';
|
||||||
import formatDate from './formatDate';
|
import formatDate from './formatDate';
|
||||||
|
import formatTime from './formatTime';
|
||||||
import formatPN from './formatPN';
|
import formatPN from './formatPN';
|
||||||
import generateAcceptHeader from './generateAcceptHeader';
|
import generateAcceptHeader from './generateAcceptHeader';
|
||||||
import resolveObjectPath from './resolveObjectPath';
|
import resolveObjectPath from './resolveObjectPath';
|
||||||
@ -56,6 +57,7 @@ const utils = {
|
|||||||
seriesSortCriteria,
|
seriesSortCriteria,
|
||||||
writeScript,
|
writeScript,
|
||||||
formatDate,
|
formatDate,
|
||||||
|
formatTime,
|
||||||
formatPN,
|
formatPN,
|
||||||
b64toBlob,
|
b64toBlob,
|
||||||
urlUtil,
|
urlUtil,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user