diff --git a/docs/latest/extensions/modules/sop-class-handler.md b/docs/latest/extensions/modules/sop-class-handler.md index cdef59033..84e743b72 100644 --- a/docs/latest/extensions/modules/sop-class-handler.md +++ b/docs/latest/extensions/modules/sop-class-handler.md @@ -5,7 +5,7 @@ An extension can register a SOP Class Handler Module by defining a the other modules, as it doesn't provide a `1:1` schema for UI or provide it's own components. It instead defines: -- `sopClassUids`: an array of string SOP Class UIDs that the +- `sopClassUIDs`: an array of string SOP Class UIDs that the `getDisplaySetFromSeries` method should be applied to. - `getDisplaySetFromSeries`: a method that maps series and study metadata to a display set @@ -15,13 +15,13 @@ A `displaySet` has the following shape: ```js return { plugin: 'html', - modality: 'SR', - displaySetInstanceUid: 0, + Modality: 'SR', + displaySetInstanceUID: 0, wadoRoot: study.getData().wadoRoot, wadoUri: instance.getData().wadouri, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), authorizationHeaders, }; ``` @@ -50,7 +50,7 @@ export default { getSopClassHandlerModule({ servicesManager, commandsManager }) { return { id: 'OHIFDicomHtmlSopClassHandler', - sopClassUids: Object.values(SOP_CLASS_UIDS), + sopClassUIDs: Object.values(SOP_CLASS_UIDS), /** * @param {object} series - @@ -63,12 +63,12 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) { return { plugin: 'html', - displaySetInstanceUid: 0, + displaySetInstanceUID: 0, wadoRoot: study.getData().wadoRoot, wadoUri: instance.getData().wadouri, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), authorizationHeaders, }; }, diff --git a/docs/v1/lesion-tracker/audit-trail.md b/docs/v1/lesion-tracker/audit-trail.md index fcb34e00b..0e5ec3cb2 100644 --- a/docs/v1/lesion-tracker/audit-trail.md +++ b/docs/v1/lesion-tracker/audit-trail.md @@ -1,6 +1,7 @@ # Lesion Tracker - Audit Trail 1. To view Audit Trails, select **View Audit Log** on the Configuration menu. -2. To filter audit logs for a column or multiple columns, type or select in the related column or columns. +2. To filter audit logs for a column or multiple columns, type or select in the + related column or columns. ![Audit Trails](../assets/img/LesionTracker/LT_Audit_Trails.png) diff --git a/docs/v1/lesion-tracker/study-and-timepoint-management.md b/docs/v1/lesion-tracker/study-and-timepoint-management.md index d9b293487..1130fef98 100644 --- a/docs/v1/lesion-tracker/study-and-timepoint-management.md +++ b/docs/v1/lesion-tracker/study-and-timepoint-management.md @@ -2,50 +2,74 @@ ![Study List](../assets/img/LesionTracker/LT_StudyList.png) -1. Study List allows to sort each column. To sort studies, click the desired column header. -2. Study List is filterable for each column and you can filter by more than one column by typing in the column field. To filter a column, type the text in the desired column header field and press Enter. -3. Pagination is provided for the Study List at the bottom of the page. You can change the number of rows per page or the current page. +1. Study List allows to sort each column. To sort studies, click the desired + column header. +2. Study List is filterable for each column and you can filter by more than one + column by typing in the column field. To filter a column, type the text in + the desired column header field and press Enter. +3. Pagination is provided for the Study List at the bottom of the page. You can + change the number of rows per page or the current page. ## Study List Context Menu -When you right-click on the study row, Study Context Menu will pop up. Study Context Menu includes operations at the study level. +When you right-click on the study row, Study Context Menu will pop up. Study +Context Menu includes operations at the study level. There are a couple of ways to view a study: -* Simply double-click on the study row. -* Select **View** option on the Study Context Menu, then you will be redirected to Viewer page. +- Simply double-click on the study row. +- Select **View** option on the Study Context Menu, then you will be redirected + to Viewer page. ![View Study](../assets/img/LesionTracker/LT_View_Study.png) ### Timepoint Association -1. To link a study with a timepoint, right-click on the study row and select **Associate** option on the Study Context Menu. You will see Study Association dialog. -2. To link multiple studies at the same time, hold Shift key and select studies you would like to associate, then right-click and select Associate on the Study Context Menu +1. To link a study with a timepoint, right-click on the study row and select + **Associate** option on the Study Context Menu. You will see Study + Association dialog. +2. To link multiple studies at the same time, hold Shift key and select studies + you would like to associate, then right-click and select Associate on the + Study Context Menu ![Select Associate](../assets/img/LesionTracker/LT_Select_Associate.png) -The Study Association dialog allows you to select and unselect Timepoint Type for the selected study or studies. Also, it will include all studies within 14 days of your selected studies, in case you forgot to select a study. After you identify timepoints for the selected study or studies, click **Save** to save the association. +The Study Association dialog allows you to select and unselect Timepoint Type +for the selected study or studies. Also, it will include all studies within 14 +days of your selected studies, in case you forgot to select a study. After you +identify timepoints for the selected study or studies, click **Save** to save +the association. ![Association Dialog](../assets/img/LesionTracker/LT_Association_Dialog.png) ### Remove Timepoint Association -To unlink a timepoint association with a study, right-click on the study row and select **Remove Association** option on the Study Context Menu. Remove Association will be disabled if the study is already dissociated. + +To unlink a timepoint association with a study, right-click on the study row and +select **Remove Association** option on the Study Context Menu. Remove +Association will be disabled if the study is already dissociated. ![Remove Associate](../assets/img/LesionTracker/LT_Remove_Associate.png) ### View Series Details -To view details of related series, right-click on the study row and select **View Series Details** option on the Study Context Menu, then Series Details dialog will pop up. Series Details dialog gives a summary of the selected study. + +To view details of related series, right-click on the study row and select +**View Series Details** option on the Study Context Menu, then Series Details +dialog will pop up. Series Details dialog gives a summary of the selected study. ![View Series Details](../assets/img/LesionTracker/LT_View_Series_Details.png) ### Anonymize (Under Development) + ... ### Send (Under Development) + ... ### Export (Under Development) + ... ### Delete (Under Development) + ... diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js index de0d09128..057e1bfde 100644 --- a/extensions/_example/src/index.js +++ b/extensions/_example/src/index.js @@ -63,7 +63,7 @@ const commandsModule = { */ const sopClassHandlerModule = { id: 'OHIFDicomHtmlSopClassHandler', - sopClassUids: Object.values({ + sopClassUIDs: Object.values({ BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11', ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22', COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33', @@ -77,12 +77,12 @@ const sopClassHandlerModule = { return { plugin: 'html', - displaySetInstanceUid: 0, //utils.guid(), + displaySetInstanceUID: 0, //utils.guid(), wadoRoot: study.getData().wadoRoot, wadoUri: instance.getData().wadouri, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), authorizationHeaders, }; }, diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 3689110c9..184e6e544 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -34,9 +34,9 @@ "@ohif/ui": "^0.50.0", "cornerstone-core": "^2.2.8", "cornerstone-math": "^0.1.8", - "cornerstone-tools": "^4.12.0", + "cornerstone-tools": "4.12.0", "cornerstone-wado-image-loader": "^3.0.0", - "dcmjs": "^0.8.2", + "dcmjs": "^0.10.1", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", @@ -51,7 +51,6 @@ "classnames": "^2.2.6", "lodash.merge": "^4.6.2", "lodash.throttle": "^4.1.1", - "query-string": "^6.8.3", "react-cornerstone-viewport": "^2.3.6" } } diff --git a/extensions/cornerstone/src/OHIFCornerstoneViewport.js b/extensions/cornerstone/src/OHIFCornerstoneViewport.js index 23e684b76..6a45f4d7f 100644 --- a/extensions/cornerstone/src/OHIFCornerstoneViewport.js +++ b/extensions/cornerstone/src/OHIFCornerstoneViewport.js @@ -8,15 +8,6 @@ import handleSegmentationStorage from './handleSegmentationStorage.js'; const { StackManager } = OHIF.utils; -// Metadata configuration -const metadataProvider = new OHIF.cornerstone.MetadataProvider(); - -cornerstone.metaData.addProvider( - metadataProvider.provider.bind(metadataProvider) -); - -StackManager.setMetadataProvider(metadataProvider); - const SOP_CLASSES = { SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4', }; @@ -58,34 +49,34 @@ class OHIFCornerstoneViewport extends Component { * Obtain the CornerstoneTools Stack for the specified display set. * * @param {Object[]} studies - * @param {String} studyInstanceUid - * @param {String} displaySetInstanceUid - * @param {String} [sopInstanceUid] + * @param {String} StudyInstanceUID + * @param {String} displaySetInstanceUID + * @param {String} [SOPInstanceUID] * @param {Number} [frameIndex=1] * @return {Object} CornerstoneTools Stack */ static getCornerstoneStack( studies, - studyInstanceUid, - displaySetInstanceUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPInstanceUID, frameIndex = 0 ) { if (!studies || !studies.length) { throw new Error('Studies not provided.'); } - if (!studyInstanceUid) { + if (!StudyInstanceUID) { throw new Error('StudyInstanceUID not provided.'); } - if (!displaySetInstanceUid) { + if (!displaySetInstanceUID) { throw new Error('StudyInstanceUID not provided.'); } // Create shortcut to displaySet const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); if (!study) { @@ -93,7 +84,7 @@ class OHIFCornerstoneViewport extends Component { } const displaySet = study.displaySets.find(set => { - return set.displaySetInstanceUid === displaySetInstanceUid; + return set.displaySetInstanceUID === displaySetInstanceUID; }); if (!displaySet) { @@ -107,17 +98,14 @@ class OHIFCornerstoneViewport extends Component { const stack = Object.assign({}, storedStack); stack.currentImageIdIndex = frameIndex; - if (sopInstanceUid) { + if (SOPInstanceUID) { const index = stack.imageIds.findIndex(imageId => { - const sopCommonModule = cornerstone.metaData.get( - 'sopCommonModule', + const imageIdSOPInstanceUID = cornerstone.metaData.get( + 'SOPInstanceUID', imageId ); - if (!sopCommonModule) { - return; - } - return sopCommonModule.sopInstanceUID === sopInstanceUid; + return imageIdSOPInstanceUID === SOPInstanceUID; }); if (index > -1) { @@ -134,39 +122,39 @@ class OHIFCornerstoneViewport extends Component { getViewportData = async ( studies, - studyInstanceUid, - displaySetInstanceUid, - sopClassUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPClassUID, + SOPInstanceUID, frameIndex ) => { let viewportData; - switch (sopClassUid) { + switch (SOPClassUID) { case SOP_CLASSES.SEGMENTATION_STORAGE: const specialCaseHandler = specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE]; viewportData = await specialCaseHandler( studies, - studyInstanceUid, - displaySetInstanceUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPInstanceUID, frameIndex ); break; default: const stack = OHIFCornerstoneViewport.getCornerstoneStack( studies, - studyInstanceUid, - displaySetInstanceUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPInstanceUID, frameIndex ); viewportData = { - studyInstanceUid, - displaySetInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, stack, }; @@ -179,31 +167,31 @@ class OHIFCornerstoneViewport extends Component { setStateFromProps() { const { studies, displaySet } = this.props.viewportData; const { - studyInstanceUid, - displaySetInstanceUid, - sopClassUids, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + sopClassUIDs, + SOPInstanceUID, frameIndex, } = displaySet; - if (!studyInstanceUid || !displaySetInstanceUid) { + if (!StudyInstanceUID || !displaySetInstanceUID) { return; } - if (sopClassUids && sopClassUids.length > 1) { + if (sopClassUIDs && sopClassUIDs.length > 1) { console.warn( - 'More than one SOPClassUid in the same series is not yet supported.' + 'More than one SOPClassUID in the same series is not yet supported.' ); } - const sopClassUid = sopClassUids && sopClassUids[0]; + const SOPClassUID = sopClassUIDs && sopClassUIDs[0]; this.getViewportData( studies, - studyInstanceUid, - displaySetInstanceUid, - sopClassUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPClassUID, + SOPInstanceUID, frameIndex ).then(viewportData => { this.setState({ @@ -221,9 +209,9 @@ class OHIFCornerstoneViewport extends Component { const prevDisplaySet = prevProps.viewportData.displaySet; if ( - displaySet.displaySetInstanceUid !== - prevDisplaySet.displaySetInstanceUid || - displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid || + displaySet.displaySetInstanceUID !== + prevDisplaySet.displaySetInstanceUID || + displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.frameIndex !== prevDisplaySet.frameIndex ) { this.setStateFromProps(); diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 35947091e..514d04d28 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -247,6 +247,7 @@ const commandsModule = ({ servicesManager }) => { if (enabledElement) { let viewport = cornerstone.getViewport(enabledElement); + viewport.voi = { windowWidth: Number(window), windowCenter: Number(level), diff --git a/extensions/cornerstone/src/handleSegmentationStorage.js b/extensions/cornerstone/src/handleSegmentationStorage.js index 64ac60cfd..799204982 100644 --- a/extensions/cornerstone/src/handleSegmentationStorage.js +++ b/extensions/cornerstone/src/handleSegmentationStorage.js @@ -6,25 +6,25 @@ import cornerstoneTools from 'cornerstone-tools'; const { StackManager, DicomLoaderService } = OHIF.utils; -function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) { +function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); const displaySet = study.displaySets.find(set => { - return set.displaySetInstanceUid === displaySetInstanceUid; + return set.displaySetInstanceUID === displaySetInstanceUID; }); return displaySet; } -function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) { +function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); return study.displaySets.filter(set => { - return set.seriesInstanceUid === seriesInstanceUid; + return set.SeriesInstanceUID === SeriesInstanceUID; }); } @@ -39,7 +39,7 @@ function parseSeg(arrayBuffer, imageIds) { function addSegMetadataToCornerstoneToolState( segMetadata, toolState, - displaySetInstanceUid + displaySetInstanceUID ) { cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState( toolState @@ -48,22 +48,22 @@ function addSegMetadataToCornerstoneToolState( const brushModule = cornerstoneTools.store.modules.brush; for (let i = 0; i < segMetadata.length; i++) { - brushModule.setters.metadata(displaySetInstanceUid, i, segMetadata[i]); + brushModule.setters.metadata(displaySetInstanceUID, i, segMetadata[i]); } } async function handleSegmentationStorage( studies, - studyInstanceUid, - displaySetInstanceUid + StudyInstanceUID, + displaySetInstanceUID ) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); const displaySet = getDisplaySet( studies, - studyInstanceUid, - displaySetInstanceUid + StudyInstanceUID, + displaySetInstanceUID ); const arrayBuffer = await DicomLoaderService.findDicomDataPromise( @@ -77,16 +77,16 @@ async function handleSegmentationStorage( dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta); - const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID; + const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID; const displaySets = getDisplaySetsBySeries( studies, - studyInstanceUid, - seriesInstanceUid + StudyInstanceUID, + SeriesInstanceUID ); if (displaySets.length > 1) { console.warn( - 'More than one display set with the same seriesInstanceUid. This is not supported yet...' + 'More than one display set with the same SeriesInstanceUID. This is not supported yet...' ); } @@ -119,8 +119,8 @@ async function handleSegmentationStorage( stack.currentImageIdIndex = 0; return { - studyInstanceUid, - displaySetInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, stack, }; } diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index 4f35f8b35..3389840e9 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -3,29 +3,9 @@ import { SimpleDialog } from '@ohif/ui'; import cornerstone from 'cornerstone-core'; import csTools from 'cornerstone-tools'; import merge from 'lodash.merge'; -import queryString from 'query-string'; import initCornerstoneTools from './initCornerstoneTools.js'; import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; -function fallbackMetaDataProvider(type, imageId) { - if (!imageId.includes('wado?requestType=WADO')) { - return; - } - - // If you call for an WADO-URI imageId and get no - // metadata, try reformatting to WADO-RS imageId - const qs = queryString.parse(imageId); - const wadoRoot = window.store.getState().servers.servers[0].wadoRoot; - const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${ - qs.seriesUID - }/instances/${qs.objectUID}/frames/${qs.frame || 1}`; - - return cornerstone.metaData.get(type, wadoRsImageId); -} - -// Add this fallback provider with a low priority so it is handled last -cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1); - /** * * @param {Object} servicesManager @@ -58,16 +38,13 @@ export default function init({ servicesManager, configuration }) { }; const { csToolsConfig } = configuration; - const { StackManager } = OHIF.utils; - const metadataProvider = new OHIF.cornerstone.MetadataProvider(); + const metadataProvider = OHIF.cornerstone.metadataProvider; - // ~~ Set our MetadataProvider cornerstone.metaData.addProvider( - metadataProvider.provider.bind(metadataProvider) + metadataProvider.get.bind(metadataProvider), + 9999 ); - StackManager.setMetadataProvider(metadataProvider); - // ~~ const defaultCsToolsConfig = csToolsConfig || { globalToolSyncEnabled: true, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js index 46a409e23..f2715eb39 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -1,6 +1,11 @@ import cornerstone from 'cornerstone-core'; -const SUPPORTED_TOOLS = ['Length', 'EllipticalRoi', 'RectangleRoi', 'ArrowAnnotate']; +const SUPPORTED_TOOLS = [ + 'Length', + 'EllipticalRoi', + 'RectangleRoi', + 'ArrowAnnotate', +]; const measurementServiceMappingsFactory = measurementService => { /** @@ -17,17 +22,17 @@ const measurementServiceMappingsFactory = measurementService => { description, points, unit, - sopInstanceUID, - frameOfReferenceUID, + SOPInstanceUID, + FrameOfReferenceUID, referenceSeriesUID, } = measurement; return { toolName: definition, measurementData: { - sopInstanceUid: sopInstanceUID, - frameOfReferenceUid: frameOfReferenceUID, - seriesInstanceUid: referenceSeriesUID, + sopInstanceUid: SOPInstanceUID, + frameOfReferenceUID: FrameOfReferenceUID, + SeriesInstanceUID: referenceSeriesUID, unit, text: label, description, @@ -57,9 +62,9 @@ const measurementServiceMappingsFactory = measurementService => { } const { - sopInstanceUid, - frameOfReferenceUid, - seriesInstanceUid, + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, } = _getAttributes(element); const points = []; @@ -67,13 +72,16 @@ const measurementServiceMappingsFactory = measurementService => { return { id: measurementData._measurementServiceId, - sopInstanceUID: sopInstanceUid, - frameOfReferenceUID: frameOfReferenceUid, - referenceSeriesUID: seriesInstanceUid, + SOPInstanceUID: SOPInstanceUID, + FrameOfReferenceUID, + referenceSeriesUID: SeriesInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, - area: measurementData.cachedStats && measurementData.cachedStats.area, /* TODO: Add concept names instead (descriptor) */ + area: + measurementData.cachedStats && + measurementData.cachedStats + .area /* TODO: Add concept names instead (descriptor) */, type: _getValueTypeFromToolType(tool), points: _getPointsFromHandles(measurementData.handles), }; @@ -82,13 +90,13 @@ const measurementServiceMappingsFactory = measurementService => { const _getAttributes = element => { const enabledElement = cornerstone.getEnabledElement(element); const imageId = enabledElement.image.imageId; - const sopInstance = cornerstone.metaData.get('instance', imageId); - const sopInstanceUid = sopInstance.sopInstanceUid; - const frameOfReferenceUid = sopInstance.frameOfReferenceUID; - const series = cornerstone.metaData.get('series', imageId); - const seriesInstanceUid = series.seriesInstanceUid; + const instance = cornerstone.metaData.get('instance', imageId); - return { sopInstanceUid, frameOfReferenceUid, seriesInstanceUid }; + return { + SOPInstanceUID: instance.SOPInstanceUID, + FrameOfReferenceUID: instance.FrameOfReferenceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + }; }; const _getValueTypeFromToolType = toolType => { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js index f17a43281..70d0d7e66 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js @@ -8,9 +8,9 @@ jest.mock('cornerstone-core', () => ({ metaData: { ...jest.requireActual('cornerstone-core').metaData, get: () => ({ - sopInstanceUid: "123", - frameOfReferenceUID: "123", - seriesInstanceUid: "123", + SOPInstanceUID: '123', + FrameOfReferenceUID: '123', + SeriesInstanceUID: '123', }), }, })); @@ -42,8 +42,8 @@ describe('measurementServiceMappings.js', () => { measurementData: { _measurementServiceId: 1, sopInstanceUid: '123', - frameOfReferenceUid: '123', - seriesInstanceUid: '123', + frameOfReferenceUID: '123', + SeriesInstanceUID: '123', handles, text: 'Test', description: 'Test', @@ -52,8 +52,8 @@ describe('measurementServiceMappings.js', () => { }; measurement = { id: 1, - sopInstanceUID: '123', - frameOfReferenceUID: '123', + SOPInstanceUID: '123', + FrameOfReferenceUID: '123', referenceSeriesUID: '123', label: 'Test', description: 'Test', @@ -66,7 +66,10 @@ describe('measurementServiceMappings.js', () => { describe('toAnnotation()', () => { it('map measurement service format to annotation', async () => { - const mappedMeasurement = await mappings.toAnnotation(measurement, definition); + const mappedMeasurement = await mappings.toAnnotation( + measurement, + definition + ); expect(mappedMeasurement).toEqual(csToolsAnnotation); }); }); diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index 85f6f8e06..d12c05bb7 100644 --- a/extensions/dicom-html/package.json +++ b/extensions/dicom-html/package.json @@ -29,7 +29,7 @@ }, "peerDependencies": { "@ohif/core": "^0.50.0", - "dcmjs": "^0.8.2", + "dcmjs": "^0.10.1", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js index 72ae81224..cf3ecccf5 100644 --- a/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js +++ b/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js @@ -11,26 +11,26 @@ const SOP_CLASS_UIDS = { X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67', }; -const sopClassUids = Object.values(SOP_CLASS_UIDS); +const sopClassUIDs = Object.values(SOP_CLASS_UIDS); // TODO: Handle the case where there is more than one SOP Class Handler for the // same SOP Class const OHIFDicomHtmlSopClassHandler = { id: 'OHIFDicomHtmlSopClassHandler', type: MODULE_TYPES.SOP_CLASS_HANDLER, - sopClassUids, + sopClassUIDs, getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); return { plugin: 'html', - modality: 'SR', - displaySetInstanceUid: utils.guid(), + Modality: 'SR', + displaySetInstanceUID: utils.guid(), wadoRoot: study.getData().wadoRoot, wadoUri: instance.getData().wadouri, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), authorizationHeaders, }; }, diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js index ae116d96d..cef4e49d1 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js +++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js @@ -8,7 +8,7 @@ const SOP_CLASS_UIDS = { const DicomMicroscopySopClassHandler = { id: 'DicomMicroscopySopClassHandlerPlugin', - sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE], + sopClassUIDs: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE], getDisplaySetFromSeries(series, study, dicomWebClient) { const instance = series.getFirstInstance(); @@ -16,12 +16,12 @@ const DicomMicroscopySopClassHandler = { return { plugin: 'microscopy', - modality: 'SM', - displaySetInstanceUid: utils.guid(), + Modality: 'SM', + displaySetInstanceUID: utils.guid(), dicomWebClient, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), }; }, }; diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.js b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.js index 0e534c339..b6200cfb8 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.js +++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.js @@ -25,8 +25,8 @@ class DicomMicroscopyViewport extends Component { const dicomWebClient = displaySet.dicomWebClient; const searchInstanceOptions = { - studyInstanceUID: displaySet.studyInstanceUid, - seriesInstanceUID: displaySet.seriesInstanceUid, + studyInstanceUID: displaySet.StudyInstanceUID, + seriesInstanceUID: displaySet.SeriesInstanceUID, }; dicomWebClient @@ -34,18 +34,18 @@ class DicomMicroscopyViewport extends Component { .then(instances => { const promises = []; for (let i = 0; i < instances.length; i++) { - const sopInstanceUID = instances[i]['00080018']['Value'][0]; + const sopInstanceUid = instances[i]['00080018']['Value'][0]; const retrieveInstanceOptions = { - studyInstanceUID: displaySet.studyInstanceUid, - seriesInstanceUID: displaySet.seriesInstanceUid, - sopInstanceUID, + studyInstanceUID: displaySet.StudyInstanceUID, + seriesInstanceUID: displaySet.SeriesInstanceUID, + sopInstanceUid, }; const promise = dicomWebClient .retrieveInstanceMetadata(retrieveInstanceOptions) .then(metadata => { - const imageType = metadata[0]['00080008']['Value']; - if (imageType[2] === 'VOLUME') { + const ImageType = metadata[0]['00080008']['Value']; + if (ImageType[2] === 'VOLUME') { return metadata[0]; } }); @@ -56,13 +56,15 @@ class DicomMicroscopyViewport extends Component { .then(async metadata => { metadata = metadata.filter(m => m); - const { api } = await import(/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'); + const { api } = await import( + /* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer' + ); const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer; this.viewer = new microscopyViewer({ client: dicomWebClient, metadata, - retrieveRendered: false + retrieveRendered: false, }); this.viewer.render({ container }); diff --git a/extensions/dicom-pdf/src/DicomPDFViewport.js b/extensions/dicom-pdf/src/DicomPDFViewport.js index 0fdcde3d4..8df900a66 100644 --- a/extensions/dicom-pdf/src/DicomPDFViewport.js +++ b/extensions/dicom-pdf/src/DicomPDFViewport.js @@ -101,9 +101,9 @@ class DicomPDFViewport extends Component { let pdfByteArray = byteArray; if (dataSet) { - const sopClassUid = dataSet.string('x00080016'); + const SOPClassUID = dataSet.string('x00080016'); - if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) { + if (SOPClassUID !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) { throw new Error('This is not a DICOM-encapsulated PDF'); } diff --git a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js index 63a020e0b..c07f1c39c 100644 --- a/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js +++ b/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js @@ -8,19 +8,19 @@ const SOP_CLASS_UIDS = { const OHIFDicomPDFSopClassHandler = { id: 'OHIFDicomPDFSopClassHandlerPlugin', type: MODULE_TYPES.SOP_CLASS_HANDLER, - sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF], + sopClassUIDs: [SOP_CLASS_UIDS.ENCAPSULATED_PDF], getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { const instance = series.getFirstInstance(); return { plugin: 'pdf', - modality: 'DOC', - displaySetInstanceUid: utils.guid(), + Modality: 'DOC', + displaySetInstanceUID: utils.guid(), wadoRoot: study.getData().wadoRoot, wadoUri: instance.getData().wadouri, - sopInstanceUid: instance.getSOPInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), + SOPInstanceUID: instance.getSOPInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), authorizationHeaders: authorizationHeaders, }; }, diff --git a/extensions/dicom-pdf/src/OHIFDicomPDFViewport.js b/extensions/dicom-pdf/src/OHIFDicomPDFViewport.js index 88cca1ed2..70fd5a37d 100644 --- a/extensions/dicom-pdf/src/OHIFDicomPDFViewport.js +++ b/extensions/dicom-pdf/src/OHIFDicomPDFViewport.js @@ -33,6 +33,7 @@ class OHIFDicomPDFViewport extends Component { componentDidMount() { const { displaySet, studies } = this.props.viewportData; + DicomLoaderService.findDicomDataPromise(displaySet, studies).then( data => this.setState({ byteArray: new Uint8Array(data) }), error => { diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index b58d9fc0a..617de8bad 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -34,7 +34,7 @@ "@ohif/ui": "^0.50.0", "cornerstone-core": "^2.2.8", "cornerstone-wado-image-loader": "^3.0.0", - "dcmjs": "^0.8.2", + "dcmjs": "^0.10.1", "dicom-parser": "^1.8.3", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", @@ -54,7 +54,7 @@ "devDependencies": { "@ohif/core": "^2.5.2", "@ohif/ui": "^1.3.2", - "cornerstone-tools": "^4.12.0", + "cornerstone-tools": "4.12.0", "cornerstone-wado-image-loader": "^3.0.0", "dicom-parser": "^1.8.3", "gh-pages": "^2.0.1", diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js index 379c87af3..6e90e0ac5 100644 --- a/extensions/vtk/src/OHIFVTKViewport.js +++ b/extensions/vtk/src/OHIFVTKViewport.js @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { getImageData, loadImageData } from 'react-vtkjs-viewport'; - import ConnectedVTKViewport from './ConnectedVTKViewport'; import LoadingIndicator from './LoadingIndicator.js'; import OHIF from '@ohif/core'; @@ -13,15 +12,7 @@ import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume'; import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper'; const { StackManager } = OHIF.utils; - -// Metadata configuration -const metadataProvider = new OHIF.cornerstone.MetadataProvider(); - -cornerstone.metaData.addProvider( - metadataProvider.provider.bind(metadataProvider) -); - -StackManager.setMetadataProvider(metadataProvider); +const style = { width: '100%', height: '100%', position: 'relative' }; const SOP_CLASSES = { SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4', @@ -68,10 +59,10 @@ class OHIFVTKViewport extends Component { viewportData: PropTypes.shape({ studies: PropTypes.array, displaySet: PropTypes.shape({ - studyInstanceUid: PropTypes.string, - displaySetInstanceUid: PropTypes.string, - sopClassUids: PropTypes.arrayOf(PropTypes.string), - sopInstanceUid: PropTypes.string, + StudyInstanceUID: PropTypes.string, + displaySetInstanceUID: PropTypes.string, + sopClassUIDs: PropTypes.arrayOf(PropTypes.string), + SOPInstanceUID: PropTypes.string, frameIndex: PropTypes.number, }), }), @@ -97,18 +88,18 @@ class OHIFVTKViewport extends Component { static getCornerstoneStack( studies, - studyInstanceUid, - displaySetInstanceUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPInstanceUID, frameIndex ) { // Create shortcut to displaySet const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); const displaySet = study.displaySets.find(set => { - return set.displaySetInstanceUid === displaySetInstanceUid; + return set.displaySetInstanceUID === displaySetInstanceUID; }); // Get stack from Stack Manager @@ -119,17 +110,14 @@ class OHIFVTKViewport extends Component { if (frameIndex !== undefined) { stack.currentImageIdIndex = frameIndex; - } else if (sopInstanceUid) { + } else if (SOPInstanceUID) { const index = stack.imageIds.findIndex(imageId => { - const sopCommonModule = cornerstone.metaData.get( - 'sopCommonModule', + const imageIdSOPInstanceUID = cornerstone.metaData.get( + 'SOPInstanceUID', imageId ); - if (!sopCommonModule) { - return; - } - return sopCommonModule.sopInstanceUID === sopInstanceUid; + return imageIdSOPInstanceUID === SOPInstanceUID; }); if (index > -1) { @@ -144,31 +132,31 @@ class OHIFVTKViewport extends Component { getViewportData = ( studies, - studyInstanceUid, - displaySetInstanceUid, - sopClassUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPClassUID, + SOPInstanceUID, frameIndex ) => { const stack = OHIFVTKViewport.getCornerstoneStack( studies, - studyInstanceUid, - displaySetInstanceUid, - sopClassUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPClassUID, + SOPInstanceUID, frameIndex ); let imageDataObject; let labelmapDataObject; - switch (sopClassUid) { + switch (SOPClassUID) { case SOP_CLASSES.SEGMENTATION_STORAGE: throw new Error('Not yet implemented'); /* const data = handleSegmentationStorage( stack.imageIds, - displaySetInstanceUid + displaySetInstanceUID ); imageDataObject = data.referenceDataObject; @@ -182,7 +170,7 @@ class OHIFVTKViewport extends Component { }); */ default: - imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid); + imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID); return imageDataObject; } @@ -194,25 +182,31 @@ class OHIFVTKViewport extends Component { * @param {object} imageDataObject * @param {object} imageDataObject.vtkImageData * @param {object} imageDataObject.imageMetaData0 - * @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth - * @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter - * @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc - * @param {string} displaySetInstanceUid + * @param {number} [imageDataObject.imageMetaData0.WindowWidth] - The volume's initial WindowWidth + * @param {number} [imageDataObject.imageMetaData0.WindowCenter] - The volume's initial WindowCenter + * @param {string} imageDataObject.imageMetaData0.Modality - CT, MR, PT, etc + * @param {string} displaySetInstanceUID * @returns vtkVolumeActor * @memberof OHIFVTKViewport */ - getOrCreateVolume(imageDataObject, displaySetInstanceUid) { - if (volumeCache[displaySetInstanceUid]) { - return volumeCache[displaySetInstanceUid]; + getOrCreateVolume(imageDataObject, displaySetInstanceUID) { + if (volumeCache[displaySetInstanceUID]) { + return volumeCache[displaySetInstanceUID]; } const { vtkImageData, imageMetaData0 } = imageDataObject; - const { windowWidth, windowCenter, modality } = imageMetaData0; + // TODO -> Should update react-vtkjs-viewport and react-cornerstone-viewports + // internals to use naturalized DICOM JSON names. + const { + windowWidth: WindowWidth, + windowCenter: WindowCenter, + modality: Modality, + } = imageMetaData0; const { lower, upper } = _getRangeFromWindowLevels( - windowWidth, - windowCenter, - modality + WindowWidth, + WindowCenter, + Modality ); const volumeActor = vtkVolume.newInstance(); const volumeMapper = vtkVolumeMapper.newInstance(); @@ -236,7 +230,7 @@ class OHIFVTKViewport extends Component { // TODO: maybe we should auto adjust samples to 1000. volumeMapper.setMaximumSamplesPerRay(4000); - volumeCache[displaySetInstanceUid] = volumeActor; + volumeCache[displaySetInstanceUID] = volumeActor; return volumeActor; } @@ -244,26 +238,26 @@ class OHIFVTKViewport extends Component { setStateFromProps() { const { studies, displaySet } = this.props.viewportData; const { - studyInstanceUid, - displaySetInstanceUid, - sopClassUids, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + sopClassUIDs, + SOPInstanceUID, frameIndex, } = displaySet; - if (sopClassUids.length > 1) { + if (sopClassUIDs.length > 1) { console.warn( - 'More than one SOPClassUid in the same series is not yet supported.' + 'More than one SOPClassUID in the same series is not yet supported.' ); } - const sopClassUid = sopClassUids[0]; + const SOPClassUID = sopClassUIDs[0]; const imageDataObject = this.getViewportData( studies, - studyInstanceUid, - displaySetInstanceUid, - sopClassUid, - sopInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, + SOPClassUID, + SOPInstanceUID, frameIndex ); @@ -277,7 +271,7 @@ class OHIFVTKViewport extends Component { const volumeActor = this.getOrCreateVolume( imageDataObject, - displaySetInstanceUid + displaySetInstanceUID ); this.setState({ @@ -317,9 +311,9 @@ class OHIFVTKViewport extends Component { const prevDisplaySet = prevProps.viewportData.displaySet; if ( - displaySet.displaySetInstanceUid !== - prevDisplaySet.displaySetInstanceUid || - displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid || + displaySet.displaySetInstanceUID !== + prevDisplaySet.displaySetInstanceUID || + displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.frameIndex !== prevDisplaySet.frameIndex ) { this.setStateFromProps(); @@ -331,7 +325,7 @@ class OHIFVTKViewport extends Component { const { isLoading, insertPixelDataPromises } = imageDataObject; - const numberOfFrames = insertPixelDataPromises.length; + const NumberOfFrames = insertPixelDataPromises.length; if (!isLoading) { this.setState({ isLoaded: true }); @@ -341,7 +335,7 @@ class OHIFVTKViewport extends Component { insertPixelDataPromises.forEach(promise => { promise.then(numberProcessed => { const percentComplete = Math.floor( - (numberProcessed * 100) / numberOfFrames + (numberProcessed * 100) / NumberOfFrames ); if (percentComplete !== this.state.percentComplete) { @@ -373,8 +367,6 @@ class OHIFVTKViewport extends Component { }); } - const style = { width: '100%', height: '100%', position: 'relative' }; - return ( <>
@@ -409,21 +401,21 @@ class OHIFVTKViewport extends Component { * @private * @param {number} [width] - the width of our window * @param {number} [center] - the center of our window - * @param {string} [modality] - 'PT', 'CT', etc. + * @param {string} [Modality] - 'PT', 'CT', etc. * @returns { lower, upper } - range */ -function _getRangeFromWindowLevels(width, center, modality = undefined) { +function _getRangeFromWindowLevels(width, center, Modality = undefined) { + // For PET just set the range to 0-5 SUV + if (Modality === 'PT') { + return { lower: 0, upper: 5 }; + } + const levelsAreNotNumbers = isNaN(center) || isNaN(width); if (levelsAreNotNumbers) { return { lower: 0, upper: 512 }; } - // For PET just set the range to 0-5 SUV - if (modality === 'PT') { - return { lower: 0, upper: 5 }; - } - return { lower: center - width / 2.0, upper: center + width / 2.0, diff --git a/extensions/vtk/src/VTKViewport.js b/extensions/vtk/src/VTKViewport.js index fa77c262c..26e1a6ed4 100644 --- a/extensions/vtk/src/VTKViewport.js +++ b/extensions/vtk/src/VTKViewport.js @@ -19,8 +19,8 @@ const VTKViewport = props => { useEffect(() => { const handleScrollEvent = evt => { const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {}; - const viewportUid = vtkViewportApiReference.uid; - const viewportWasScrolled = viewportUid === evt.detail.uid; + const viewportUID = vtkViewportApiReference.uid; + const viewportWasScrolled = viewportUID === evt.detail.uid; if (viewportWasScrolled) { setViewportActiveHandler(); diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js index a0aadcc1d..7702b5656 100644 --- a/extensions/vtk/src/commandsModule.js +++ b/extensions/vtk/src/commandsModule.js @@ -70,12 +70,9 @@ const commandsModule = ({ commandsManager }) => { if (cornerstoneElement) { const imageId = cornerstoneElement.image.imageId; - const { modality } = cornerstone.metaData.get( - 'generalSeriesModule', - imageId - ); + const Modality = cornerstone.metaData.get('Modality', imageId); - if (modality !== 'PT') { + if (Modality !== 'PT') { const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi; return { diff --git a/extensions/vtk/src/handleSegmentationStorage.js b/extensions/vtk/src/handleSegmentationStorage.js index c8aa5983c..22c26ff39 100644 --- a/extensions/vtk/src/handleSegmentationStorage.js +++ b/extensions/vtk/src/handleSegmentationStorage.js @@ -1,42 +1,42 @@ -import * as dcmjs from "dcmjs"; +import * as dcmjs from 'dcmjs'; -import OHIF from "@ohif/core"; -import { api } from "dicomweb-client"; +import OHIF from '@ohif/core'; +import { api } from 'dicomweb-client'; const { StackManager } = OHIF.utils; -function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) { +function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); const displaySet = study.displaySets.find(set => { - return set.displaySetInstanceUid === displaySetInstanceUid; + return set.displaySetInstanceUID === displaySetInstanceUID; }); return displaySet; } -function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) { +function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); return study.displaySets.filter(set => { - return set.seriesInstanceUid === seriesInstanceUid; + return set.SeriesInstanceUID === SeriesInstanceUID; }); } -function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) { +function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); // Create shortcut to displaySet const displaySet = getDisplaySet( studies, - studyInstanceUid, - displaySetInstanceUid + StudyInstanceUID, + displaySetInstanceUID ); // Get stack from Stack Manager @@ -52,19 +52,19 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) { function retrieveDicomData( studyInstanceUID, seriesInstanceUID, - sopInstanceUID, + sopInstanceUid, wadoRoot ) { const config = { url: wadoRoot, - headers: DICOMWeb.getAuthorizationHeader() + headers: DICOMWeb.getAuthorizationHeader(), }; const dicomWeb = new api.DICOMwebClient(config); const options = { studyInstanceUID, seriesInstanceUID, - sopInstanceUID + sopInstanceUid, }; return dicomWeb.retrieveInstance(options); @@ -72,30 +72,26 @@ function retrieveDicomData( async function handleSegmentationStorage( studies, - studyInstanceUid, - displaySetInstanceUid + StudyInstanceUID, + displaySetInstanceUID ) { const study = studies.find( - study => study.studyInstanceUid === studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); const displaySet = getDisplaySet( studies, - studyInstanceUid, - displaySetInstanceUid + StudyInstanceUID, + displaySetInstanceUID ); // TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG // from google cloud const wadoRoot = displaySet.images[0].getData().wadoRoot; - const StudyInstanceUID = displaySet.images[0].getStudyInstanceUID(); - const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID(); - const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID(); - const arrayBuffer = await retrieveDicomData( - StudyInstanceUID, - SeriesInstanceUID, - SOPInstanceUID, + displaySet.images[0].getStudyInstanceUID(), + displaySet.images[0].getSeriesInstanceUID(), + displaySet.images[0].getSOPInstanceUID(), wadoRoot ); @@ -108,16 +104,16 @@ async function handleSegmentationStorage( dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta); - const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID; + const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID; const displaySets = getDisplaySetsBySeries( studies, - studyInstanceUid, - seriesInstanceUid + StudyInstanceUID, + SeriesInstanceUID ); if (displaySets.length > 1) { console.warn( - "More than one display set with the same seriesInstanceUid. This is not supported yet..." + 'More than one display set with the same SeriesInstanceUID. This is not supported yet...' ); } @@ -125,7 +121,7 @@ async function handleSegmentationStorage( const imageIds = referenceDisplaySet.images.map(image => image.getImageId()); if (!results) { - throw new Error("Fractional segmentations are not supported"); + throw new Error('Fractional segmentations are not supported'); } const cachedStack = StackManager.findOrCreateStack( @@ -137,13 +133,13 @@ async function handleSegmentationStorage( return { referenceDataObject, - labelmapDataObject + labelmapDataObject, }; return { - studyInstanceUid, - displaySetInstanceUid, - stack + StudyInstanceUID, + displaySetInstanceUID, + stack, }; } diff --git a/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js b/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js index 7ec4758c5..82ebbc5e6 100644 --- a/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js +++ b/extensions/vtk/src/toolbarComponents/VTKMPRToolbarButton.js @@ -13,21 +13,21 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI return false; }; - const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[ + const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[ activeViewportIndex ]; const studies = studyMetadataManager.all(); const study = studies.find( - study => study.studyInstanceUID === studyInstanceUid + study => study.studyInstanceUID === StudyInstanceUID ); if (!study) { return false; } - const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid); + const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID); if (!displaySet) { return false; diff --git a/platform/core/README.md b/platform/core/README.md index accc7585a..ac3b6c6bd 100644 --- a/platform/core/README.md +++ b/platform/core/README.md @@ -66,8 +66,8 @@ import { studies } from '@ohif/core'; const studiesMetadata = await studies.retrieveStudiesMetadata( server, // Object - studyInstanceUids, // Array - seriesInstanceUids // Array (optional) + studyInstanceUIDs, // Array + seriesInstanceUIDs // Array (optional) ); ``` diff --git a/platform/core/package.json b/platform/core/package.json index f60376e0d..901e85184 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -32,7 +32,7 @@ }, "peerDependencies": { "cornerstone-core": "^2.2.8", - "cornerstone-tools": "^4.12.0", + "cornerstone-tools": "4.12.0", "cornerstone-wado-image-loader": "^3.0.0", "dicom-parser": "^1.8.3" }, diff --git a/platform/core/src/DICOMSR/dataExchange.js b/platform/core/src/DICOMSR/dataExchange.js index 4cb332c4d..aab594da3 100644 --- a/platform/core/src/DICOMSR/dataExchange.js +++ b/platform/core/src/DICOMSR/dataExchange.js @@ -31,6 +31,7 @@ const retrieveMeasurements = server => { const serverUrl = server.wadoRoot; const studies = utils.studyMetadataManager.all(); + const latestSeries = findMostRecentStructuredReport(studies); if (!latestSeries) return Promise.resolve({}); @@ -57,17 +58,17 @@ const storeMeasurements = async (measurementData, filter, server) => { const serverUrl = server.wadoRoot; const firstMeasurementKey = Object.keys(measurementData)[0]; const firstMeasurement = measurementData[firstMeasurementKey][0]; - const studyInstanceUid = - firstMeasurement && firstMeasurement.studyInstanceUid; + const StudyInstanceUID = + firstMeasurement && firstMeasurement.StudyInstanceUID; try { await stowSRFromMeasurements(measurementData, serverUrl); - if (studyInstanceUid) { - studies.deleteStudyMetadataPromise(studyInstanceUid); + if (StudyInstanceUID) { + studies.deleteStudyMetadataPromise(StudyInstanceUID); } return { - message: 'Measurements were saved with success', + message: 'Measurements saved successfully', }; } catch (error) { log.error( diff --git a/platform/core/src/DICOMSR/parseDicomStructuredReport.js b/platform/core/src/DICOMSR/parseDicomStructuredReport.js index 576d077c5..d1801dec3 100644 --- a/platform/core/src/DICOMSR/parseDicomStructuredReport.js +++ b/platform/core/src/DICOMSR/parseDicomStructuredReport.js @@ -1,6 +1,6 @@ import * as dcmjs from 'dcmjs'; -import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid'; +import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid'; /** * Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData @@ -13,6 +13,7 @@ import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBy */ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { // Get the dicom data as an Object + const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer); const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( dicomData.dict @@ -30,17 +31,18 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { measurementData[toolName] = []; measurements.forEach(measurement => { - const instanceMetadata = findInstanceMetadataBySopInstanceUid( + const instanceMetadata = findInstanceMetadataBySopInstanceUID( displaySets, measurement.sopInstanceUid ); + const { _study: study, _series: series } = instanceMetadata; - const { studyInstanceUid, patientId } = study; - const { seriesInstanceUid } = series; + const { StudyInstanceUID, PatientID } = study; + const { SeriesInstanceUID } = series; const { sopInstanceUid, frameIndex } = measurement; const imagePath = getImagePath( - studyInstanceUid, - seriesInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, sopInstanceUid, frameIndex ); @@ -56,9 +58,10 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { const toolData = Object.assign({}, measurement, { imageId, imagePath, - seriesInstanceUid, - studyInstanceUid, - patientId, + SOPInstanceUID: sopInstanceUid, + SeriesInstanceUID, + StudyInstanceUID, + PatientID, measurementNumber: ++measurementNumber, timepointId: currentTimepointId, toolType: toolName, @@ -75,19 +78,19 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { /** * Function to create imagePath with all imageData related * - * @param {string} studyInstanceUid - * @param {string} seriesInstanceUid - * @param {string} sopInstanceUid + * @param {string} StudyInstanceUID + * @param {string} SeriesInstanceUID + * @param {string} SOPInstanceUID * @param {string} frameIndex * @returns */ const getImagePath = ( - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, frameIndex ) => { - return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join( + return [StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, frameIndex].join( '_' ); }; diff --git a/platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js b/platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js index 34ef42111..99ffbe0b5 100644 --- a/platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js +++ b/platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js @@ -2,17 +2,18 @@ * Should Find the requested instance metadata into the displaySets and return * * @param {Array} displaySets - List of displaySets - * @param {string} sopInstanceUid - sopInstanceUID to look for + * @param {string} SOPInstanceUID - sopInstanceUID to look for * @returns {Object} - instance metadata found */ -const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => { +const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => { let instanceFound; displaySets.find(displaySet => { if (!displaySet.images) return false; instanceFound = displaySet.images.find( - instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid + instanceMetadata => + instanceMetadata.getSOPInstanceUID() === SOPInstanceUID ); return !!instanceFound; @@ -21,4 +22,4 @@ const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => { return instanceFound; }; -export default findInstanceMetadataBySopInstanceUid; +export default findInstanceMetadataBySopInstanceUID; diff --git a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js index daebc4003..4db4db76c 100644 --- a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js +++ b/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js @@ -37,9 +37,9 @@ const isStructuredReportSeries = series => { ]; const firstInstance = series.getFirstInstance(); - const sopClassUid = firstInstance._instance.sopClassUid; + const SOPClassUID = firstInstance.getData().metadata.SOPClassUID; - return supportedSopClassUIDs.includes(sopClassUid); + return supportedSopClassUIDs.includes(SOPClassUID); }; /** @@ -51,9 +51,9 @@ const isStructuredReportSeries = series => { */ const compareSeriesDate = (series1, series2) => { return ( - series1._data.seriesDate > series2._data.seriesDate || - (series1._data.seriesDate === series2._data.seriesDate && - series1._data.seriesTime > series2._data.seriesTime) + series1._data.SeriesDate > series2._data.SeriesDate || + (series1._data.SeriesDate === series2._data.SeriesDate && + series1._data.SeriesTime > series2._data.SeriesTime) ); }; diff --git a/platform/core/src/DICOMWeb/getModalities.js b/platform/core/src/DICOMWeb/getModalities.js index 642c7316f..9d124f59b 100644 --- a/platform/core/src/DICOMWeb/getModalities.js +++ b/platform/core/src/DICOMWeb/getModalities.js @@ -1,23 +1,23 @@ -export default function getModalities(modality, modalitiesInStudy) { - if (!modality && !modalitiesInStudy) { +export default function getModalities(Modality, ModalitiesInStudy) { + if (!Modality && !ModalitiesInStudy) { return {}; } - const modalities = modality || { + const modalities = Modality || { vr: 'CS', Value: [], }; - if (modalitiesInStudy) { - if (modalities.vr && modalities.vr === modalitiesInStudy.vr) { - for (let i = 0; i < modalitiesInStudy.Value.length; i++) { - const value = modalitiesInStudy.Value[i]; + if (ModalitiesInStudy) { + if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) { + for (let i = 0; i < ModalitiesInStudy.Value.length; i++) { + const value = ModalitiesInStudy.Value[i]; if (modalities.Value.indexOf(value) === -1) { modalities.Value.push(value); } } } else { - return modalitiesInStudy; + return ModalitiesInStudy; } } diff --git a/platform/core/src/DICOMWeb/getModalities.test.js b/platform/core/src/DICOMWeb/getModalities.test.js index 958d031b4..debae322d 100644 --- a/platform/core/src/DICOMWeb/getModalities.test.js +++ b/platform/core/src/DICOMWeb/getModalities.test.js @@ -1,74 +1,74 @@ import getModalities from './getModalities'; describe('getModalities', () => { - test('should return an empty object when modality and modalitiesInStudy are not present', () => { - const modality = null; - const modalitiesInStudy = null; + test('should return an empty object when Modality and ModalitiesInStudy are not present', () => { + const Modality = null; + const ModalitiesInStudy = null; - expect(getModalities(modality, modalitiesInStudy)).toEqual({}); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual({}); }); - test('should return an empty object when modality and modalitiesInStudy are not present', () => { - const modality = null; - const modalitiesInStudy = null; + test('should return an empty object when Modality and ModalitiesInStudy are not present', () => { + const Modality = null; + const ModalitiesInStudy = null; - expect(getModalities(modality, modalitiesInStudy)).toEqual({}); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual({}); }); - test('should return modalities in Study when modality is not defined', () => { - const modality = null; + test('should return modalities in Study when Modality is not defined', () => { + const Modality = null; - const modalitiesInStudy = { + const ModalitiesInStudy = { Value: ['MOCKED_VALUE'], vr: 'MOCKED_VALUE', }; - expect(getModalities(modality, modalitiesInStudy)).toEqual( - modalitiesInStudy + expect(getModalities(Modality, ModalitiesInStudy)).toEqual( + ModalitiesInStudy ); }); - test('should return only the modalitues that exists in modalitiesInStudy', () => { - const modality = { + test('should return only the modalitues that exists in ModalitiesInStudy', () => { + const Modality = { Value: ['DESIRED_VALUE'], vr: 'DESIRED_VR', }; - const modalitiesInStudy = { + const ModalitiesInStudy = { Value: ['DESIRED_VALUE', 'NOT_DESIRED_VALUE'], vr: 'DESIRED_VR', }; - expect(getModalities(modality, modalitiesInStudy)).toEqual(modality); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality); }); - test('should return the seek modality when the desired modality does not exist in modalitiesInStudy', () => { - const modality = { + test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy', () => { + const Modality = { Value: ['DESIRED_VALUE'], vr: 'DESIRED_VR', }; - const modalitiesInStudy = { + const ModalitiesInStudy = { Value: ['NOT_DESIRED_VALUE'], vr: 'DESIRED_VR', }; - expect(getModalities(modality, modalitiesInStudy)).toEqual(modality); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality); }); - test('should return the seek modality when the desired modality does not exist in modalitiesInStudy VR', () => { - const modality = { + test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy VR', () => { + const Modality = { Value: ['DESIRED_VALUE'], vr: 'DESIRED_VR', }; - const modalitiesInStudy = { + const ModalitiesInStudy = { Value: ['NOT_DESIRED_VALUE'], vr: 'ANOTHER_VR', }; - expect(getModalities(modality, modalitiesInStudy)).toEqual( - modalitiesInStudy + expect(getModalities(Modality, ModalitiesInStudy)).toEqual( + ModalitiesInStudy ); }); }); diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.js index f8ba427fe..8178516e0 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.js @@ -1,412 +1,508 @@ -import { parsingUtils } from '../lib/parsingUtils'; - -const FUNCTION = 'function'; +import * as dcmjs from 'dcmjs'; +import queryString from 'query-string'; +import dicomParser from 'dicom-parser'; +import { getFallbackTagFromInstance } from '../utils/metadataProvider/metadataProviderFallbackTags'; +import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData'; +import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData'; class MetadataProvider { constructor() { // Define the main "metadataLookup" private property as an immutable property. - Object.defineProperty(this, 'metadataLookup', { + Object.defineProperty(this, 'studies', { configurable: false, enumerable: false, writable: false, value: new Map(), }); - - // Local reference to provider function bound to current instance. - Object.defineProperty(this, '_provider', { + Object.defineProperty(this, 'imageIdToUIDs', { configurable: false, enumerable: false, - writable: true, - value: null, + writable: false, + value: new Map(), }); } - /** - * Cornerstone Metadata provider to store image meta data - * Data from instances, series, and studies are associated with - * imageIds to facilitate usage of this information by Cornerstone's Tools - * - * e.g. the imagePlane metadata object contains instance information about - * row/column pixel spacing, patient position, and patient orientation. It - * is used in CornerstoneTools to position reference lines and orientation markers. - * - * @param {String} imageId The Cornerstone ImageId - * @param {Object} data An object containing instance, series, and study metadata - */ - addMetadata(imageId, data) { - const instanceMetadata = data.instance; - const seriesMetadata = data.series; - const studyMetadata = data.study; - const numImages = data.numImages; - const metadata = {}; + async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) { + let dicomJSONDataset; - metadata.frameNumber = data.frameNumber; + // If Arraybuffer, parse to DICOMJSON before naturalizing. + if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) { + const dicomData = DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer); - metadata.study = { - accessionNumber: studyMetadata.accessionNumber, - patientId: studyMetadata.patientId, - studyInstanceUid: studyMetadata.studyInstanceUid, - studyDate: studyMetadata.studyDate, - studyTime: studyMetadata.studyTime, - studyDescription: studyMetadata.studyDescription, - institutionName: studyMetadata.institutionName, - patientHistory: studyMetadata.patientHistory, - }; - - metadata.series = { - seriesDescription: seriesMetadata.seriesDescription, - seriesNumber: seriesMetadata.seriesNumber, - seriesDate: seriesMetadata.seriesDate, - seriesTime: seriesMetadata.seriesTime, - modality: seriesMetadata.modality, - seriesInstanceUid: seriesMetadata.seriesInstanceUid, - numImages: numImages, - }; - - metadata.instance = instanceMetadata; - - metadata.patient = { - name: studyMetadata.patientName, - id: studyMetadata.patientId, - birthDate: studyMetadata.patientBirthDate, - sex: studyMetadata.patientSex, - age: studyMetadata.patientAge, - }; - - // If there is sufficient information, populate - // the imagePlane object for easier use in the Viewer - metadata.imagePlane = this.getImagePlane(instanceMetadata); - - // Add the metadata to the imageId lookup object - this.metadataLookup.set(imageId, metadata); - } - - /** - * Return the metadata for the given imageId - * @param {String} imageId The Cornerstone ImageId - * @returns image metadata - */ - getMetadata(imageId) { - return this.metadataLookup.get(imageId); - } - - /** - * Adds a set of metadata to the Cornerstone metadata provider given a specific - * imageId, type, and dataset - * - * @param imageId - * @param type (e.g. series, instance, tagDisplay) - * @param data - */ - addSpecificMetadata(imageId, type, data) { - const metadata = {}; - metadata[type] = data; - - const oldMetadata = this.metadataLookup.get(imageId); - this.metadataLookup.set(imageId, Object.assign(oldMetadata, metadata)); - } - - getFromImage(image, type, tag, attrName, defaultValue) { - let value; - - if (image.data) { - value = this.getFromDataSet(image.data, type, tag); + dicomJSONDataset = dicomData.dict; } else { - value = image.instance[attrName]; + dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer; } - return value === null ? defaultValue : value; - } + // Check if dataset is already naturalized. - getFromDataSet(dataSet, type, tag) { - if (!dataSet) { - return; - } + let naturalizedDataset; - const fn = dataSet[type]; - if (!fn) { - return; - } - - return fn.call(dataSet, tag); - } - - getFrameIncrementPointer(image) { - const dataSet = image.data; - let frameInstancePointer = ''; - - if (parsingUtils.isValidDataSet(dataSet)) { - const frameInstancePointerNames = { - x00181063: 'frameTime', - x00181065: 'frameTimeVector', - }; - - // (0028,0009) = Frame Increment Pointer - const frameInstancePointerTag = parsingUtils.attributeTag( - dataSet, - 'x00280009' + if (dicomJSONDataset['SeriesInstanceUID'] === undefined) { + naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( + dicomJSONDataset ); - frameInstancePointer = frameInstancePointerNames[frameInstancePointerTag]; } else { - frameInstancePointer = image.instance.frameIncrementPointer; + naturalizedDataset = dicomJSONDataset; } - return frameInstancePointer || ''; - } + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + } = naturalizedDataset; - getFrameTimeVector(image) { - const dataSet = image.data; + const study = this._getAndCacheStudy(StudyInstanceUID); + const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID); + const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID); - if (parsingUtils.isValidDataSet(dataSet)) { - // Frame Increment Pointer points to Frame Time Vector (0018,1065) field - return parsingUtils.floatArray(dataSet, 'x00181065'); + Object.assign(instance, naturalizedDataset); + + if (options.server) { + await this._checkBulkDataAndInlineBinaries(instance, options.server); } - return image.instance.frameTimeVector; + return instance; } - getFrameTime(image) { - const dataSet = image.data; + addImageIdToUIDs(imageId, uids) { + // This method is a fallback for when you don't have WADO-URI or WADO-RS. + // You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here. + // An example would be dicom hosted at some random site. - if (parsingUtils.isValidDataSet(dataSet)) { - // Frame Increment Pointer points to Frame Time (0018,1063) field or is not defined (for addtional flexibility). - // Yet another value is possible for this field (5200,9230 for Multi-frame Functional Groups) - // but that case is currently not supported. - return dataSet.floatString('x00181063', -1); + this.imageIdToUIDs.set(imageId, uids); + } + + _getAndCacheStudy(StudyInstanceUID) { + const studies = this.studies; + + let study = studies.get(StudyInstanceUID); + + if (!study) { + study = { series: new Map() }; + studies.set(StudyInstanceUID, study); } - return image.instance.frameTime; + return study; + } + _getAndCacheSeriesFromStudy(study, SeriesInstanceUID) { + let series = study.series.get(SeriesInstanceUID); + + if (!series) { + series = { instances: new Map() }; + study.series.set(SeriesInstanceUID, series); + } + + return series; } - /** - * Updates the related metadata for missing fields given a specified image - * - * @param image - */ - updateMetadata(image) { - const imageMetadata = this.metadataLookup.get(image.imageId); - if (!imageMetadata) { + _getAndCacheInstanceFromStudy(series, SOPInstanceUID) { + let instance = series.instances.get(SOPInstanceUID); + + if (!instance) { + instance = {}; + series.instances.set(SOPInstanceUID, instance); + } + + return instance; + } + + async _checkBulkDataAndInlineBinaries(instance, server) { + await fetchOverlayData(instance, server); + + if (instance.PhotometricInterpretation === 'PALETTE COLOR') { + await fetchPaletteColorLookupTableData(instance, server); + } + } + + _getInstance(imageId) { + const uids = this._getUIDsFromImageID(imageId); + + if (!uids) { return; } - imageMetadata.patient.age = - imageMetadata.patient.age || - this.getFromDataSet(image.data, 'string', 'x00101010'); + const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = uids; - imageMetadata.instance.rows = imageMetadata.instance.rows || image.rows; - imageMetadata.instance.columns = - imageMetadata.instance.columns || image.columns; - - imageMetadata.instance.sopClassUid = - imageMetadata.instance.sopClassUid || - this.getFromDataSet(image.data, 'string', 'x00080016'); - imageMetadata.instance.sopInstanceUid = - imageMetadata.instance.sopInstanceUid || - this.getFromDataSet(image.data, 'string', 'x00080018'); - - imageMetadata.instance.pixelSpacing = - imageMetadata.instance.pixelSpacing || - this.getFromDataSet(image.data, 'string', 'x00280030'); - imageMetadata.instance.frameOfReferenceUID = - imageMetadata.instance.frameOfReferenceUID || - this.getFromDataSet(image.data, 'string', 'x00200052'); - imageMetadata.instance.imageOrientationPatient = - imageMetadata.instance.imageOrientationPatient || - this.getFromDataSet(image.data, 'string', 'x00200037'); - imageMetadata.instance.imagePositionPatient = - imageMetadata.instance.imagePositionPatient || - this.getFromDataSet(image.data, 'string', 'x00200032'); - - imageMetadata.instance.sliceThickness = - imageMetadata.instance.sliceThickness || - this.getFromDataSet(image.data, 'string', 'x00180050'); - imageMetadata.instance.sliceLocation = - imageMetadata.instance.sliceLocation || - this.getFromDataSet(image.data, 'string', 'x00201041'); - imageMetadata.instance.tablePosition = - imageMetadata.instance.tablePosition || - this.getFromDataSet(image.data, 'string', 'x00189327'); - imageMetadata.instance.spacingBetweenSlices = - imageMetadata.instance.spacingBetweenSlices || - this.getFromDataSet(image.data, 'string', 'x00180088'); - - imageMetadata.instance.lossyImageCompression = - imageMetadata.instance.lossyImageCompression || - this.getFromDataSet(image.data, 'string', 'x00282110'); - imageMetadata.instance.lossyImageCompressionRatio = - imageMetadata.instance.lossyImageCompressionRatio || - this.getFromDataSet(image.data, 'string', 'x00282112'); - - imageMetadata.instance.frameIncrementPointer = - imageMetadata.instance.frameIncrementPointer || - this.getFromDataSet(image.data, 'string', 'x00280009'); - imageMetadata.instance.frameTime = - imageMetadata.instance.frameTime || - this.getFromDataSet(image.data, 'string', 'x00181063'); - imageMetadata.instance.frameTimeVector = - imageMetadata.instance.frameTimeVector || - this.getFromDataSet(image.data, 'string', 'x00181065'); - - if ( - (image.data || image.instance) && - !imageMetadata.instance.multiframeMetadata - ) { - imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata( - image - ); - } - - imageMetadata.imagePlane = - imageMetadata.imagePlane || this.getImagePlane(imageMetadata.instance); - } - - /** - * Constructs and returns the imagePlane given the metadata instance - * - * @param metadataInstance The metadata instance (InstanceMetadata class) containing information to construct imagePlane - * @returns imagePlane The constructed imagePlane to be used in viewer easily - */ - getImagePlane(instance) { - if ( - !instance.rows || - !instance.columns || - !instance.pixelSpacing || - !instance.frameOfReferenceUID || - !instance.imageOrientationPatient || - !instance.imagePositionPatient - ) { - return; - } - - const imageOrientation = instance.imageOrientationPatient.split('\\'); - const imagePosition = instance.imagePositionPatient.split('\\'); - - let columnPixelSpacing = 1.0; - let rowPixelSpacing = 1.0; - if (instance.pixelSpacing) { - const split = instance.pixelSpacing.split('\\'); - rowPixelSpacing = parseFloat(split[0]); - columnPixelSpacing = parseFloat(split[1]); - } - - return { - frameOfReferenceUID: instance.frameOfReferenceUID, - rows: instance.rows, - columns: instance.columns, - rowCosines: [ - parseFloat(imageOrientation[0]), - parseFloat(imageOrientation[1]), - parseFloat(imageOrientation[2]), - ], - columnCosines: [ - parseFloat(imageOrientation[3]), - parseFloat(imageOrientation[4]), - parseFloat(imageOrientation[5]), - ], - imagePositionPatient: [ - parseFloat(imagePosition[0]), - parseFloat(imagePosition[1]), - parseFloat(imagePosition[2]), - ], - rowPixelSpacing, - columnPixelSpacing, - }; - } - - /** - * This function extracts miltiframe information from a dicomParser.DataSet object. - * - * @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found. - * @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc). - */ - getMultiframeModuleMetadata(image) { - const imageInfo = { - isMultiframeImage: false, - frameIncrementPointer: null, - numberOfFrames: 0, - frameTime: 0, - frameTimeVector: null, - averageFrameRate: 0, // backwards compatibility only... it might be useless in the future - }; - - let frameTime; - - const numberOfFrames = this.getFromImage( - image, - 'intString', - 'x00280008', - 'numberOfFrames', - -1 + return this._getInstanceData( + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ); + } - if (numberOfFrames > 0) { - // set multi-frame image indicator - imageInfo.isMultiframeImage = true; - imageInfo.numberOfFrames = numberOfFrames; + get(query, imageId, options = { fallback: false }) { + const instance = this._getInstance(imageId); - // (0028,0009) = Frame Increment Pointer - const frameIncrementPointer = this.getFrameIncrementPointer(image); + if (query === INSTANCE) { + return instance; + } - if (frameIncrementPointer === 'frameTimeVector') { - // Frame Increment Pointer points to Frame Time Vector (0018,1065) field - const frameTimeVector = this.getFrameTimeVector(image); + return this.getTagFromInstance(query, instance, options); + } - if (frameTimeVector instanceof Array && frameTimeVector.length > 0) { - imageInfo.frameIncrementPointer = frameIncrementPointer; - imageInfo.frameTimeVector = frameTimeVector; - frameTime = - frameTimeVector.reduce((a, b) => a + b) / frameTimeVector.length; - imageInfo.averageFrameRate = 1000 / frameTime; - } - } else if ( - frameIncrementPointer === 'frameTime' || - frameIncrementPointer === '' - ) { - frameTime = this.getFrameTime(image); + getTag(query, imageId, options) { + return this.get(query, imageId, options); + } - if (frameTime > 0) { - imageInfo.frameIncrementPointer = frameIncrementPointer; - imageInfo.frameTime = frameTime; - imageInfo.averageFrameRate = 1000 / frameTime; - } + getInstance(imageId) { + return this.get(INSTANCE, imageId); + } + + getTagFromInstance( + naturalizedTagOrWADOImageLoaderTag, + instance, + options = { fallback: false } + ) { + if (!instance) { + return; + } + + // If its a naturalized dcmjs tag present on the instance, return. + if (instance[naturalizedTagOrWADOImageLoaderTag]) { + return instance[naturalizedTagOrWADOImageLoaderTag]; + } + + if (options.fallback) { + // Perhaps the tag has fallbacks? + const fallbackTag = getFallbackTagFromInstance( + naturalizedTagOrWADOImageLoaderTag, + instance + ); + + if (fallbackTag) { + return fallbackTag; } } - return imageInfo; + // Maybe its a legacy CornerstoneWADOImageLoader tag then: + return this._getCornerstoneWADOImageLoaderTag( + naturalizedTagOrWADOImageLoaderTag, + instance + ); } - /** - * Get a bound reference to the provider function. - */ - getProvider() { - let provider = this._provider; - if (typeof this._provider !== FUNCTION) { - provider = this.provider.bind(this); - this._provider = provider; + _getCornerstoneWADOImageLoaderTag(wadoImageLoaderTag, instance) { + let metadata; + + switch (wadoImageLoaderTag) { + case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE: + const { SeriesDate, SeriesTime } = instance; + + let seriesDate; + let seriesTime; + + if (SeriesDate) { + seriesDate = dicomParser.parseDA(SeriesDate); + } + + if (SeriesTime) { + seriesTime = dicomParser.parseTM(SeriesTime); + } + + metadata = { + modality: instance.Modality, + seriesInstanceUID: instance.SeriesInstanceUID, + seriesNumber: instance.SeriesNumber, + studyInstanceUID: instance.StudyInstanceUID, + seriesDate, + seriesTime, + }; + break; + case WADO_IMAGE_LOADER_TAGS.PATIENT_STUDY_MODULE: + metadata = { + patientAge: instance.PatientAge, + patientSize: instance.PatientSize, + patientWeight: instance.PatientWeight, + }; + break; + case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE: + const { ImageOrientationPatient } = instance; + + // Fallback for DX images. + const PixelSpacing = getFallbackTagFromInstance( + 'PixelSpacing', + instance + ); + + let rowPixelSpacing; + let columnPixelSpacing; + + let rowCosines; + let columnCosines; + + if (PixelSpacing) { + rowPixelSpacing = PixelSpacing[0]; + columnPixelSpacing = PixelSpacing[1]; + } + + if (ImageOrientationPatient) { + rowCosines = ImageOrientationPatient.slice(0, 3); + columnCosines = ImageOrientationPatient.slice(3, 6); + } + + metadata = { + frameOfReferenceUID: instance.FrameOfReferenceUID, + rows: instance.Rows, + columns: instance.Columns, + imageOrientationPatient: ImageOrientationPatient, + rowCosines, + columnCosines, + imagePositionPatient: instance.ImagePositionPatient, + sliceThickness: instance.SliceThickness, + sliceLocation: instance.SliceLocation, + pixelSpacing: PixelSpacing, + rowPixelSpacing, + columnPixelSpacing, + }; + break; + case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE: + metadata = { + samplesPerPixel: instance.SamplesPerPixel, + photometricInterpretation: instance.PhotometricInterpretation, + rows: instance.Rows, + columns: instance.Columns, + bitsAllocated: instance.BitsAllocated, + bitsStored: instance.BitsStored, + highBit: instance.HighBit, + pixelRepresentation: instance.PixelRepresentation, + planarConfiguration: instance.PlanarConfiguration, + pixelAspectRatio: instance.PixelAspectRatio, + smallestPixelValue: instance.SmallestPixelValue, + largestPixelValue: instance.LargestPixelValue, + redPaletteColorLookupTableDescriptor: + instance.RedPaletteColorLookupTableDescriptor, + greenPaletteColorLookupTableDescriptor: + instance.GreenPaletteColorLookupTableDescriptor, + bluePaletteColorLookupTableDescriptor: + instance.BluePaletteColorLookupTableDescriptor, + redPaletteColorLookupTableData: + instance.RedPaletteColorLookupTableData, + greenPaletteColorLookupTableData: + instance.GreenPaletteColorLookupTableData, + bluePaletteColorLookupTableData: + instance.BluePaletteColorLookupTableData, + }; + + break; + case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE: + const { WindowCenter, WindowWidth } = instance; + + const windowCenter = Array.isArray(WindowCenter) + ? WindowCenter + : [WindowCenter]; + const windowWidth = Array.isArray(WindowWidth) + ? WindowWidth + : [WindowWidth]; + + metadata = { + windowCenter, + windowWidth, + }; + + break; + case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE: + metadata = { + rescaleIntercept: instance.RescaleIntercept, + rescaleSlope: instance.RescaleSlope, + rescaleType: instance.RescaleType, + }; + break; + case WADO_IMAGE_LOADER_TAGS.SOP_COMMON_MODULE: + metadata = { + sopClassUID: instance.SOPClassUID, + sopInstanceUID: instance.SOPInstanceUID, + }; + break; + case WADO_IMAGE_LOADER_TAGS.PET_ISOTOPE_MODULE: + const { RadiopharmaceuticalInformationSequence } = instance; + + if (RadiopharmaceuticalInformationSequence) { + const RadiopharmaceuticalInformation = Array.isArray( + RadiopharmaceuticalInformationSequence + ) + ? RadiopharmaceuticalInformationSequence[0] + : RadiopharmaceuticalInformationSequence; + + const { + RadiopharmaceuticalStartTime, + RadionuclideTotalDose, + RadionuclideHalfLife, + } = RadiopharmaceuticalInformation; + + const radiopharmaceuticalInfo = { + radiopharmaceuticalStartTime: dicomParser.parseTM( + RadiopharmaceuticalStartTime + ), + radionuclideTotalDose: RadionuclideTotalDose, + radionuclideHalfLife: RadionuclideHalfLife, + }; + metadata = { + radiopharmaceuticalInfo, + }; + } + + break; + case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE: + metadata = []; + + for ( + let overlayGroup = 0x00; + overlayGroup <= 0x1e; + overlayGroup += 0x02 + ) { + let groupStr = `60${overlayGroup.toString(16)}`; + + if (groupStr.length === 3) { + groupStr = `600${overlayGroup.toString(16)}`; + } + + const OverlayDataTag = `${groupStr}3000`; + const OverlayData = instance[OverlayDataTag]; + + if (!OverlayData) { + continue; + } + + const OverlayRowsTag = `${groupStr}0010`; + const OverlayColumnsTag = `${groupStr}0011`; + const OverlayType = `${groupStr}0040`; + const OverlayOriginTag = `${groupStr}0050`; + const OverlayDescriptionTag = `${groupStr}0022`; + const OverlayLabelTag = `${groupStr}1500`; + const ROIAreaTag = `${groupStr}1301`; + const ROIMeanTag = `${groupStr}1302`; + const ROIStandardDeviationTag = `${groupStr}1303`; + const OverlayOrigin = instance[OverlayOriginTag]; + + const overlay = { + rows: instance[OverlayRowsTag], + columns: instance[OverlayColumnsTag], + type: instance[OverlayType], + x: OverlayOrigin[0], + y: OverlayOrigin[1], + pixelData: OverlayData, + description: instance[OverlayDescriptionTag], + label: instance[OverlayLabelTag], + roiArea: instance[ROIAreaTag], + roiMean: instance[ROIMeanTag], + roiStandardDeviation: instance[ROIStandardDeviationTag], + }; + + metadata.push(overlay); + } + + break; + + case WADO_IMAGE_LOADER_TAGS.PATIENT_MODULE: + const { PatientName } = instance; + + let patientName; + if (PatientName) { + patientName = PatientName.Alphabetic; + } + + metadata = { + patientName, + patientId: instance.PatientID, + }; + + break; + + case WADO_IMAGE_LOADER_TAGS.GENERAL_IMAGE_MODULE: + metadata = { + instanceNumber: instance.InstanceNumber, + lossyImageCompression: instance.LossyImageCompression, + lossyImageCompressionRatio: instance.LossyImageCompressionRatio, + lossyImageCompressionMethod: instance.LossyImageCompressionMethod, + }; + + break; + case WADO_IMAGE_LOADER_TAGS.GENERAL_STUDY_MODULE: + metadata = { + studyDescription: instance.StudyDescription, + studyDate: instance.StudyDate, + studyTime: instance.StudyTime, + accessionNumber: instance.AccessionNumber, + }; + + break; + case WADO_IMAGE_LOADER_TAGS.CINE_MODULE: + metadata = { + frameTime: instance.FrameTime, + }; + + break; } - return provider; + return metadata; } - /** - * Looks up metadata for Cornerstone Tools given a specified type and imageId - * A type may be, e.g. 'study', or 'patient', or 'imagePlaneModule'. These types - * are keys in the stored metadata objects. - * - * @param type - * @param imageId - * @returns {Object} Relevant metadata of the specified type - */ - provider(type, imageId) { - const imageMetadata = this.metadataLookup.get(imageId); - if (!imageMetadata) { + _getInstanceData(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { + const study = this.studies.get(StudyInstanceUID); + + if (!study) { return; } - if (imageMetadata.hasOwnProperty(type)) { - return imageMetadata[type]; + const series = study.series.get(SeriesInstanceUID); + + if (!series) { + return; + } + + const instance = series.instances.get(SOPInstanceUID); + + return instance; + } + + _getUIDsFromImageID(imageId) { + if (imageId.includes('wadors:')) { + const strippedImageId = imageId.split('studies/')[1]; + const splitImageId = strippedImageId.split('/'); + + return { + StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series' + SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances' + SOPInstanceUID: splitImageId[4], + }; + } + if (imageId.includes('wado?requestType=WADO')) { + const qs = queryString.parse(imageId); + + return { + StudyInstanceUID: qs.studyUID, + SeriesInstanceUID: qs.seriesUID, + SOPInstanceUID: qs.objectUID, + }; + } else { + // Maybe its a non-standard imageId + return this.imageIdToUIDs.get(imageId); } } } -export default MetadataProvider; +const metadataProvider = new MetadataProvider(); + +export default metadataProvider; + +const WADO_IMAGE_LOADER_TAGS = { + // CornerstoneWADOImageLoader specific + GENERAL_SERIES_MODULE: 'generalSeriesModule', + PATIENT_STUDY_MODULE: 'patientStudyModule', + IMAGE_PLANE_MODULE: 'imagePlaneModule', + IMAGE_PIXEL_MODULE: 'imagePixelModule', + VOI_LUT_MODULE: 'voiLutModule', + MODALITY_LUT_MODULE: 'modalityLutModule', + SOP_COMMON_MODULE: 'sopCommonModule', + PET_ISOTOPE_MODULE: 'petIsotopeModule', + OVERLAY_PLANE_MODULE: 'overlayPlaneModule', + + // react-cornerstone-viewport specifc + PATIENT_MODULE: 'patientModule', + GENERAL_IMAGE_MODULE: 'generalImageModule', + GENERAL_STUDY_MODULE: 'generalStudyModule', + CINE_MODULE: 'cineModule', +}; + +const INSTANCE = 'instance'; diff --git a/platform/core/src/classes/OHIFStudyMetadataSource.js b/platform/core/src/classes/OHIFStudyMetadataSource.js index f78a5e6c1..4e81f4113 100644 --- a/platform/core/src/classes/OHIFStudyMetadataSource.js +++ b/platform/core/src/classes/OHIFStudyMetadataSource.js @@ -1,9 +1,8 @@ -import { studyMetadataManager, updateMetaDataManager } from '../utils'; +import { studyMetadataManager } from '../utils'; import OHIFError from './OHIFError'; import { StudyMetadata } from './metadata/StudyMetadata'; import { StudyMetadataSource } from './StudyMetadataSource.js'; -import { StudySummary } from './metadata/StudySummary'; import { retrieveStudyMetadata } from '../studies/retrieveStudyMetadata.js'; export class OHIFStudyMetadataSource extends StudyMetadataSource { @@ -19,12 +18,12 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource { /** * Load study info (OHIF.viewer.Studies) and study metadata (OHIF.viewer.StudyMetadataList) for a given study. - * @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object. + * @param {StudyMetadata} study StudyMetadata object. */ loadStudy(study) { - if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) { + if (!(study instanceof StudyMetadata)) { throw new OHIFError( - 'OHIFStudyMetadataSource::loadStudy study is not an instance of StudySummary or StudyMetadata' + 'OHIFStudyMetadataSource::loadStudy study is not an instance of StudyMetadata' ); } @@ -33,7 +32,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource { if (study instanceof StudyMetadata) { const alreadyLoaded = OHIF.viewer.Studies.findBy({ - studyInstanceUid: studyInstanceUID, + StudyInstanceUID: studyInstanceUID, }); if (!alreadyLoaded) { @@ -49,7 +48,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource { // Create study metadata object const studyMetadata = new StudyMetadata( studyInfo, - studyInfo.studyInstanceUid + studyInfo.StudyInstanceUID ); // Get Study display sets @@ -72,10 +71,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource { // Set some studyInfo properties studyInfo.selected = true; studyInfo.displaySets = studyMetadata.getDisplaySets(); - - // Updates WADO-RS metaDataManager - updateMetaDataManager(studyInfo); - studyMetadataManager.add(studyMetadata); } } diff --git a/platform/core/src/classes/OHIFStudySummary.js b/platform/core/src/classes/OHIFStudySummary.js deleted file mode 100644 index e40072a77..000000000 --- a/platform/core/src/classes/OHIFStudySummary.js +++ /dev/null @@ -1,55 +0,0 @@ -import StudySummary from './StudySummary'; - -/** - * Constants - */ - -const STRING = 'string'; -const propertyReplacementMap = { - modalities: 'ModalitiesInStudy', - patientBirthdate: 'PatientBirthDate', -}; - -/** - * OHIF Viewers specialized version of StudySummary class - */ -export class OHIFStudySummary extends StudySummary { - // @Override - addTags(tagMap) { - const _hasOwn = Object.prototype.hasOwnProperty; - const _tagMap = Object.create(null); - for (let property in tagMap) { - if (_hasOwn.call(tagMap, property)) { - let standardProperty = OHIFStudySummary.getStandardPropertyName( - property - ); - if (standardProperty) { - _tagMap[standardProperty] = tagMap[property]; - } - } - } - super.addTags(_tagMap); - } - - /** - * Turns a non-standard, OHIF specific, DICOM property name into a standard one. - * @param {string} property A string representing a non-conforming keyword. - * @returns {string|undefined} Returns a standard-conforming property name. - */ - static getStandardPropertyName(property) { - let standardProperty; - if (typeof property === STRING && property.charAt(0) !== '_') { - if (property in propertyReplacementMap) { - standardProperty = propertyReplacementMap[propertyReplacementMap]; - } else { - standardProperty = property - .replace(/^sop/, 'SOP') - .replace(/Uid$/, 'UID') - .replace(/Id$/, 'ID'); - standardProperty = - standardProperty.charAt(0).toUpperCase() + standardProperty.substr(1); - } - } - return standardProperty; - } -} diff --git a/platform/core/src/classes/StudyLoadingListener.js b/platform/core/src/classes/StudyLoadingListener.js index 9859bb056..c655250bc 100644 --- a/platform/core/src/classes/StudyLoadingListener.js +++ b/platform/core/src/classes/StudyLoadingListener.js @@ -60,8 +60,8 @@ class BaseLoadingListener { } _getProgressId() { - const displaySetInstanceUid = this.stack.displaySetInstanceUid; - return 'StackProgress:' + displaySetInstanceUid; + const displaySetInstanceUID = this.stack.displaySetInstanceUID; + return 'StackProgress:' + displaySetInstanceUID; } _clearProgress() { @@ -362,7 +362,7 @@ class StackLoadingListener extends BaseLoadingListener { _logProgress() { const totalFramesCount = this.stack.imageIds.length; - const displaySetInstanceUid = this.stack.displaySetInstanceUid; + const displaySetInstanceUID = this.stack.displaySetInstanceUID; let progressBar = '['; for (let i = 0; i < totalFramesCount; i++) { @@ -371,7 +371,7 @@ class StackLoadingListener extends BaseLoadingListener { } progressBar += ']'; - log.info(`${displaySetInstanceUid}: ${progressBar}`); + log.info(`${displaySetInstanceUID}: ${progressBar}`); } } @@ -388,12 +388,12 @@ class StudyLoadingListener { return; } - const displaySetInstanceUid = stack.displaySetInstanceUid; + const displaySetInstanceUID = stack.displaySetInstanceUID; - if (!this.listeners[displaySetInstanceUid]) { + if (!this.listeners[displaySetInstanceUID]) { const listener = this._createListener(stack, stackMetaData); if (listener) { - this.listeners[displaySetInstanceUid] = listener; + this.listeners[displaySetInstanceUID] = listener; } } } @@ -424,12 +424,12 @@ class StudyLoadingListener { } clear() { - const displaySetInstanceUids = Object.keys(this.listeners); - const length = displaySetInstanceUids.length; + const displaySetInstanceUIDs = Object.keys(this.listeners); + const length = displaySetInstanceUIDs.length; for (let i = 0; i < length; i++) { - const displaySetInstanceUid = displaySetInstanceUids[i]; - const displaySet = this.listeners[displaySetInstanceUid]; + const displaySetInstanceUID = displaySetInstanceUIDs[i]; + const displaySet = this.listeners[displaySetInstanceUID]; displaySet.destroy(); } diff --git a/platform/core/src/classes/StudyMetadataSource.js b/platform/core/src/classes/StudyMetadataSource.js index 8f4fb8dc3..af9b6ac6c 100644 --- a/platform/core/src/classes/StudyMetadataSource.js +++ b/platform/core/src/classes/StudyMetadataSource.js @@ -19,7 +19,7 @@ export class StudyMetadataSource { /** * Load study info and study metadata for a given study into the viewer. - * @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object. + * @param {StudyMetadata} study StudyMetadata object. */ loadStudy(study) { /** diff --git a/platform/core/src/classes/StudyPrefetcher.js b/platform/core/src/classes/StudyPrefetcher.js index fc2c4afc8..5f384ad4c 100644 --- a/platform/core/src/classes/StudyPrefetcher.js +++ b/platform/core/src/classes/StudyPrefetcher.js @@ -94,17 +94,23 @@ export class StudyPrefetcher { } getStudy(image) { - const studyMetadata = cornerstone.metaData.get('study', image.imageId); + const StudyInstanceUID = cornerstone.metaData.get( + 'StudyInstanceUID', + image.imageId + ); return OHIF.viewer.Studies.find( - study => study.studyInstanceUid === studyMetadata.studyInstanceUid + study => study.StudyInstanceUID === StudyInstanceUID ); } getSeries(study, image) { - const seriesMetadata = cornerstone.metaData.get('series', image.imageId); + const SeriesInstanceUID = cornerstone.metaData.get( + 'SeriesInstanceUID', + image.imageId + ); const studyMetadata = OHIF.viewerbase.getStudyMetadata(study); - return studyMetadata.getSeriesByUID(seriesMetadata.seriesInstanceUid); + return studyMetadata.getSeriesByUID(SeriesInstanceUID); } getInstance(series, image) { @@ -112,13 +118,13 @@ export class StudyPrefetcher { 'instance', image.imageId ); - return series.getInstanceByUID(instanceMetadata.sopInstanceUid); + return series.getInstanceByUID(instanceMetadata.SOPInstanceUID); } getActiveDisplaySet(displaySets, instance) { return displaySets.find(displaySet => { return displaySet.images.some(displaySetImage => { - return displaySetImage.sopInstanceUid === instance.sopInstanceUid; + return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID; }); }); } diff --git a/platform/core/src/classes/index.js b/platform/core/src/classes/index.js index c259f8845..9fc2f1bfe 100644 --- a/platform/core/src/classes/index.js +++ b/platform/core/src/classes/index.js @@ -1,15 +1,10 @@ -import { - InstanceMetadata, - SeriesMetadata, - StudyMetadata, - StudySummary, -} from './metadata'; +import { InstanceMetadata, SeriesMetadata, StudyMetadata } from './metadata'; import CommandsManager from './CommandsManager.js'; import { DICOMFileLoadingListener } from './StudyLoadingListener'; import HotkeysManager from './HotkeysManager.js'; import ImageSet from './ImageSet'; -import MetadataProvider from './MetadataProvider.js'; +import MetadataProvider from './MetadataProvider'; import OHIFError from './OHIFError.js'; import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource'; import { StackLoadingListener } from './StudyLoadingListener'; @@ -31,7 +26,6 @@ export { StudyMetadata, SeriesMetadata, InstanceMetadata, - StudySummary, TypeSafeCollection, OHIFError, StudyMetadataSource, @@ -50,7 +44,6 @@ const classes = { StudyMetadata, SeriesMetadata, InstanceMetadata, - StudySummary, TypeSafeCollection, OHIFError, StudyMetadataSource, diff --git a/platform/core/src/classes/metadata/InstanceMetadata.js b/platform/core/src/classes/metadata/InstanceMetadata.js index 4b59852ae..27c899c80 100644 --- a/platform/core/src/classes/metadata/InstanceMetadata.js +++ b/platform/core/src/classes/metadata/InstanceMetadata.js @@ -8,20 +8,12 @@ import OHIFError from '../OHIFError.js'; const UNDEFINED = 'undefined'; const STRING = 'string'; -const STUDY_INSTANCE_UID = 'x0020000d'; -const SERIES_INSTANCE_UID = 'x0020000e'; export class InstanceMetadata extends Metadata { constructor(data, uid) { super(data, uid); // Initialize Private Properties Object.defineProperties(this, { - _sopInstanceUID: { - configurable: true, // configurable so that it can be redefined in sub-classes... - enumerable: false, - writable: true, - value: null, - }, _imageId: { configurable: true, // configurable so that it can be redefined in sub-classes... enumerable: false, @@ -43,14 +35,14 @@ export class InstanceMetadata extends Metadata { */ _definePublicProperties() { /** - * Property: this.sopInstanceUID + * Property: this.SOPInstanceUID * Same as this.getSOPInstanceUID() * It's specially useful in contexts where a method call is not suitable like in search criteria. For example: * sopInstanceCollection.findBy({ - * sopInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0' + * SOPInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0' * }); */ - Object.defineProperty(this, 'sopInstanceUID', { + Object.defineProperty(this, 'SOPInstanceUID', { configurable: false, enumerable: false, get: function() { @@ -67,21 +59,21 @@ export class InstanceMetadata extends Metadata { * Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call. */ getStudyInstanceUID() { - return this.getTagValue(STUDY_INSTANCE_UID, null); + return this.getTagValue('StudyInstanceUID', null); } /** * Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call. */ getSeriesInstanceUID() { - return this.getTagValue(SERIES_INSTANCE_UID, null); + return this.getTagValue('SeriesInstanceUID', null); } /** * Returns the SOPInstanceUID of the current instance. */ getSOPInstanceUID() { - return this._sopInstanceUID; + return this.getTagValue('SOPInstanceUID', null); } // @TODO: Improve this... (E.g.: blob data) @@ -127,13 +119,6 @@ export class InstanceMetadata extends Metadata { return typeof value === STRING ? parseInt(value) : value; } - /** - * @deprecated Please use getTagValue instead. - */ - getRawValue(tagOrProperty, defaultValue) { - return this.getTagValue(tagOrProperty, defaultValue); - } - /** * This function should be overriden by specialized classes in order to allow client libraries or viewers to take advantage of the Study Metadata API. */ diff --git a/platform/core/src/classes/metadata/OHIFInstanceMetadata.js b/platform/core/src/classes/metadata/OHIFInstanceMetadata.js index 8fc5f42a5..884016182 100644 --- a/platform/core/src/classes/metadata/OHIFInstanceMetadata.js +++ b/platform/core/src/classes/metadata/OHIFInstanceMetadata.js @@ -1,5 +1,4 @@ import { InstanceMetadata } from './InstanceMetadata'; -import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions.js'; import getImageId from '../../utils/getImageId.js'; export class OHIFInstanceMetadata extends InstanceMetadata { @@ -20,7 +19,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata { configurable: false, enumerable: false, writable: false, - value: instance.sopInstanceUid, + value: instance.SOPInstanceUID, }, _study: { configurable: false, @@ -56,16 +55,16 @@ export class OHIFInstanceMetadata extends InstanceMetadata { return this._cache[tagOrProperty]; } - const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty); + const instanceData = this._instance.metadata; // Search property value in the whole study metadata chain... let rawValue; - if (propertyName in this._instance) { - rawValue = this._instance[propertyName]; - } else if (propertyName in this._series) { - rawValue = this._series[propertyName]; - } else if (propertyName in this._study) { - rawValue = this._study[propertyName]; + if (tagOrProperty in instanceData) { + rawValue = instanceData[tagOrProperty]; + } else if (tagOrProperty in this._series) { + rawValue = this._series[tagOrProperty]; + } else if (tagOrProperty in this._study) { + rawValue = this._study[tagOrProperty]; } if (rawValue !== void 0) { @@ -79,12 +78,10 @@ export class OHIFInstanceMetadata extends InstanceMetadata { // Override tagExists(tagOrProperty) { - const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty); - return ( - propertyName in this._instance || - propertyName in this._series || - propertyName in this._study + tagOrProperty in this._instance.metadata || + tagOrProperty in this._series || + tagOrProperty in this._study ); } @@ -97,28 +94,4 @@ export class OHIFInstanceMetadata extends InstanceMetadata { return this._imageId; } - - /** - * Static methods - */ - - // @TODO: The current mapping of standard DICOM property names to local property names is not optimal. - // The inconsistency in property naming makes this function increasingly complex. - // A possible solution to improve this would be adapt retriveMetadata names to use DICOM standard names as in dicomTagDescriptions.js - static getPropertyName(tagOrProperty) { - let propertyName; - const tagInfo = DICOMTagDescriptions.find(tagOrProperty); - - if (tagInfo !== void 0) { - // This function tries to translate standard DICOM property names into local naming convention. - propertyName = tagInfo.keyword - .replace(/^SOP/, 'sop') - .replace(/UID$/, 'Uid') - .replace(/ID$/, 'Id'); - propertyName = - propertyName.charAt(0).toLowerCase() + propertyName.substr(1); - } - - return propertyName; - } } diff --git a/platform/core/src/classes/metadata/OHIFSeriesMetadata.js b/platform/core/src/classes/metadata/OHIFSeriesMetadata.js index 668a686e1..ba2c71ea3 100644 --- a/platform/core/src/classes/metadata/OHIFSeriesMetadata.js +++ b/platform/core/src/classes/metadata/OHIFSeriesMetadata.js @@ -18,7 +18,7 @@ export class OHIFSeriesMetadata extends SeriesMetadata { configurable: false, enumerable: false, writable: false, - value: series.seriesInstanceUid, + value: series.SeriesInstanceUID, }); // populate internal list of instances... diff --git a/platform/core/src/classes/metadata/OHIFStudyMetadata.js b/platform/core/src/classes/metadata/OHIFStudyMetadata.js index e5396dd48..8ebf574ff 100644 --- a/platform/core/src/classes/metadata/OHIFStudyMetadata.js +++ b/platform/core/src/classes/metadata/OHIFStudyMetadata.js @@ -18,11 +18,11 @@ export class OHIFStudyMetadata extends StudyMetadata { configurable: false, enumerable: false, writable: false, - value: study.studyInstanceUid, + value: study.StudyInstanceUID, }); // populate internal list of series - study.seriesList.forEach(series => { + study.series.forEach(series => { this.addSeries(new OHIFSeriesMetadata(series, study)); }); } diff --git a/platform/core/src/classes/metadata/README.md b/platform/core/src/classes/metadata/README.md index 264b754df..0ce89b17e 100644 --- a/platform/core/src/classes/metadata/README.md +++ b/platform/core/src/classes/metadata/README.md @@ -83,10 +83,10 @@ well (again, flexibility and extensibility are design goals). Most implemented methods rely on the implementation of an unimplemented method. For example, the method `getStringValue` from `InstanceMetadata` class, which has indeed been implemented and is meant to retrieve a metadata value as a string, internally -calls the `getRawValue` method which _was NOT implemented_ and is meant to query +calls the `getTagValue` method which _was NOT implemented_ and is meant to query the internal data structures for the requested metadata value and return it _as is_. Used in that way, an application would not benefit much from the already -implemented methods. On the other hand, by simply overriding the `getRawValue` +implemented methods. On the other hand, by simply overriding the `getTagValue` method on a specialized class to deal with the intrinsics of its internal data structures, this very application would now benefit from all already implemented methods. @@ -99,12 +99,12 @@ The following code snippet tries to illustrate the idea: class InstanceMetadata { [ ... ] - getRawValue(tagOrProperty, defaultValue) { + getTagValue(tagOrProperty, defaultValue) { // Please implement this method in a specialized subclass... } [ ... ] getStringValue(tagOrProperty, index, defaultValue) { - let rawValue = this.getRawValue(tagOrProperty, ''); + let rawValue = this.getTagValue(tagOrProperty, ''); // parse the returned value into a string... [ ... ] return stringValue; @@ -117,11 +117,11 @@ class InstanceMetadata { class MyFancyAppInstanceMetadata extends InstanceMetadata { // Overriding this method will make all methods implemented in the super class // that rely on it to be immediately available... - getRawValue(tagOrProperty, defaultValue) { - let rawValue; + getTagValue(tagOrProperty, defaultValue) { + let tagValue; // retrieve raw value from internal data structures... [ ... ] - return rawValue; + return tagValue; } } @@ -135,9 +135,9 @@ if (sopInstaceMetadata instanceof MyFancyAppInstanceMetadata) { // true if (sopInstaceMetadata instanceof InstanceMetadata) { // also true // this code will also be executed... } -// The following will also work since the internal "getRawValue" call inside +// The following will also work since the internal "getTagValue" call inside // "getStringValue" method will now be satisfied... (thanks to the override) -let patientName = sopInstaceMetadata.getStringValue('PatientName', ''); +let PatientName = sopInstaceMetadata.getStringValue('PatientName', ''); [ ... ] ``` diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index 74191a3b5..c0cb917f3 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -110,14 +110,14 @@ export class StudyMetadata extends Metadata { return; } - const sopClassUids = getSopClassUids(series); + const sopClassUIDs = getSopClassUIDs(series); if (sopClassHandlerModules && sopClassHandlerModules.length > 0) { const displaySet = _getDisplaySetFromSopClassModule( sopClassHandlerModules, series, study, - sopClassUids + sopClassUIDs ); if (displaySet) { displaySet.sopClassModule = true; @@ -135,10 +135,10 @@ export class StudyMetadata extends Metadata { // series into another display set. const stackableInstances = []; series.forEachInstance(instance => { - // All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010) + // All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010) if ( - !isImage(instance.getRawValue('x00080016')) && - !instance.getRawValue('x00280010') + !isImage(instance.getTagValue('SOPClassUID')) && + !instance.getTagValue('Rows') ) { return; } @@ -149,23 +149,23 @@ export class StudyMetadata extends Metadata { displaySet = makeDisplaySet(series, [instance]); displaySet.setAttributes({ - sopClassUids, + sopClassUIDs, isClip: true, - seriesInstanceUid: series.getSeriesInstanceUID(), - studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid for drag/drop purposes - numImageFrames: instance.getRawValue('x00280008'), // Override the default value of instances.length - instanceNumber: instance.getRawValue('x00200013'), // Include the instance number - acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime + SeriesInstanceUID: series.getSeriesInstanceUID(), + StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes + numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length + InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number + AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime }); displaySets.push(displaySet); - } else if (isSingleImageModality(instance.modality)) { + } else if (isSingleImageModality(instance.Modality)) { displaySet = makeDisplaySet(series, [instance]); displaySet.setAttributes({ - sopClassUids, - studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid - seriesInstanceUid: series.getSeriesInstanceUID(), - instanceNumber: instance.getRawValue('x00200013'), // Include the instance number - acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime + sopClassUIDs, + StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID + SeriesInstanceUID: series.getSeriesInstanceUID(), + InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number + AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime }); displaySets.push(displaySet); } else { @@ -175,9 +175,9 @@ export class StudyMetadata extends Metadata { if (stackableInstances.length) { const displaySet = makeDisplaySet(series, stackableInstances); - displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID()); + displaySet.setAttribute('StudyInstanceUID', study.getStudyInstanceUID()); displaySet.setAttributes({ - sopClassUids, + sopClassUIDs, }); displaySets.push(displaySet); } @@ -416,7 +416,7 @@ export class StudyMetadata extends Metadata { * needs series sorted by the same criteria used for sorting display sets. */ sortSeriesByDisplaySets() { - // Object for mapping display sets' index by seriesInstanceUid + // Object for mapping display sets' index by SeriesInstanceUID const displaySetsMapping = {}; // Loop through each display set to create the mapping @@ -428,8 +428,8 @@ export class StudyMetadata extends Metadata { } // In case of multiframe studies, just get the first index occurence - if (displaySetsMapping[displaySet.seriesInstanceUid] === void 0) { - displaySetsMapping[displaySet.seriesInstanceUid] = index; + if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) { + displaySetsMapping[displaySet.SeriesInstanceUID] = index; } }); @@ -581,8 +581,7 @@ export class StudyMetadata extends Metadata { const dwc = api.DICOMwebClient; const isMultiFrame = instance => { - // NumberOfFrames (0028,0008) - return instance.getRawValue('x00280008') > 1; + return instance.getTagValue('NumberOfFrames') > 1; }; const makeDisplaySet = (series, instances) => { @@ -592,15 +591,15 @@ const makeDisplaySet = (series, instances) => { // set appropriate attributes to image set... imageSet.setAttributes({ - displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID - seriesDate: seriesData.seriesDate, - seriesTime: seriesData.seriesTime, - seriesInstanceUid: series.getSeriesInstanceUID(), - seriesNumber: instance.getRawValue('x00200011'), - seriesDescription: instance.getRawValue('x0008103e'), + displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID + SeriesDate: seriesData.SeriesDate, + SeriesTime: seriesData.SeriesTime, + SeriesInstanceUID: series.getSeriesInstanceUID(), + SeriesNumber: instance.getTagValue('SeriesNumber'), + SeriesDescription: instance.getTagValue('SeriesDescription'), numImageFrames: instances.length, - frameRate: instance.getRawValue('x00181063'), - modality: instance.getRawValue('x00080060'), + frameRate: instance.getTagValue('FrameTime'), + Modality: instance.getTagValue('Modality'), isMultiFrame: isMultiFrame(instance), }); @@ -610,19 +609,19 @@ const makeDisplaySet = (series, instances) => { imageSet.sortBy((a, b) => { // Sort by InstanceNumber (0020,0013) return ( - (parseInt(a.getRawValue('x00200013', 0)) || 0) - - (parseInt(b.getRawValue('x00200013', 0)) || 0) + (parseInt(a.getTagValue('InstanceNumber', 0)) || 0) - + (parseInt(b.getTagValue('InstanceNumber', 0)) || 0) ); }); } // Include the first image instance number (after sorted) imageSet.setAttribute( - 'instanceNumber', - imageSet.getImage(0).getRawValue('x00200013') + 'InstanceNumber', + imageSet.getImage(0).getTagValue('InstanceNumber') ); - const isReconstructable = isDisplaySetReconstructable(series, instances); + const isReconstructable = isDisplaySetReconstructable(instances); imageSet.isReconstructable = isReconstructable.value; @@ -635,57 +634,57 @@ const makeDisplaySet = (series, instances) => { return imageSet; }; -const isSingleImageModality = modality => { - return modality === 'CR' || modality === 'MG' || modality === 'DX'; +const isSingleImageModality = Modality => { + return Modality === 'CR' || Modality === 'MG' || Modality === 'DX'; }; -function getSopClassUids(series) { - const uniqueSopClassUidsInSeries = new Set(); +function getSopClassUIDs(series) { + const uniqueSopClassUIDsInSeries = new Set(); series.forEachInstance(instance => { - const instanceSopClassUid = instance.getRawValue('x00080016'); + const instanceSopClassUID = instance.getTagValue('SOPClassUID'); - uniqueSopClassUidsInSeries.add(instanceSopClassUid); + uniqueSopClassUIDsInSeries.add(instanceSopClassUID); }); - const sopClassUids = Array.from(uniqueSopClassUidsInSeries); + const sopClassUIDs = Array.from(uniqueSopClassUIDsInSeries); - return sopClassUids; + return sopClassUIDs; } /** * @private * @param {SeriesMetadata} series * @param {StudyMetadata} study - * @param {string[]} sopClassUids + * @param {string[]} sopClassUIDs */ function _getDisplaySetFromSopClassModule( sopClassHandlerExtensions, // TODO: Update Usage series, study, - sopClassUids + sopClassUIDs ) { - // TODO: For now only use the plugins if all instances have the same sopClassUid - if (sopClassUids.length !== 1) { + // TODO: For now only use the plugins if all instances have the same SOPClassUID + if (sopClassUIDs.length !== 1) { console.warn( - 'getDisplaySetFromSopClassPlugin: More than one SOPClassUid in the same series is not yet supported.' + 'getDisplaySetFromSopClassPlugin: More than one SOPClassUID in the same series is not yet supported.' ); return; } - const sopClassUid = sopClassUids[0]; + const SOPClassUID = sopClassUIDs[0]; const sopClassHandlerModules = sopClassHandlerExtensions.map(extension => { return extension.module; }); - const handlersForSopClassUid = sopClassHandlerModules.filter(module => { - return module.sopClassUids.includes(sopClassUid); + const handlersForSopClassUID = sopClassHandlerModules.filter(module => { + return module.sopClassUIDs.includes(SOPClassUID); }); // TODO: Sort by something, so we can determine which plugin to use - if (!handlersForSopClassUid || !handlersForSopClassUid.length) { + if (!handlersForSopClassUID || !handlersForSopClassUID.length) { return; } - const plugin = handlersForSopClassUid[0]; + const plugin = handlersForSopClassUID[0]; const headers = DICOMWeb.getAuthorizationHeader(); const dicomWebClient = new dwc({ url: study.getData().wadoRoot, @@ -698,15 +697,15 @@ function _getDisplaySetFromSopClassModule( dicomWebClient, headers ); - if (displaySet && !displaySet.modality) { + if (displaySet && !displaySet.Modality) { const instance = series.getFirstInstance(); - displaySet.modality = instance.getRawValue('x00080060'); + displaySet.Modality = instance.getTagValue('Modality'); } return displaySet; } /** - * Sort series primarily by modality (i.e., series with references to other + * Sort series primarily by Modality (i.e., series with references to other * series like SEG, KO or PR are grouped in the end of the list) and then by * series number: * @@ -724,8 +723,8 @@ function _getDisplaySetFromSopClassModule( */ function seriesSortingCriteria(a, b) { - const isLowPriorityA = isLowPriorityModality(a.modality); - const isLowPriorityB = isLowPriorityModality(b.modality); + const isLowPriorityA = isLowPriorityModality(a.Modality); + const isLowPriorityB = isLowPriorityModality(b.Modality); if (!isLowPriorityA && isLowPriorityB) { return -1; } @@ -742,7 +741,7 @@ function seriesSortingCriteria(a, b) { */ function sortBySeriesNumber(a, b) { const seriesNumberAIsGreaterOrUndefined = - a.seriesNumber > b.seriesNumber || (!a.seriesNumber && b.seriesNumber); + a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber); return seriesNumberAIsGreaterOrUndefined ? 1 : -1; } diff --git a/platform/core/src/classes/metadata/StudySummary.js b/platform/core/src/classes/metadata/StudySummary.js deleted file mode 100644 index a9b4550d3..000000000 --- a/platform/core/src/classes/metadata/StudySummary.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Metadata } from './Metadata'; -import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions'; - -/** - * Constants - */ - -const STUDY_INSTANCE_UID = 'x0020000d'; - -/** - * Class Definition - */ - -export class StudySummary extends Metadata { - constructor(tagMap, attributeMap, uid) { - // Call the superclass constructor passing an plain object with no prototype to be used as the main "_data" attribute. - const _data = Object.create(null); - super(_data, uid); - - // Initialize internal tag map if first argument is given. - if (tagMap !== void 0) { - this.addTags(tagMap); - } - - // Initialize internal property map if second argument is given. - if (attributeMap !== void 0) { - this.setCustomAttributes(attributeMap); - } - } - - getStudyInstanceUID() { - // This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API - return this.getTagValue(STUDY_INSTANCE_UID) || null; - } - - /** - * Append tags to internal tag map. - * @param {Object} tagMap An object whose own properties will be used as tag values and appended to internal tag map. - */ - addTags(tagMap) { - const _hasOwn = Object.prototype.hasOwnProperty; - const _data = this._data; - for (let tag in tagMap) { - if (_hasOwn.call(tagMap, tag)) { - const description = DICOMTagDescriptions.find(tag); - // When a description is available, use its tag as internal key... - if (description) { - _data[description.tag] = tagMap[tag]; - } else { - _data[tag] = tagMap[tag]; - } - } - } - } - - tagExists(tagName) { - const _data = this._data; - const description = DICOMTagDescriptions.find(tagName); - if (description) { - return description.tag in _data; - } - return tagName in _data; - } - - getTagValue(tagName) { - const _data = this._data; - const description = DICOMTagDescriptions.find(tagName); - if (description) { - return _data[description.tag]; - } - return _data[tagName]; - } -} diff --git a/platform/core/src/classes/metadata/WadoRsMetaDataBuilder.js b/platform/core/src/classes/metadata/WadoRsMetaDataBuilder.js deleted file mode 100644 index 4cd0e8a35..000000000 --- a/platform/core/src/classes/metadata/WadoRsMetaDataBuilder.js +++ /dev/null @@ -1,48 +0,0 @@ -export class WadoRsMetaDataBuilder { - constructor() { - this.tags = {}; - } - - addTag(tag, value, multi) { - this.tags[tag] = { - tag, - value, - multi, - }; - - return this; - } - - toJSON() { - const json = {}; - const keys = Object.keys(this.tags); - - keys.forEach(key => { - if (!this.tags.hasOwnProperty(key)) { - return; - } - - const tag = this.tags[key]; - const multi = !!tag.multi; - let value = tag.value; - - if (value == null || (value.length === 1 && value[0] == null)) { - return; - } - - if (typeof value === 'string' && multi) { - value = value.split('\\'); - } - - if (!Array.isArray(value)) { - value = [value]; - } - - json[key] = { - Value: value, - }; - }); - - return json; - } -} diff --git a/platform/core/src/classes/metadata/index.js b/platform/core/src/classes/metadata/index.js index a3e6da50d..227654142 100644 --- a/platform/core/src/classes/metadata/index.js +++ b/platform/core/src/classes/metadata/index.js @@ -5,13 +5,9 @@ import { OHIFSeriesMetadata } from './OHIFSeriesMetadata'; import { OHIFStudyMetadata } from './OHIFStudyMetadata'; import { SeriesMetadata } from './SeriesMetadata'; import { StudyMetadata } from './StudyMetadata'; -import { StudySummary } from './StudySummary'; -import { WadoRsMetaDataBuilder } from './WadoRsMetaDataBuilder'; const metadata = { Metadata, - StudySummary, - WadoRsMetaDataBuilder, StudyMetadata, SeriesMetadata, InstanceMetadata, @@ -22,8 +18,6 @@ const metadata = { export { Metadata, - StudySummary, - WadoRsMetaDataBuilder, StudyMetadata, SeriesMetadata, InstanceMetadata, diff --git a/platform/core/src/cornerstone.js b/platform/core/src/cornerstone.js index dc7f99338..7940792ff 100644 --- a/platform/core/src/cornerstone.js +++ b/platform/core/src/cornerstone.js @@ -1,4 +1,4 @@ -import MetadataProvider from './classes/MetadataProvider'; +import metadataProvider from './classes/MetadataProvider'; import { getBoundingBox, pixelToPage, @@ -6,7 +6,7 @@ import { } from './lib/cornerstone.js'; const cornerstone = { - MetadataProvider, + metadataProvider, getBoundingBox, pixelToPage, repositionTextBox, diff --git a/platform/core/src/hanging-protocols/HPMatcher.js b/platform/core/src/hanging-protocols/HPMatcher.js index 9c1775a47..26eea7ef3 100644 --- a/platform/core/src/hanging-protocols/HPMatcher.js +++ b/platform/core/src/hanging-protocols/HPMatcher.js @@ -6,24 +6,19 @@ import { CustomAttributeRetrievalCallbacks } from './customAttributes'; /** * Import Constants */ -const { StudySummary, InstanceMetadata } = metadata; +const { InstanceMetadata } = metadata; /** * Match a Metadata instance against rules using Validate.js for validation. - * @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object + * @param {InstanceMetadata} metadataInstance Metadata instance object * @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match * @return {Object} Matching Object with score and details (which rule passed or failed) */ const match = (metadataInstance, rules) => { // Make sure the supplied data is valid. - if ( - !( - metadataInstance instanceof StudySummary || - metadataInstance instanceof InstanceMetadata - ) - ) { + if (!(metadataInstance instanceof InstanceMetadata)) { throw new OHIFError( - 'HPMatcher::match metadataInstance must be an instance of StudySummary or InstanceMetadata' + 'HPMatcher::match metadataInstance must be an instance of InstanceMetadata' ); } diff --git a/platform/core/src/hanging-protocols/ProtocolEngine.js b/platform/core/src/hanging-protocols/ProtocolEngine.js index ea32b2f1e..492d71dfd 100644 --- a/platform/core/src/hanging-protocols/ProtocolEngine.js +++ b/platform/core/src/hanging-protocols/ProtocolEngine.js @@ -13,7 +13,7 @@ import { ProtocolStore } from './protocolStore/classes'; /** * Import Constants */ -const { StudyMetadata, InstanceMetadata, StudySummary } = metadata; +const { StudyMetadata, InstanceMetadata } = metadata; // Useful constants const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue'; @@ -298,10 +298,7 @@ export default class ProtocolEngine { } // Invalid data - if ( - !(priorStudy instanceof StudyMetadata) && - !(priorStudy instanceof StudySummary) - ) { + if (!priorStudy instanceof StudyMetadata) { return; } @@ -385,11 +382,9 @@ export default class ProtocolEngine { // This tests to make sure there is actually image data in this instance // TODO: Change this when we add PDF and MPEG support // See https://ohiforg.atlassian.net/browse/LT-227 - // sopClassUid = x00080016 - // rows = x00280010 if ( - !isImage(instance.getTagValue('x00080016')) && - !instance.getTagValue('x00280010') + !isImage(instance.getTagValue('SOPClassUID')) && + !instance.getTagValue('Rows') ) { return; } @@ -436,19 +431,19 @@ export default class ProtocolEngine { const currentSOPInstanceUID = instance.getSOPInstanceUID(); const imageDetails = { - studyInstanceUid: study.getStudyInstanceUID(), - seriesInstanceUid: series.getSeriesInstanceUID(), - sopInstanceUid: currentSOPInstanceUID, + StudyInstanceUID: study.getStudyInstanceUID(), + SeriesInstanceUID: series.getSeriesInstanceUID(), + SOPInstanceUID: currentSOPInstanceUID, currentImageIdIndex: index, matchingScore: totalMatchScore, matchDetails: matchDetails, sortingInfo: { score: totalMatchScore, study: - instance.getTagValue('x00080020') + - instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030 - series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011 - instance: parseInt(instance.getTagValue('x00200013')), // TODO: change for acquisitionTime InstanceNumber = x00200013 + instance.getTagValue('StudyDate') + + instance.getTagValue('StudyTime'), + series: parseInt(instance.getTagValue('SeriesNumber')), // TODO: change for seriesDateTime + instance: parseInt(instance.getTagValue('InstanceNumber')), // TODO: change for acquisitionTime }, }; @@ -461,7 +456,7 @@ export default class ProtocolEngine { // If the instance was found, set the displaySet ID if (displaySet) { - imageDetails.displaySetInstanceUid = displaySet.getUID(); + imageDetails.displaySetInstanceUID = displaySet.getUID(); imageDetails.imageId = instance.getImageId(); } @@ -654,25 +649,25 @@ export default class ProtocolEngine { } if (currentMatch && currentMatch.imageId) { - currentViewportData.studyInstanceUid = currentMatch.studyInstanceUid; - currentViewportData.seriesInstanceUid = currentMatch.seriesInstanceUid; - currentViewportData.sopInstanceUid = currentMatch.sopInstanceUid; + currentViewportData.StudyInstanceUID = currentMatch.StudyInstanceUID; + currentViewportData.SeriesInstanceUID = currentMatch.SeriesInstanceUID; + currentViewportData.SOPInstanceUID = currentMatch.SOPInstanceUID; currentViewportData.currentImageIdIndex = currentMatch.currentImageIdIndex; - currentViewportData.displaySetInstanceUid = - currentMatch.displaySetInstanceUid; + currentViewportData.displaySetInstanceUID = + currentMatch.displaySetInstanceUID; currentViewportData.imageId = currentMatch.imageId; } // @TODO Why should we throw an exception when a best match is not found? This was aborting the whole process. - // if (!currentViewportData.displaySetInstanceUid) { + // if (!currentViewportData.displaySetInstanceUID) { // throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?'); // } viewportData.push(currentViewportData); }); - this.setLayout(layoutProps.rows, layoutProps.columns); + this.setLayout(layoutProps.Rows, layoutProps.Columns); if (typeof this.options.setViewportSpecificData !== 'function') { log.error( diff --git a/platform/core/src/hanging-protocols/classes/Rule.js b/platform/core/src/hanging-protocols/classes/Rule.js index bf0ddc0dc..29f93c791 100644 --- a/platform/core/src/hanging-protocols/classes/Rule.js +++ b/platform/core/src/hanging-protocols/classes/Rule.js @@ -6,7 +6,7 @@ const EQUALS_REGEXP = /^equals$/; /** * This Class represents a Rule to be evaluated given a set of attributes * Rules have: - * - An attribute (e.g. 'seriesDescription') + * - An attribute (e.g. 'SeriesDescription') * - A constraint Object, in the form required by Validate.js: * * rule.constraint = { diff --git a/platform/core/src/hanging-protocols/classes/ViewportStructure.js b/platform/core/src/hanging-protocols/classes/ViewportStructure.js index 2a605fa06..908ef2962 100644 --- a/platform/core/src/hanging-protocols/classes/ViewportStructure.js +++ b/platform/core/src/hanging-protocols/classes/ViewportStructure.js @@ -45,9 +45,9 @@ export default class ViewportStructure { // Viewport structure can be updated later when we build more complex display layouts switch (this.type) { case 'grid': - // For the typical grid layout, we only need to multiply rows by columns to + // For the typical grid layout, we only need to multiply Rows by Columns to // obtain the number of viewports - return this.properties.rows * this.properties.columns; + return this.properties.Rows * this.properties.Columns; } } } diff --git a/platform/core/src/hanging-protocols/protocolStore/defaultProtocol.js b/platform/core/src/hanging-protocols/protocolStore/defaultProtocol.js index f285cf832..52beaeb92 100644 --- a/platform/core/src/hanging-protocols/protocolStore/defaultProtocol.js +++ b/platform/core/src/hanging-protocols/protocolStore/defaultProtocol.js @@ -9,8 +9,8 @@ function getDefaultProtocol() { protocol.locked = true; const oneByOne = new ViewportStructure('grid', { - rows: 1, - columns: 1, + Rows: 1, + Columns: 1, }); const viewport = new Viewport(); diff --git a/platform/core/src/hanging-protocols/protocolStore/testProtocols.js b/platform/core/src/hanging-protocols/protocolStore/testProtocols.js index c7852546d..23f5b8539 100644 --- a/platform/core/src/hanging-protocols/protocolStore/testProtocols.js +++ b/platform/core/src/hanging-protocols/protocolStore/testProtocols.js @@ -14,8 +14,8 @@ function getMRTwoByTwoTest() { proto.locked = true; // Use http://localhost:3000/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78 - var studyInstanceUid = new ProtocolMatchingRule( - 'studyInstanceUid', + var StudyInstanceUID = new ProtocolMatchingRule( + 'StudyInstanceUID', { equals: { value: '1.2.840.113619.2.5.1762583153.215519.978957063.78', @@ -24,30 +24,30 @@ function getMRTwoByTwoTest() { true ); - proto.addProtocolMatchingRule(studyInstanceUid); + proto.addProtocolMatchingRule(StudyInstanceUID); var oneByTwo = new ViewportStructure('grid', { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }); // Stage 1 var left = new Viewport(); var right = new Viewport(); - var firstSeries = new SeriesMatchingRule('seriesNumber', { + var firstSeries = new SeriesMatchingRule('SeriesNumber', { equals: { value: 1, }, }); - var secondSeries = new SeriesMatchingRule('seriesNumber', { + var secondSeries = new SeriesMatchingRule('SeriesNumber', { equals: { value: 2, }, }); - var thirdImage = new ImageMatchingRule('instanceNumber', { + var thirdImage = new ImageMatchingRule('InstanceNumber', { equals: { value: 3, }, @@ -67,19 +67,19 @@ function getMRTwoByTwoTest() { // Stage 2 var twoByOne = new ViewportStructure('grid', { - rows: 2, - columns: 1, + Rows: 2, + Columns: 1, }); var left2 = new Viewport(); var right2 = new Viewport(); - var fourthSeries = new SeriesMatchingRule('seriesNumber', { + var fourthSeries = new SeriesMatchingRule('SeriesNumber', { equals: { value: 4, }, }); - var fifthSeries = new SeriesMatchingRule('seriesNumber', { + var fifthSeries = new SeriesMatchingRule('SeriesNumber', { equals: { value: 5, }, @@ -133,8 +133,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -195,8 +195,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -257,8 +257,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + Rows: 2, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -402,8 +402,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -464,8 +464,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -548,8 +548,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + Rows: 2, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -737,8 +737,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 1, + Rows: 1, + Columns: 1, }, layoutTemplateName: 'gridLayout', }, @@ -770,8 +770,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + Rows: 2, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -931,8 +931,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -993,8 +993,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -1044,8 +1044,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -1127,8 +1127,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -1190,8 +1190,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 1, - columns: 2, + Rows: 1, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -1275,8 +1275,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + Rows: 2, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, @@ -1386,8 +1386,8 @@ function getDemoProtocols() { viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + Rows: 2, + Columns: 2, }, layoutTemplateName: 'gridLayout', }, diff --git a/platform/core/src/lib/cornerstone.js b/platform/core/src/lib/cornerstone.js index d4d472c99..4a321addf 100644 --- a/platform/core/src/lib/cornerstone.js +++ b/platform/core/src/lib/cornerstone.js @@ -21,7 +21,7 @@ function getBoundingBox(context, textLines, x, y, options) { // Get the text width in the current font const width = context.measureText(text).width; - // Find the maximum with for all the text rows; + // Find the maximum with for all the text Rows; maxWidth = Math.max(maxWidth, width); }); diff --git a/platform/core/src/measurements/classes/MeasurementApi.js b/platform/core/src/measurements/classes/MeasurementApi.js index 04db3592f..c8a7eed87 100644 --- a/platform/core/src/measurements/classes/MeasurementApi.js +++ b/platform/core/src/measurements/classes/MeasurementApi.js @@ -8,9 +8,8 @@ import guid from '../../utils/guid'; import studyMetadataManager from '../../utils/studyMetadataManager'; import { measurementApiDefaultConfig } from './../configuration.js'; - const configuration = { - ...measurementApiDefaultConfig + ...measurementApiDefaultConfig, }; export default class MeasurementApi { @@ -99,8 +98,8 @@ export default class MeasurementApi { const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState(); // Stop here if the metadata for the measurement's study is not loaded yet - const { studyInstanceUid } = measurement; - const metadata = studyMetadataManager.get(studyInstanceUid); + const { StudyInstanceUID } = measurement; + const metadata = studyMetadataManager.get(StudyInstanceUID); if (!metadata) return; // Iterate each child tool if the current tool has children @@ -221,7 +220,7 @@ export default class MeasurementApi { this.options.onMeasurementsUpdated(Object.assign({}, this.tools)); } - retrieveMeasurements(patientId, timepointIds) { + retrieveMeasurements(PatientID, timepointIds) { const retrievalFn = configuration.dataExchange.retrieve; const { server } = configuration; if (typeof retrievalFn !== 'function') { @@ -297,9 +296,9 @@ export default class MeasurementApi { : null; const timepoints = this.timepointApi.all(timepointFilter); const timepointIds = timepoints.map(t => t.timepointId); - const patientId = timepoints[0].patientId; + const PatientID = timepoints[0].PatientID; const filter = { - patientId, + PatientID, timepointIds, }; @@ -576,17 +575,17 @@ export default class MeasurementApi { const toolGroupId = this.toolsGroupsMap[measurementData.toolType]; - // TODO: Remove TrialPatientLocationUid from here and override it somehow + // TODO: Remove TrialPatientLocationUID from here and override it somehow // by dependant applications. Here we should use the location attribute instead of the uid let filter; const uid = measurementData.additionalData && - measurementData.additionalData.TrialPatientLocationUid; + measurementData.additionalData.TrialPatientLocationUID; if (uid) { filter = tool => tool._id !== measurementData._id && tool.additionalData && - tool.additionalData.TrialPatientLocationUid === uid; + tool.additionalData.TrialPatientLocationUID === uid; } else { filter = tool => tool._id !== measurementData._id && @@ -691,8 +690,8 @@ export default class MeasurementApi { // Get the timepoint let timepoint; - if (measurement.studyInstanceUid) { - timepoint = this.timepointApi.study(measurement.studyInstanceUid)[0]; + if (measurement.StudyInstanceUID) { + timepoint = this.timepointApi.study(measurement.StudyInstanceUID)[0]; } else { const { timepointId } = measurement; timepoint = this.timepointApi.timepoints.find( @@ -760,7 +759,7 @@ export default class MeasurementApi { measurement.lesionNamingNumber = found.lesionNamingNumber; measurement.measurementNumber = found.measurementNumber; - // TODO: Remove TrialPatientLocationUid from here and override it somehow + // TODO: Remove TrialPatientLocationUID from here and override it somehow // by dependant applications // Change the update object to set the same number, additionalData, @@ -768,8 +767,8 @@ export default class MeasurementApi { updateObject.lesionNamingNumber = found.lesionNamingNumber; updateObject.measurementNumber = found.measurementNumber; updateObject.additionalData = measurement.additionalData || {}; - updateObject.additionalData.TrialPatientLocationUid = - found.additionalData && found.additionalData.TrialPatientLocationUid; + updateObject.additionalData.TrialPatientLocationUID = + found.additionalData && found.additionalData.TrialPatientLocationUID; updateObject.location = found.location; updateObject.label = found.label; updateObject.description = found.description; @@ -807,7 +806,7 @@ export default class MeasurementApi { toolId: toolType, toolItemId: addedMeasurement._id, timepointId: timepoint.timepointId, - studyInstanceUid: addedMeasurement.studyInstanceUid, + StudyInstanceUID: addedMeasurement.StudyInstanceUID, createdAt: addedMeasurement.createdAt, lesionNamingNumber: addedMeasurement.lesionNamingNumber, measurementNumber: addedMeasurement.measurementNumber, diff --git a/platform/core/src/measurements/classes/TimepointApi.js b/platform/core/src/measurements/classes/TimepointApi.js index 92af88051..6719ac003 100644 --- a/platform/core/src/measurements/classes/TimepointApi.js +++ b/platform/core/src/measurements/classes/TimepointApi.js @@ -55,7 +55,7 @@ export default class TimepointApi { }); const filteredTimepoints = sortedTimepoints.find( tp => - tp.patientId === timepoint.patientId && + tp.PatientID === timepoint.PatientID && tp.timepointType === timepoint.timepointType ); @@ -129,14 +129,14 @@ export default class TimepointApi { ); } - disassociateStudy(timepointIds, studyInstanceUid) { + disassociateStudy(timepointIds, StudyInstanceUID) { const disassociateFn = configuration.dataExchange.disassociate; if (typeof disassociateFn !== 'function') { log.error('Study disassociate function has not been configured.'); return; } - disassociateFn(timepointIds, studyInstanceUid).then(() => { + disassociateFn(timepointIds, StudyInstanceUID).then(() => { log.info('Disassociation completed'); this.timepoints = []; @@ -510,9 +510,9 @@ export default class TimepointApi { } // Return only the timepoints for the given study - study(studyInstanceUid) { + study(StudyInstanceUID) { return this.all().filter(timepoint => - timepoint.studyInstanceUids.includes(studyInstanceUid) + timepoint.studyInstanceUIDs.includes(StudyInstanceUID) ); } diff --git a/platform/core/src/measurements/conformance/ConformanceCriteria.js b/platform/core/src/measurements/conformance/ConformanceCriteria.js index b30532659..f600207eb 100644 --- a/platform/core/src/measurements/conformance/ConformanceCriteria.js +++ b/platform/core/src/measurements/conformance/ConformanceCriteria.js @@ -21,12 +21,12 @@ class ConformanceCriteria { this.options = options; } - loadStudy(studyInstanceUid) { + loadStudy(StudyInstanceUID) { if (typeof this.options.loadStudy !== 'function') { throw new Error('loadStudy callback is not defined'); } - return this.options.loadStudy(null, studyInstanceUid); + return this.options.loadStudy(null, StudyInstanceUID); } async validate(trialCriteriaType) { @@ -181,7 +181,7 @@ class ConformanceCriteria { const measurements = this.measurementApi.fetch(measurementType); measurements.forEach(measurement => { - const { studyInstanceUid } = measurement; + const { StudyInstanceUID } = measurement; const timepointId = measurement.timepointId; const timepoint = @@ -195,7 +195,7 @@ class ConformanceCriteria { return; } - const promise = this.loadStudy(studyInstanceUid); + const promise = this.loadStudy(StudyInstanceUID); promise.then( studyMetadata => { data[measurementType].push({ diff --git a/platform/core/src/measurements/conformance/criteria/MeasurementsLength.js b/platform/core/src/measurements/conformance/criteria/MeasurementsLength.js index 0ca2291b1..2cbbf4e5a 100644 --- a/platform/core/src/measurements/conformance/criteria/MeasurementsLength.js +++ b/platform/core/src/measurements/conformance/criteria/MeasurementsLength.js @@ -65,7 +65,7 @@ export const MeasurementsLengthSchema = { }, isNodal: { label: 'Filter to evaluate only nodal or extranodal measurements', - type: 'boolean' + type: 'boolean', }, message: { label: 'Message to be displayed in case of nonconformity', @@ -119,19 +119,24 @@ export class MeasurementsLengthCriterion extends BaseCriterion { shortestDiameter = (child && child.shortestDiameter) || 0; } - const { sliceThickness } = metadata; - const modality = (metadata.getRawValue('x00080060') || '').toUpperCase(); + const { SliceThickness } = metadata; - // Stop here if the measurement does not match the modality and location filters - if (typeof isNodal === 'boolean' && typeof options.isNodal === 'boolean' && options.isNodal !== isNodal) + const Modality = metadata.getTagValue('Modality') || ''; + + // Stop here if the measurement does not match the Modality and location filters + if ( + typeof isNodal === 'boolean' && + typeof options.isNodal === 'boolean' && + options.isNodal !== isNodal + ) return; if (options.locationIn && options.locationIn.indexOf(location) === -1) return; - if (options.modalityIn && options.modalityIn.indexOf(modality) === -1) + if (options.modalityIn && options.modalityIn.indexOf(Modality) === -1) return; if (options.locationNotIn && options.locationNotIn.indexOf(location) > -1) return; - if (options.modalityNotIn && options.modalityNotIn.indexOf(modality) > -1) + if (options.modalityNotIn && options.modalityNotIn.indexOf(Modality) > -1) return; // Check the measurement length @@ -139,11 +144,11 @@ export class MeasurementsLengthCriterion extends BaseCriterion { (options.longAxis && longestDiameter < options.longAxis) || (options.shortAxis && shortestDiameter < options.shortAxis) || (longMultiplier && - !isNaN(sliceThickness) && - longestDiameter < longMultiplier * sliceThickness) || + !isNaN(SliceThickness) && + longestDiameter < longMultiplier * SliceThickness) || (shortMultiplier && - !isNaN(sliceThickness) && - shortestDiameter < shortMultiplier * sliceThickness); + !isNaN(SliceThickness) && + shortestDiameter < shortMultiplier * SliceThickness); // Mark this measurement as invalid if some of the checks have failed if (failed) { diff --git a/platform/core/src/measurements/conformance/criteria/Modality.js b/platform/core/src/measurements/conformance/criteria/Modality.js index 64bf480d7..2f4079928 100644 --- a/platform/core/src/measurements/conformance/criteria/Modality.js +++ b/platform/core/src/measurements/conformance/criteria/Modality.js @@ -32,7 +32,7 @@ export const ModalitySchema = { /* * ModalityCriteria - * Check if a modality is allowed or denied + * Check if a Modality is allowed or denied * Options: * method (string): Specify if it\'s goinig to "allow" or "deny" the modalities * measurementTypes (string[]): List of measurement types that will be evaluated @@ -56,16 +56,14 @@ export class ModalityCriterion extends BaseCriterion { items.forEach(item => { const { measurement, metadata } = item; - const modality = ( - metadata.getRawValue('x00080060') || '' - ).toUpperCase(); + const Modality = metadata.getTagValue('Modality') || ''; if ( - (validationMethod === 'allow' && !modalitiesSet.has(modality)) || - (validationMethod === 'deny' && modalitiesSet.has(modality)) + (validationMethod === 'allow' && !modalitiesSet.has(Modality)) || + (validationMethod === 'deny' && modalitiesSet.has(Modality)) ) { measurements.push(measurement); - invalidModalities.add(modality); + invalidModalities.add(Modality); } }); }); @@ -74,7 +72,7 @@ export class ModalityCriterion extends BaseCriterion { const uniqueModalities = Array.from(invalidModalities); const uniqueModalitiesText = uniqueModalities.join(', '); const modalityText = - uniqueModalities.length > 1 ? 'modalities' : 'modality'; + uniqueModalities.length > 1 ? 'modalities' : 'Modality'; message = `The ${modalityText} ${uniqueModalitiesText} should not be used as a method of measurement`; } diff --git a/platform/core/src/measurements/dataExchange.js b/platform/core/src/measurements/dataExchange.js index 0af694d4d..c584c30d9 100644 --- a/platform/core/src/measurements/dataExchange.js +++ b/platform/core/src/measurements/dataExchange.js @@ -1,6 +1,6 @@ import log from '../log'; -export const retrieveMeasurements = (patientId, timepointIds) => { +export const retrieveMeasurements = (PatientID, timepointIds) => { log.error('retrieveMeasurements'); return Promise.resolve(); }; @@ -30,7 +30,7 @@ export const removeTimepoint = timepointId => { return Promise.resolve(); }; -export const disassociateStudy = (timepointIds, studyInstanceUid) => { +export const disassociateStudy = (timepointIds, StudyInstanceUID) => { log.error('disassociateStudy'); return Promise.resolve(); }; diff --git a/platform/core/src/measurements/lib/getImageAttributes.js b/platform/core/src/measurements/lib/getImageAttributes.js index caa804e4d..e2759a675 100644 --- a/platform/core/src/measurements/lib/getImageAttributes.js +++ b/platform/core/src/measurements/lib/getImageAttributes.js @@ -5,32 +5,30 @@ export default function(element) { const enabledElement = cornerstone.getEnabledElement(element); const imageId = enabledElement.image.imageId; - // Get studyInstanceUid & patientId - const study = cornerstone.metaData.get('study', imageId); - const studyInstanceUid = study.studyInstanceUid; - const patientId = study.patientId; + // Get StudyInstanceUID & PatientID + const { + StudyInstanceUID, + PatientID, + SeriesInstanceUID, + SOPInstanceUID, + } = cornerstone.metaData.get('instance', imageId); - // Get seriesInstanceUid - const series = cornerstone.metaData.get('series', imageId); - const seriesInstanceUid = series.seriesInstanceUid; - - // Get sopInstanceUid - const sopInstance = cornerstone.metaData.get('instance', imageId); - const sopInstanceUid = sopInstance.sopInstanceUid; - const frameIndex = sopInstance.frame || 0; + const splitImageId = imageId.split('&frame'); + const frameIndex = + splitImageId[1] !== undefined ? Number(splitImageId[1]) : 0; const imagePath = [ - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, frameIndex, ].join('_'); return { - patientId, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + PatientID, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, frameIndex, imagePath, }; diff --git a/platform/core/src/measurements/lib/getImageIdForImagePath.js b/platform/core/src/measurements/lib/getImageIdForImagePath.js index 943e0c72a..953811d5d 100644 --- a/platform/core/src/measurements/lib/getImageIdForImagePath.js +++ b/platform/core/src/measurements/lib/getImageIdForImagePath.js @@ -2,13 +2,13 @@ import studyMetadataManager from '../../utils/studyMetadataManager'; export default function(imagePath, thumbnail = false) { const [ - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, frameIndex, ] = imagePath.split('_'); - const studyMetadata = studyMetadataManager.get(studyInstanceUid); - const series = studyMetadata.getSeriesByUID(seriesInstanceUid); - const instance = series.getInstanceByUID(sopInstanceUid); + const studyMetadata = studyMetadataManager.get(StudyInstanceUID); + const series = studyMetadata.getSeriesByUID(SeriesInstanceUID); + const instance = series.getInstanceByUID(SOPInstanceUID); return instance.getImageId(frameIndex, thumbnail); } diff --git a/platform/core/src/measurements/measurementHandlers/handleChildMeasurementAdded.js b/platform/core/src/measurements/measurementHandlers/handleChildMeasurementAdded.js index bdb7d5d48..35955e7a4 100644 --- a/platform/core/src/measurements/measurementHandlers/handleChildMeasurementAdded.js +++ b/platform/core/src/measurements/measurementHandlers/handleChildMeasurementAdded.js @@ -38,7 +38,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) { const parentMeasurement = collection.find( t => t.toolType === tool.parentTool && - t.patientId === imageAttributes.patientId && + t.PatientID === imageAttributes.PatientID && t[tool.attribute] === null ); @@ -63,8 +63,8 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) { toolType: tool.parentTool, lesionNamingNumber: measurementData.lesionNamingNumber, userId: user.getUserId(), - patientId: imageAttributes.patientId, - studyInstanceUid: imageAttributes.studyInstanceUid, + PatientID: imageAttributes.PatientID, + StudyInstanceUID: imageAttributes.StudyInstanceUID, }; measurement[tool.attribute] = Object.assign( diff --git a/platform/core/src/redux/actions.js b/platform/core/src/redux/actions.js index 259e91ca1..1e46a9c4d 100644 --- a/platform/core/src/redux/actions.js +++ b/platform/core/src/redux/actions.js @@ -110,9 +110,9 @@ export const setMeasurements = state => ({ state, }); -export const setStudyData = (studyInstanceUid, data) => ({ +export const setStudyData = (StudyInstanceUID, data) => ({ type: 'SET_STUDY_DATA', - studyInstanceUid, + StudyInstanceUID, data, }); diff --git a/platform/core/src/redux/actions.test.js b/platform/core/src/redux/actions.test.js index 79a47a11a..e80297a66 100644 --- a/platform/core/src/redux/actions.test.js +++ b/platform/core/src/redux/actions.test.js @@ -29,20 +29,20 @@ describe('actions', () => { describe('viewport action creators', () => { it('should create an action to set the viewport specific data', () => { const viewportSpecificData = { - displaySetInstanceUid: 'ef859a23-4631-93ab-d26b-7940a822c699', - seriesDate: '20151026', - seriesTime: '082611.370000', - seriesInstanceUid: + displaySetInstanceUID: 'ef859a23-4631-93ab-d26b-7940a822c699', + SeriesDate: '20151026', + SeriesTime: '082611.370000', + SeriesInstanceUID: '1.3.6.1.4.1.25403.345050719074.3824.20170126085406.5', - seriesNumber: 2, - seriesDescription: 'Chest 3x3 Soft', + SeriesNumber: 2, + SeriesDescription: 'Chest 3x3 Soft', numImageFrames: 126, - modality: 'CT', + Modality: 'CT', isMultiFrame: false, - instanceNumber: 1, - studyInstanceUid: + InstanceNumber: 1, + StudyInstanceUID: '1.3.6.1.4.1.25403.345050719074.3824.20170126085406.1', - sopClassUids: ['1.2.840.10008.5.1.4.1.1.2'], + sopClassUIDs: ['1.2.840.10008.5.1.4.1.1.2'], plugin: 'cornerstone', viewport: { zoomScale: null, diff --git a/platform/core/src/redux/reducers/studies.js b/platform/core/src/redux/reducers/studies.js index 3bd61aef2..b2545a281 100644 --- a/platform/core/src/redux/reducers/studies.js +++ b/platform/core/src/redux/reducers/studies.js @@ -8,7 +8,7 @@ const servers = (state = defaultState, action) => { switch (action.type) { case 'SET_STUDY_DATA': const updatedStudyData = cloneDeep(state).studyData; - updatedStudyData[action.studyInstanceUid] = action.data; + updatedStudyData[action.StudyInstanceUID] = action.data; return Object.assign({}, state, { studyData: updatedStudyData }); default: diff --git a/platform/core/src/redux/reducers/viewports.js b/platform/core/src/redux/reducers/viewports.js index e96444f09..5396e226c 100644 --- a/platform/core/src/redux/reducers/viewports.js +++ b/platform/core/src/redux/reducers/viewports.js @@ -22,7 +22,7 @@ export const DEFAULT_STATE = { }; /** - * Take the new number of rows and columns, delete all not used viewport data and also set + * Take the new number of Rows and Columns, delete all not used viewport data and also set * active viewport as default in case current one is not available anymore. * * @param {Number} numRows @@ -49,7 +49,7 @@ const findActiveViewportSpecificData = ( return viewportSpecificData; }; /** - * Take new number of rows and columns and make sure the current active viewport index is still available, if not, return the default + * Take new number of Rows and Columns and make sure the current active viewport index is still available, if not, return the default * * @param {Number} numRows * @param {Number} numColumns diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index 3e0f1a5e6..a21ea8837 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -15,8 +15,8 @@ import guid from '../../utils/guid'; * * @typedef {Object} Measurement * @property {number} id - - * @property {string} sopInstanceUID - - * @property {string} frameOfReferenceUID - + * @property {string} sopInstanceUid - + * @property {string} FrameOfReferenceUID - * @property {string} referenceSeriesUID - * @property {string} label - * @property {string} description - @@ -30,8 +30,8 @@ import guid from '../../utils/guid'; /* Measurement schema keys for object validation. */ const MEASUREMENT_SCHEMA_KEYS = [ 'id', - 'sopInstanceUID', - 'frameOfReferenceUID', + 'SOPInstanceUID', + 'FrameOfReferenceUID', 'referenceSeriesUID', 'label', 'description', @@ -197,7 +197,11 @@ class MeasurementService { this.mappings[source.id] = [mapping]; } - log.info(`New measurement mapping added to source '${this._getSourceInfo(source)}'.`); + log.info( + `New measurement mapping added to source '${this._getSourceInfo( + source + )}'.` + ); } /** @@ -219,11 +223,18 @@ class MeasurementService { return; } - const mapping = this._getMappingByMeasurementSource(measurementId, definition); + const mapping = this._getMappingByMeasurementSource( + measurementId, + definition + ); if (mapping) return mapping.toSourceSchema(measurement, definition); const measurement = this.getMeasurement(measurementId); - const matchingMapping = this._getMatchingMapping(source, definition, measurement); + const matchingMapping = this._getMatchingMapping( + source, + definition, + measurement + ); if (matchingMapping) { log.info('Matching mapping found:', matchingMapping); @@ -249,12 +260,15 @@ class MeasurementService { const sourceInfo = this._getSourceInfo(source); if (!definition) { + console.log('TEST'); log.warn('No source definition provided. Exiting early.'); return; } if (!this._sourceHasMappings(source)) { - log.warn(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`); + log.warn( + `No measurement mappings found for '${sourceInfo}' source. Exiting early.` + ); return; } @@ -271,7 +285,10 @@ class MeasurementService { /* Assign measurement source instance */ measurement.source = source; } catch (error) { - log.warn(`Failed to map '${sourceInfo}' measurement for definition ${definition}:`, error.message); + log.warn( + `Failed to map '${sourceInfo}' measurement for definition ${definition}:`, + error.message + ); return; } @@ -295,13 +312,24 @@ class MeasurementService { }; if (this.measurements[internalId]) { - log.info(`Measurement already defined. Updating measurement.`, newMeasurement); + log.info( + `Measurement already defined. Updating measurement.`, + newMeasurement + ); this.measurements[internalId] = newMeasurement; - this._broadcastChange(this.EVENTS.MEASUREMENT_UPDATED, source, newMeasurement); + this._broadcastChange( + this.EVENTS.MEASUREMENT_UPDATED, + source, + newMeasurement + ); } else { log.info(`Measurement added.`, newMeasurement); this.measurements[internalId] = newMeasurement; - this._broadcastChange(this.EVENTS.MEASUREMENT_ADDED, source, newMeasurement); + this._broadcastChange( + this.EVENTS.MEASUREMENT_ADDED, + source, + newMeasurement + ); } return newMeasurement.id; diff --git a/platform/core/src/services/MeasurementService/MeasurementService.test.js b/platform/core/src/services/MeasurementService/MeasurementService.test.js index 05b23a8ad..5b24b54a0 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.test.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.test.js @@ -26,8 +26,8 @@ describe('MeasurementService.js', () => { measurementData: {}, }; measurement = { - sopInstanceUID: '123', - frameOfReferenceUID: '1234', + SOPInstanceUID: '123', + FrameOfReferenceUID: '1234', referenceSeriesUID: '12345', label: 'Label', description: 'Description', @@ -203,7 +203,6 @@ describe('MeasurementService.js', () => { const measurements = measurementService.getMeasurements(); expect(measurements.length).toEqual(2); - expect(measurements.length).toEqual(2); }); }); @@ -278,7 +277,9 @@ describe('MeasurementService.js', () => { /* Add new measurement */ source.addOrUpdate(definition, newMeasurement); - const savedMeasurement = measurementService.getMeasurement(newMeasurement.id); + const savedMeasurement = measurementService.getMeasurement( + newMeasurement.id + ); /* Clear dynamic data */ delete newMeasurement.modifiedTimestamp; diff --git a/platform/core/src/studies/getSeriesInfo.js b/platform/core/src/studies/getSeriesInfo.js index e8b7d40ea..347d9fa18 100644 --- a/platform/core/src/studies/getSeriesInfo.js +++ b/platform/core/src/studies/getSeriesInfo.js @@ -12,12 +12,12 @@ const INFO = Symbol('INFO'); export default function getSeriesInfo(series) { let info = series[INFO]; if (!info) { - const modality = DICOMWeb.getString(series['00080060'], '').toUpperCase(); + const Modality = DICOMWeb.getString(series['00080060'], '').toUpperCase(); info = Object.freeze({ - modality, - isLowPriority: isLowPriorityModality(modality), - seriesInstanceUid: DICOMWeb.getString(series['0020000E']), - seriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0, + Modality, + isLowPriority: isLowPriorityModality(Modality), + SeriesInstanceUID: DICOMWeb.getString(series['0020000E']), + SeriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0, }); series[INFO] = info; } diff --git a/platform/core/src/studies/getStudyBoxData.js b/platform/core/src/studies/getStudyBoxData.js index a67b047ad..416838a06 100644 --- a/platform/core/src/studies/getStudyBoxData.js +++ b/platform/core/src/studies/getStudyBoxData.js @@ -2,13 +2,13 @@ * Overridable namespace to allow getting study boxes data externally. * * The function must handle the first parameter as a studyInformation object containing at least the - * studyInstanceUid attribute. + * StudyInstanceUID attribute. * * Shall return a promise that will be resolved with an object containing those attributes: - * - studyInstanceUid {String}: copy of studyInformation.studyInstanceUid - * - modalities {String}: 2 uppercase letters for each modality split by any non-alphabetical char(s) - * - studyDate {String}: date formatted as YYYYMMDD - * - studyDescription {String}: study description string + * - StudyInstanceUID {String}: copy of studyInformation.StudyInstanceUID + * - modalities {String}: 2 uppercase letters for each Modality split by any non-alphabetical char(s) + * - StudyDate {String}: date formatted as YYYYMMDD + * - StudyDescription {String}: study description string */ // TODO: What is this for? const getStudyBoxData = false; diff --git a/platform/core/src/studies/retrieveStudiesMetadata.js b/platform/core/src/studies/retrieveStudiesMetadata.js index 341e93394..ebed831cb 100644 --- a/platform/core/src/studies/retrieveStudiesMetadata.js +++ b/platform/core/src/studies/retrieveStudiesMetadata.js @@ -8,23 +8,23 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata'; * and waits for all of the results to be returned. * * @param {Object} server Object with server configuration parameters - * @param {Array} studyInstanceUids The UIDs of the Studies to be retrieved + * @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against * @returns {Promise} that will be resolved with the metadata or rejected with the error */ export default function retrieveStudiesMetadata( server, - studyInstanceUids, + studyInstanceUIDs, filters ) { // Create an empty array to store the Promises for each metaData retrieval call const promises = []; - // Loop through the array of studyInstanceUids - studyInstanceUids.forEach(function(studyInstanceUid) { + // Loop through the array of studyInstanceUIDs + studyInstanceUIDs.forEach(function(StudyInstanceUID) { // Send the call and resolve or reject the related promise based on its outcome - const promise = retrieveStudyMetadata(server, studyInstanceUid, filters); + const promise = retrieveStudyMetadata(server, StudyInstanceUID, filters); // Add the current promise to the array of promises promises.push(promise); diff --git a/platform/core/src/studies/retrieveStudyMetadata.js b/platform/core/src/studies/retrieveStudyMetadata.js index 58699efff..b2d84be9e 100644 --- a/platform/core/src/studies/retrieveStudyMetadata.js +++ b/platform/core/src/studies/retrieveStudyMetadata.js @@ -8,12 +8,12 @@ const StudyMetaDataPromises = new Map(); * Retrieves study metadata * * @param {Object} server Object with server configuration parameters - * @param {string} studyInstanceUid The UID of the Study to be retrieved + * @param {string} StudyInstanceUID The UID of the Study to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against * @returns {Promise} that will be resolved with the metadata or rejected with the error */ -export function retrieveStudyMetadata(server, studyInstanceUid, filters) { +export function retrieveStudyMetadata(server, StudyInstanceUID, filters) { // @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all // and further requests for that metadata will always fail. On failure, we probably need to remove the // corresponding promise from the "StudyMetaDataPromises" map... @@ -21,26 +21,26 @@ export function retrieveStudyMetadata(server, studyInstanceUid, filters) { if (!server) { throw new Error(`${moduleName}: Required 'server' parameter not provided.`); } - if (!studyInstanceUid) { + if (!StudyInstanceUID) { throw new Error( - `${moduleName}: Required 'studyInstanceUid' parameter not provided.` + `${moduleName}: Required 'StudyInstanceUID' parameter not provided.` ); } // Already waiting on result? Return cached promise - if (StudyMetaDataPromises.has(studyInstanceUid)) { - return StudyMetaDataPromises.get(studyInstanceUid); + if (StudyMetaDataPromises.has(StudyInstanceUID)) { + return StudyMetaDataPromises.get(StudyInstanceUID); } // Create a promise to handle the data retrieval const promise = new Promise((resolve, reject) => { - RetrieveMetadata(server, studyInstanceUid, filters).then(function(data) { + RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) { resolve(data); }, reject); }); // Store the promise in cache - StudyMetaDataPromises.set(studyInstanceUid, promise); + StudyMetaDataPromises.set(StudyInstanceUID, promise); return promise; } @@ -49,11 +49,11 @@ export function retrieveStudyMetadata(server, studyInstanceUid, filters) { * Delete the cached study metadata retrieval promise to ensure that the browser will * re-retrieve the study metadata when it is next requested * - * @param {String} studyInstanceUid The UID of the Study to be removed from cache + * @param {String} StudyInstanceUID The UID of the Study to be removed from cache * */ -export function deleteStudyMetadataPromise(studyInstanceUid) { - if (StudyMetaDataPromises.has(studyInstanceUid)) { - StudyMetaDataPromises.delete(studyInstanceUid); +export function deleteStudyMetadataPromise(StudyInstanceUID) { + if (StudyMetaDataPromises.has(StudyInstanceUID)) { + StudyMetaDataPromises.delete(StudyInstanceUID); } } diff --git a/platform/core/src/studies/retrieveStudyMetadata.test.js b/platform/core/src/studies/retrieveStudyMetadata.test.js index 4c2718587..4725ab562 100644 --- a/platform/core/src/studies/retrieveStudyMetadata.test.js +++ b/platform/core/src/studies/retrieveStudyMetadata.test.js @@ -12,12 +12,12 @@ describe('retrieveStudyMetadata.js', () => { expect(callWithNoServer).toThrow(Error); }); - it('throws an exception if no studyInstanceUid parameter is provided', () => { - const callWithNoStudyInstanceUid = () => { + it('throws an exception if no StudyInstanceUID parameter is provided', () => { + const callWithNoStudyInstanceUID = () => { retrieveStudyMetadata(fakeDicomWebServer, null); }; - expect(callWithNoStudyInstanceUid).toThrow(Error); + expect(callWithNoStudyInstanceUID).toThrow(Error); }); it('caches and returns the same promise for identical studyInstanceUIDs', () => { diff --git a/platform/core/src/studies/services/qido/instances.js b/platform/core/src/studies/services/qido/instances.js index 76d581bbb..105baefef 100644 --- a/platform/core/src/studies/services/qido/instances.js +++ b/platform/core/src/studies/services/qido/instances.js @@ -6,33 +6,33 @@ import { api } from 'dicomweb-client'; * an array of series that are present in the study * * @param server The DICOM server - * @param studyInstanceUid + * @param StudyInstanceUID * @param resultData * @returns {Array} Series List */ -function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { +function resultDataToStudyMetadata(server, StudyInstanceUID, resultData) { const seriesMap = {}; - const seriesList = []; + const series = []; resultData.forEach(function(instance) { // Use seriesMap to cache series data // If the series instance UID has already been used to // process series data, continue using that series - var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']); - var series = seriesMap[seriesInstanceUid]; + var SeriesInstanceUID = DICOMWeb.getString(instance['0020000E']); + var series = seriesMap[SeriesInstanceUID]; // If no series data exists in the seriesMap cache variable, // process any available series data if (!series) { series = { - seriesInstanceUid: seriesInstanceUid, - seriesNumber: DICOMWeb.getString(instance['00200011']), + SeriesInstanceUID: SeriesInstanceUID, + SeriesNumber: DICOMWeb.getString(instance['00200011']), instances: [], }; // Save this data in the seriesMap cache variable - seriesMap[seriesInstanceUid] = series; - seriesList.push(series); + seriesMap[SeriesInstanceUID] = series; + series.push(series); } // The uri for the dicomweb @@ -44,36 +44,36 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { // manually create a WADO-URI from the UIDs // NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured? - var sopInstanceUid = DICOMWeb.getString(instance['00080018']); + var SOPInstanceUID = DICOMWeb.getString(instance['00080018']); var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + - studyInstanceUid + + StudyInstanceUID + '&seriesUID=' + - seriesInstanceUid + + SeriesInstanceUID + '&objectUID=' + - sopInstanceUid + + SOPInstanceUID + '&contentType=application%2Fdicom'; // Add this instance to the current series series.instances.push({ - sopClassUid: DICOMWeb.getString(instance['00080016']), - sopInstanceUid: sopInstanceUid, + SOPClassUID: DICOMWeb.getString(instance['00080016']), + SOPInstanceUID: SOPInstanceUID, uri: uri, - instanceNumber: DICOMWeb.getString(instance['00200013']), + InstanceNumber: DICOMWeb.getString(instance['00200013']), }); }); - return seriesList; + return series; } /** * Retrieve a set of instances using a QIDO call * @param server - * @param studyInstanceUid + * @param StudyInstanceUID * @throws ECONNREFUSED - * @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}} + * @returns {{wadoUriRoot: String, StudyInstanceUID: String, series: Array}} */ -export default function Instances(server, studyInstanceUid) { +export default function Instances(server, StudyInstanceUID) { // TODO: Are we using this function anywhere?? Can we remove it? const config = { @@ -86,18 +86,14 @@ export default function Instances(server, studyInstanceUid) { server.qidoSupportsIncludeField ); const options = { - studyInstanceUID: studyInstanceUid, + studyInstanceUID: StudyInstanceUID, }; return dicomWeb.searchForInstances(options).then(result => { return { wadoUriRoot: server.wadoUriRoot, - studyInstanceUid: studyInstanceUid, - seriesList: resultDataToStudyMetadata( - server, - studyInstanceUid, - result.data - ), + StudyInstanceUID: StudyInstanceUID, + series: resultDataToStudyMetadata(server, StudyInstanceUID, result.data), }; }); } diff --git a/platform/core/src/studies/services/qido/studies.js b/platform/core/src/studies/services/qido/studies.js index 01d325390..7a798855a 100644 --- a/platform/core/src/studies/services/qido/studies.js +++ b/platform/core/src/studies/services/qido/studies.js @@ -35,11 +35,11 @@ function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) { ].join(','); const parameters = { - PatientName: filter.patientName, - PatientID: filter.patientId, - AccessionNumber: filter.accessionNumber, - StudyDescription: filter.studyDescription, - ModalitiesInStudy: filter.modalitiesInStudy, + PatientName: filter.PatientName, + PatientID: filter.PatientID, + AccessionNumber: filter.AccessionNumber, + StudyDescription: filter.StudyDescription, + ModalitiesInStudy: filter.ModalitiesInStudy, limit: filter.limit, offset: filter.offset, fuzzymatching: filter.fuzzymatching, @@ -54,11 +54,11 @@ function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) { } // Build the StudyInstanceUID parameter - if (filter.studyInstanceUid) { - let studyUids = filter.studyInstanceUid; - studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids; - studyUids = studyUids.replace(/[^0-9.]+/g, '\\'); - parameters.StudyInstanceUID = studyUids; + if (filter.StudyInstanceUID) { + let studyUIDs = filter.StudyInstanceUID; + studyUIDs = Array.isArray(studyUIDs) ? studyUIDs.join() : studyUIDs; + studyUIDs = studyUIDs.replace(/[^0-9.]+/g, '\\'); + parameters.StudyInstanceUID = studyUIDs; } // Clean query params of undefined values. @@ -85,23 +85,23 @@ function resultDataToStudies(resultData) { resultData.forEach(study => studies.push({ - studyInstanceUid: DICOMWeb.getString(study['0020000D']), + StudyInstanceUID: DICOMWeb.getString(study['0020000D']), // 00080005 = SpecificCharacterSet - studyDate: DICOMWeb.getString(study['00080020']), - studyTime: DICOMWeb.getString(study['00080030']), - accessionNumber: DICOMWeb.getString(study['00080050']), + StudyDate: DICOMWeb.getString(study['00080020']), + StudyTime: DICOMWeb.getString(study['00080030']), + AccessionNumber: DICOMWeb.getString(study['00080050']), referringPhysicianName: DICOMWeb.getString(study['00080090']), // 00081190 = URL - patientName: DICOMWeb.getName(study['00100010']), - patientId: DICOMWeb.getString(study['00100020']), - patientBirthdate: DICOMWeb.getString(study['00100030']), + PatientName: DICOMWeb.getName(study['00100010']), + PatientID: DICOMWeb.getString(study['00100020']), + PatientBirthdate: DICOMWeb.getString(study['00100030']), patientSex: DICOMWeb.getString(study['00100040']), studyId: DICOMWeb.getString(study['00200010']), numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']), numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']), - studyDescription: DICOMWeb.getString(study['00081030']), - // modality: DICOMWeb.getString(study['00080060']), - // modalitiesInStudy: DICOMWeb.getString(study['00080061']), + StudyDescription: DICOMWeb.getString(study['00081030']), + // Modality: DICOMWeb.getString(study['00080060']), + // ModalitiesInStudy: DICOMWeb.getString(study['00080061']), modalities: DICOMWeb.getString( DICOMWeb.getModalities(study['00080060'], study['00080061']) ), diff --git a/platform/core/src/studies/services/wado/retrieveMetadata.js b/platform/core/src/studies/services/wado/retrieveMetadata.js index 1cb9d886e..f3c544450 100644 --- a/platform/core/src/studies/services/wado/retrieveMetadata.js +++ b/platform/core/src/studies/services/wado/retrieveMetadata.js @@ -6,12 +6,12 @@ import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync'; * will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed. * * @param {Object} server Object with server configuration parameters - * @param {string} studyInstanceUid The Study Instance UID of the study which needs to be loaded + * @param {string} StudyInstanceUID The Study Instance UID of the study which needs to be loaded * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against * @returns {Object} A study descriptor object */ -async function RetrieveMetadata(server, studyInstanceUid, filters = {}) { +async function RetrieveMetadata(server, StudyInstanceUID, filters = {}) { const RetrieveMetadataLoader = server.enableStudyLazyLoad != false ? RetrieveMetadataLoaderAsync @@ -19,7 +19,7 @@ async function RetrieveMetadata(server, studyInstanceUid, filters = {}) { const retrieveMetadataLoader = new RetrieveMetadataLoader( server, - studyInstanceUid, + StudyInstanceUID, filters ); const studyMetadata = retrieveMetadataLoader.execLoad(); diff --git a/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js b/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js index 69594ccf9..2f224274b 100644 --- a/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js +++ b/platform/core/src/studies/services/wado/retrieveMetadataLoaderAsync.js @@ -9,12 +9,12 @@ import { } from './studyInstanceHelpers'; /** - * Map seriesList to an array of seriesInstanceUid - * @param {Arrays} seriesList list of Series Instance UIDs + * Map series to an array of SeriesInstanceUID + * @param {Arrays} series list of Series Instance UIDs * @returns {Arrays} A list of Series Instance UIDs */ -function mapStudySeries(seriesList) { - return seriesList.map(series => getSeriesInfo(series).seriesInstanceUid); +function mapStudySeries(series) { + return series.map(series => getSeriesInfo(series).SeriesInstanceUID); } function attachSeriesLoader(server, study, seriesLoader) { @@ -107,9 +107,9 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader result, sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria ); - const seriesInstanceUidsMap = mapStudySeries(seriesSorted); + const seriesInstanceUIDsMap = mapStudySeries(seriesSorted); - return seriesInstanceUidsMap; + return seriesInstanceUIDsMap; } async load(preLoadData) { diff --git a/platform/core/src/studies/services/wado/studyInstanceHelpers.js b/platform/core/src/studies/services/wado/studyInstanceHelpers.js index f4928a12f..cd6e253ef 100644 --- a/platform/core/src/studies/services/wado/studyInstanceHelpers.js +++ b/platform/core/src/studies/services/wado/studyInstanceHelpers.js @@ -1,63 +1,7 @@ -import { api } from 'dicomweb-client'; import DICOMWeb from '../../../DICOMWeb'; - -const WADOProxy = { - convertURL: (url, server) => { - // TODO: Remove all WADOProxy stuff from this file - return url; - }, -}; -function parseFloatArray(obj) { - const result = []; - - if (!obj) { - return result; - } - - const objs = obj.split('\\'); - for (let i = 0; i < objs.length; i++) { - result.push(parseFloat(objs[i])); - } - - return result; -} - -/** - * Simple cache schema for retrieved color palettes. - */ -const paletteColorCache = { - count: 0, - maxAge: 24 * 60 * 60 * 1000, // 24h cache? - entries: {}, - isValidUID: function(paletteUID) { - return typeof paletteUID === 'string' && paletteUID.length > 0; - }, - get: function(paletteUID) { - let entry = null; - if (this.entries.hasOwnProperty(paletteUID)) { - entry = this.entries[paletteUID]; - // check how the entry is... - if (Date.now() - entry.time > this.maxAge) { - // entry is too old... remove entry. - delete this.entries[paletteUID]; - this.count--; - entry = null; - } - } - return entry; - }, - add: function(entry) { - if (this.isValidUID(entry.uid)) { - let paletteUID = entry.uid; - if (this.entries.hasOwnProperty(paletteUID) !== true) { - this.count++; // increment cache entry count... - } - entry.time = Date.now(); - this.entries[paletteUID] = entry; - // @TODO: Add logic to get rid of old entries and reduce memory usage... - } - }, -}; +import metadataProvider from '../../../classes/MetadataProvider'; +import getWADORSImageId from '../../../utils/getWADORSImageId'; +import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; /** * Create a plain JS object that describes a study (a study descriptor object) @@ -67,47 +11,47 @@ const paletteColorCache = { function createStudy(server, aSopInstance) { // TODO: Pass a reference ID to the server instead of including the URLs here return { - seriesList: [], + series: [], seriesMap: Object.create(null), seriesLoader: null, wadoUriRoot: server.wadoUriRoot, wadoRoot: server.wadoRoot, qidoRoot: server.qidoRoot, - patientName: DICOMWeb.getName(aSopInstance['00100010']), - patientId: DICOMWeb.getString(aSopInstance['00100020']), - patientAge: DICOMWeb.getNumber(aSopInstance['00101010']), - patientSize: DICOMWeb.getNumber(aSopInstance['00101020']), - patientWeight: DICOMWeb.getNumber(aSopInstance['00101030']), - accessionNumber: DICOMWeb.getString(aSopInstance['00080050']), - studyDate: DICOMWeb.getString(aSopInstance['00080020']), - modalities: DICOMWeb.getString(aSopInstance['00080061']), - studyDescription: DICOMWeb.getString(aSopInstance['00081030']), - imageCount: DICOMWeb.getString(aSopInstance['00201208']), - studyInstanceUid: DICOMWeb.getString(aSopInstance['0020000D']), - institutionName: DICOMWeb.getString(aSopInstance['00080080']), + PatientName: DICOMWeb.getName(aSopInstance['00100010']), + PatientID: DICOMWeb.getString(aSopInstance['00100020']), + PatientAge: DICOMWeb.getNumber(aSopInstance['00101010']), + PatientSize: DICOMWeb.getNumber(aSopInstance['00101020']), + PatientWeight: DICOMWeb.getNumber(aSopInstance['00101030']), + AccessionNumber: DICOMWeb.getString(aSopInstance['00080050']), + StudyDate: DICOMWeb.getString(aSopInstance['00080020']), + modalities: DICOMWeb.getString(aSopInstance['00080061']), // TODO -> Rename this.. it'll take a while to not mess this one up. + StudyDescription: DICOMWeb.getString(aSopInstance['00081030']), + NumberOfStudyRelatedInstances: DICOMWeb.getString(aSopInstance['00201208']), + StudyInstanceUID: DICOMWeb.getString(aSopInstance['0020000D']), + InstitutionName: DICOMWeb.getString(aSopInstance['00080080']), }; } /** Returns a WADO url for an instance * - * @param studyInstanceUid - * @param seriesInstanceUid - * @param sopInstanceUid + * @param StudyInstanceUID + * @param SeriesInstanceUID + * @param SOPInstanceUID * @returns {string} */ function buildInstanceWadoUrl( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ) { // TODO: This can be removed, since DICOMWebClient has the same function. Not urgent, though const params = []; params.push('requestType=WADO'); - params.push(`studyUID=${studyInstanceUid}`); - params.push(`seriesUID=${seriesInstanceUid}`); - params.push(`objectUID=${sopInstanceUid}`); + params.push(`studyUID=${StudyInstanceUID}`); + params.push(`seriesUID=${SeriesInstanceUID}`); + params.push(`objectUID=${SOPInstanceUID}`); params.push('contentType=application/dicom'); params.push('transferSyntax=*'); @@ -118,342 +62,120 @@ function buildInstanceWadoUrl( function buildInstanceWadoRsUri( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ) { - return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}`; + return `${server.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}`; } function buildInstanceFrameWadoRsUri( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, frame ) { const baseWadoRsUri = buildInstanceWadoRsUri( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ); frame = frame != null || 1; return `${baseWadoRsUri}/frames/${frame}`; } -function getFrameIncrementPointer(element) { - const frameIncrementPointerNames = { - '00181065': 'frameTimeVector', - '00181063': 'frameTime', - }; - - if (!element || !element.Value || !element.Value.length) { - return; - } - - const value = element.Value[0]; - return frameIncrementPointerNames[value]; -} - -function getRadiopharmaceuticalInfo(instance) { - const modality = DICOMWeb.getString(instance['00080060']); - - if (modality !== 'PT') { - return; - } - - const radiopharmaceuticalInfo = instance['00540016']; - if ( - radiopharmaceuticalInfo === undefined || - !radiopharmaceuticalInfo.Value || - !radiopharmaceuticalInfo.Value.length - ) { - return; - } - - const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0]; - return { - radiopharmaceuticalStartTime: DICOMWeb.getString( - firstPetRadiopharmaceuticalInfo['00181072'] - ), - radionuclideTotalDose: DICOMWeb.getNumber( - firstPetRadiopharmaceuticalInfo['00181074'] - ), - radionuclideHalfLife: DICOMWeb.getNumber( - firstPetRadiopharmaceuticalInfo['00181075'] - ), - }; -} - -/** - * Parses the SourceImageSequence, if it exists, in order - * to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID - * is used to refer to this image in any accompanying DICOM-SR documents. - * - * @param instance - * @returns {String} The ReferenceSOPInstanceUID - */ -function getSourceImageInstanceUid(instance) { - // TODO= Parse the whole Source Image Sequence - // This is a really poor workaround for now. - // Later we should probably parse the whole sequence. - var SourceImageSequence = instance['00082112']; - if ( - SourceImageSequence && - SourceImageSequence.Value && - SourceImageSequence.Value.length && - SourceImageSequence.Value[0]['00081155'].Value - ) { - return SourceImageSequence.Value[0]['00081155'].Value[0]; - } -} - async function makeSOPInstance(server, study, instance) { - const { studyInstanceUid } = study; - const seriesInstanceUid = DICOMWeb.getString(instance['0020000E']); - let series = study.seriesMap[seriesInstanceUid]; + const naturalizedInstance = await metadataProvider.addInstance(instance, { + server, + }); + + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + } = naturalizedInstance; + + let series = study.seriesMap[SeriesInstanceUID]; if (!series) { series = { - seriesInstanceUid, - seriesDescription: DICOMWeb.getString(instance['0008103E']), - modality: DICOMWeb.getString(instance['00080060']), - seriesNumber: DICOMWeb.getNumber(instance['00200011']), - seriesDate: DICOMWeb.getString(instance['00080021']), - seriesTime: DICOMWeb.getString(instance['00080031']), + SeriesInstanceUID, + SeriesDescription: naturalizedInstance.SeriesDescription, + Modality: naturalizedInstance.Modality, + SeriesNumber: naturalizedInstance.SeriesNumber, + SeriesDate: naturalizedInstance.SeriesDate, + SeriesTime: naturalizedInstance.SeriesTime, instances: [], }; - study.seriesMap[seriesInstanceUid] = series; - study.seriesList.push(series); + study.seriesMap[SeriesInstanceUID] = series; + study.series.push(series); } - const sopInstanceUid = DICOMWeb.getString(instance['00080018']); const wadouri = buildInstanceWadoUrl( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ); const baseWadoRsUri = buildInstanceWadoRsUri( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ); const wadorsuri = buildInstanceFrameWadoRsUri( server, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID ); const sopInstance = { - imageType: DICOMWeb.getString(instance['00080008']), - sopClassUid: DICOMWeb.getString(instance['00080016']), - modality: DICOMWeb.getString(instance['00080060']), - sopInstanceUid, - instanceNumber: DICOMWeb.getNumber(instance['00200013']), - imagePositionPatient: DICOMWeb.getString(instance['00200032']), - imageOrientationPatient: DICOMWeb.getString(instance['00200037']), - frameOfReferenceUID: DICOMWeb.getString(instance['00200052']), - sliceLocation: DICOMWeb.getNumber(instance['00201041']), - samplesPerPixel: DICOMWeb.getNumber(instance['00280002']), - photometricInterpretation: DICOMWeb.getString(instance['00280004']), - planarConfiguration: DICOMWeb.getNumber(instance['00280006']), - rows: DICOMWeb.getNumber(instance['00280010']), - columns: DICOMWeb.getNumber(instance['00280011']), - pixelSpacing: DICOMWeb.getString(instance['00280030']), - pixelAspectRatio: DICOMWeb.getString(instance['00280034']), - bitsAllocated: DICOMWeb.getNumber(instance['00280100']), - bitsStored: DICOMWeb.getNumber(instance['00280101']), - highBit: DICOMWeb.getNumber(instance['00280102']), - pixelRepresentation: DICOMWeb.getNumber(instance['00280103']), - smallestPixelValue: DICOMWeb.getNumber(instance['00280106']), - largestPixelValue: DICOMWeb.getNumber(instance['00280107']), - windowCenter: DICOMWeb.getString(instance['00281050']), - windowWidth: DICOMWeb.getString(instance['00281051']), - rescaleIntercept: DICOMWeb.getNumber(instance['00281052']), - rescaleSlope: DICOMWeb.getNumber(instance['00281053']), - rescaleType: DICOMWeb.getNumber(instance['00281054']), - sourceImageInstanceUid: getSourceImageInstanceUid(instance), - laterality: DICOMWeb.getString(instance['00200062']), - viewPosition: DICOMWeb.getString(instance['00185101']), - acquisitionDateTime: DICOMWeb.getString(instance['0008002A']), - numberOfFrames: DICOMWeb.getNumber(instance['00280008']), - frameIncrementPointer: getFrameIncrementPointer(instance['00280009']), - frameTime: DICOMWeb.getNumber(instance['00181063']), - frameTimeVector: parseFloatArray(DICOMWeb.getString(instance['00181065'])), - sliceThickness: DICOMWeb.getNumber(instance['00180050']), - spacingBetweenSlices: DICOMWeb.getString(instance['00180088']), - lossyImageCompression: DICOMWeb.getString(instance['00282110']), - derivationDescription: DICOMWeb.getString(instance['00282111']), - lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']), - lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']), - echoNumber: DICOMWeb.getString(instance['00180086']), - contrastBolusAgent: DICOMWeb.getString(instance['00180010']), - radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance), - baseWadoRsUri: baseWadoRsUri, - wadouri: WADOProxy.convertURL(wadouri, server), - wadorsuri: WADOProxy.convertURL(wadorsuri, server), + metadata: naturalizedInstance, + baseWadoRsUri, + wadouri, + wadorsuri, wadoRoot: server.wadoRoot, imageRendering: server.imageRendering, thumbnailRendering: server.thumbnailRendering, }; - // Get additional information if the instance uses "PALETTE COLOR" photometric interpretation - if (sopInstance.photometricInterpretation === 'PALETTE COLOR') { - const redPaletteColorLookupTableDescriptor = parseFloatArray( - DICOMWeb.getString(instance['00281101']) - ); - const greenPaletteColorLookupTableDescriptor = parseFloatArray( - DICOMWeb.getString(instance['00281102']) - ); - const bluePaletteColorLookupTableDescriptor = parseFloatArray( - DICOMWeb.getString(instance['00281103']) - ); - const palettes = await getPaletteColors( - server, - instance, - redPaletteColorLookupTableDescriptor - ); - - if (palettes) { - if (palettes.uid) { - sopInstance.paletteColorLookupTableUID = palettes.uid; - } - - sopInstance.redPaletteColorLookupTableData = palettes.red; - sopInstance.greenPaletteColorLookupTableData = palettes.green; - sopInstance.bluePaletteColorLookupTableData = palettes.blue; - sopInstance.redPaletteColorLookupTableDescriptor = redPaletteColorLookupTableDescriptor; - sopInstance.greenPaletteColorLookupTableDescriptor = greenPaletteColorLookupTableDescriptor; - sopInstance.bluePaletteColorLookupTableDescriptor = bluePaletteColorLookupTableDescriptor; - } - } - series.instances.push(sopInstance); - return sopInstance; -} -/** - * Convert String to ArrayBuffer - * - * @param {String} str Input String - * @return {ArrayBuffer} Output converted ArrayBuffer - */ -function str2ab(str) { - const strLen = str.length; - const bytes = new Uint8Array(strLen); + if ( + sopInstance.thumbnailRendering === 'wadors' || + sopInstance.imageRendering === 'wadors' + ) { + // If using WADO-RS for either images or thumbnails, + // Need to add this to cornerstoneWADOImageLoader's provider + // (it won't be hit on cornerstone.metaData.get, but cornerstoneWADOImageLoader + // will cry if you don't add data to cornerstoneWADOImageLoader.wadors.metaDataManager). - for (let i = 0; i < strLen; i++) { - bytes[i] = str.charCodeAt(i); - } + const wadoRSMetadata = Object.assign(instance); - return bytes.buffer; -} + if (sopInstance.NumberOfFrames) { + for (let i = 0; i < sopInstance.NumberOfFrames; i++) { + const wadorsImageId = getWADORSImageId(sopInstance, i); -function getPaletteColor(server, instance, tag, lutDescriptor) { - const numLutEntries = lutDescriptor[0]; - const bits = lutDescriptor[2]; - - const readUInt16 = (byteArray, position) => { - return byteArray[position] + byteArray[position + 1] * 256; - }; - - const arrayBufferToPaletteColorLUT = arraybuffer => { - const byteArray = new Uint8Array(arraybuffer); - const lut = []; - - if (bits === 16) { - for (let i = 0; i < numLutEntries; i++) { - lut[i] = readUInt16(byteArray, i * 2); + cornerstoneWADOImageLoader.wadors.metaDataManager.add( + wadorsImageId, + wadoRSMetadata + ); } } else { - for (let i = 0; i < numLutEntries; i++) { - lut[i] = byteArray[i]; - } + const wadorsImageId = getWADORSImageId(sopInstance); + + cornerstoneWADOImageLoader.wadors.metaDataManager.add( + wadorsImageId, + wadoRSMetadata + ); } - - return lut; - }; - - if (instance[tag].BulkDataURI) { - let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server); - - // TODO: Workaround for dcm4chee behind SSL-terminating proxy returning - // incorrect bulk data URIs - if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) { - uri = uri.replace('http', 'https'); - } - - const config = { - url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used - headers: DICOMWeb.getAuthorizationHeader(server), - }; - const dicomWeb = new api.DICOMwebClient(config); - const options = { - BulkDataURI: uri, - }; - - return dicomWeb - .retrieveBulkData(options) - .then(result => result[0]) - .then(arrayBufferToPaletteColorLUT); - } else if (instance[tag].InlineBinary) { - const inlineBinaryData = atob(instance[tag].InlineBinary); - const arraybuf = str2ab(inlineBinaryData); - - return arrayBufferToPaletteColorLUT(arraybuf); } - throw new Error( - 'Palette Color LUT was not provided as InlineBinary or BulkDataURI' - ); -} - -/** - * Fetch palette colors for instances with "PALETTE COLOR" photometricInterpretation. - * - * @param server {Object} Current server; - * @param instance {Object} The retrieved instance metadata; - * @returns {String} The ReferenceSOPInstanceUID - */ -async function getPaletteColors(server, instance, lutDescriptor) { - let paletteUID = DICOMWeb.getString(instance['00281199']); - - return new Promise((resolve, reject) => { - let entry; - if (paletteColorCache.isValidUID(paletteUID)) { - entry = paletteColorCache.get(paletteUID); - - if (entry) { - return resolve(entry); - } - } - - // no entry in cache... Fetch remote data. - const r = getPaletteColor(server, instance, '00281201', lutDescriptor); - const g = getPaletteColor(server, instance, '00281202', lutDescriptor); - const b = getPaletteColor(server, instance, '00281203', lutDescriptor); - - const promises = [r, g, b]; - - Promise.all(promises).then(args => { - entry = { - red: args[0], - green: args[1], - blue: args[2], - }; - - // when paletteUID is present, the entry can be cached... - entry.uid = paletteUID; - paletteColorCache.add(entry); - - resolve(entry); - }); - }); + return sopInstance; } /** diff --git a/platform/core/src/studies/sortStudy.js b/platform/core/src/studies/sortStudy.js index 6bfd8b4e6..aa0614dd9 100644 --- a/platform/core/src/studies/sortStudy.js +++ b/platform/core/src/studies/sortStudy.js @@ -15,16 +15,16 @@ function seriesInfoSortingCriteria(firstSeries, secondSeries) { if (a.isLowPriority && !b.isLowPriority) { return 1; } - return a.seriesNumber - b.seriesNumber; + return a.SeriesNumber - b.SeriesNumber; } const seriesSortCriteria = { - default: (a, b) => a.seriesNumber - b.seriesNumber, + default: (a, b) => a.SeriesNumber - b.SeriesNumber, seriesInfoSortingCriteria, }; const instancesSortCriteria = { - default: (a, b) => a.instanceNumber - b.instanceNumber, + default: (a, b) => a.InstanceNumber - b.InstanceNumber, }; const sortingCriteria = { @@ -33,18 +33,18 @@ const sortingCriteria = { }; /** - * Sorts given seriesList (given param is modified) + * Sorts given series (given param is modified) * The default criteria is based on series number in ascending order. * - * @param {Array} seriesList List of series + * @param {Array} series List of series * @param {function} seriesSortingCriteria method for sorting - * @returns {Array} sorted seriesList object + * @returns {Array} sorted series object */ const sortStudySeries = ( - seriesList, + series, seriesSortingCriteria = seriesSortCriteria.default ) => { - return seriesList.sort(seriesSortingCriteria); + return series.sort(seriesSortingCriteria); }; /** @@ -78,14 +78,14 @@ export default function sortStudy( seriesSortingCriteria = seriesSortCriteria.default, instancesSortingCriteria = instancesSortCriteria.default ) { - if (!study || !study.seriesList) { + if (!study || !study.series) { throw new Error('Insufficient study data was provided to sortStudy'); } - sortStudySeries(study.seriesList, seriesSortingCriteria); + sortStudySeries(study.series, seriesSortingCriteria); if (deepSort) { - study.seriesList.forEach(series => { + study.series.forEach(series => { sortStudyInstances(series.instances, instancesSortingCriteria); }); } diff --git a/platform/core/src/utils/DICOMTagDescriptions.js b/platform/core/src/utils/DICOMTagDescriptions.js deleted file mode 100644 index 32215c5ac..000000000 --- a/platform/core/src/utils/DICOMTagDescriptions.js +++ /dev/null @@ -1,3268 +0,0 @@ -// TODO: Deprecate since we have the same thing in dcmjs? -const NUMBER = 'number'; -const STRING = 'string'; -const REGEX_TAG = /^x[0-9a-fx]{8}$/; - -const DICOMTagDescriptions = Object.create(Object.prototype, { - _descriptions: { - configurable: false, - enumerable: false, - writable: false, - value: Object.create(null), - }, - tagNumberToString: { - configurable: false, - enumerable: true, - writable: false, - value: function tagNumberToString(tag) { - let string; // by default, undefined is returned... - if (this.isValidTagNumber(tag)) { - // if it's a number, build its hexadecimal representation... - string = 'x' + ('00000000' + tag.toString(16)).substr(-8); - } - return string; - }, - }, - isValidTagNumber: { - configurable: false, - enumerable: true, - writable: false, - value: function isValidTagNumber(tag) { - return typeof tag === NUMBER && tag >= 0 && tag <= 0xffffffff; - }, - }, - isValidTag: { - configurable: false, - enumerable: true, - writable: false, - value: function isValidTag(tag) { - return typeof tag === STRING - ? REGEX_TAG.test(tag) - : this.isValidTagNumber(tag); - }, - }, - find: { - configurable: false, - enumerable: true, - writable: false, - value: function find(name) { - let description; // by default, undefined is returned... - if (typeof name !== STRING) { - // if it's a number, a tag string will be returned... - name = this.tagNumberToString(name); - } - if (typeof name === STRING) { - description = this._descriptions[name]; - } - return description; - }, - }, - init: { - configurable: false, - enumerable: true, - writable: false, - value: function init(descriptionMap) { - const _hasOwn = Object.prototype.hasOwnProperty; - const _descriptions = this._descriptions; - for (let tag in descriptionMap) { - if (_hasOwn.call(descriptionMap, tag)) { - if (!this.isValidTag(tag)) { - // Skip in case tag is not valid... - console.info(`DICOMTagDescriptions: Invalid tag "${tag}"...`); - continue; - } - if (tag in _descriptions) { - // Skip in case the tag is duplicated... - console.info(`DICOMTagDescriptions: Duplicated tag "${tag}"...`); - continue; - } - // Save keyword... - const keyword = descriptionMap[tag]; - // Create a description entry and freeze it... - const entry = Object.create(null); - entry.tag = tag; - entry.keyword = keyword; - Object.freeze(entry); - // Add tag references to entry... - _descriptions[tag] = entry; - // Add keyword references to entry (if not present already)... - if (keyword in _descriptions) { - const currentEntry = _descriptions[keyword]; - console.info( - `DICOMTagDescriptions: Using <${currentEntry.tag},${ - currentEntry.keyword - }> instead of <${entry.tag},${ - entry.keyword - }> for keyword "${keyword}"...` - ); - } else { - _descriptions[keyword] = entry; - } - } - } - // Freeze internal description map... - Object.freeze(_descriptions); - // Freeze itself... - Object.freeze(this); - }, - }, -}); - -/** - * Map with DICOM Tag Descriptions - */ -let initialTagDescriptionMap = { - x00020000: 'FileMetaInfoGroupLength', - x00020001: 'FileMetaInfoVersion', - x00020002: 'MediaStorageSOPClassUID', - x00020003: 'MediaStorageSOPInstanceUID', - x00020010: 'TransferSyntaxUID', - x00020012: 'ImplementationClassUID', - x00020013: 'ImplementationVersionName', - x00020016: 'SourceApplicationEntityTitle', - x00020100: 'PrivateInformationCreatorUID', - x00020102: 'PrivateInformation', - x00041130: 'FileSetID', - x00041141: 'FileSetDescriptorFileID', - x00041142: 'SpecificCharacterSetOfFile', - x00041200: 'FirstDirectoryRecordOffset', - x00041202: 'LastDirectoryRecordOffset', - x00041212: 'FileSetConsistencyFlag', - x00041220: 'DirectoryRecordSequence', - x00041400: 'OffsetOfNextDirectoryRecord', - x00041410: 'RecordInUseFlag', - x00041420: 'LowerLevelDirectoryEntityOffset', - x00041430: 'DirectoryRecordType', - x00041432: 'PrivateRecordUID', - x00041500: 'ReferencedFileID', - x00041504: 'MRDRDirectoryRecordOffset', - x00041510: 'ReferencedSOPClassUIDInFile', - x00041511: 'ReferencedSOPInstanceUIDInFile', - x00041512: 'ReferencedTransferSyntaxUIDInFile', - x0004151a: 'ReferencedRelatedSOPClassUIDInFile', - x00041600: 'NumberOfReferences', - x00080000: 'IdentifyingGroupLength', - x00080001: 'LengthToEnd', - x00080005: 'SpecificCharacterSet', - x00080006: 'LanguageCodeSequence', - x00080008: 'ImageType', - x00080010: 'RecognitionCode', - x00080012: 'InstanceCreationDate', - x00080013: 'InstanceCreationTime', - x00080014: 'InstanceCreatorUID', - x00080016: 'SOPClassUID', - x00080018: 'SOPInstanceUID', - x0008001a: 'RelatedGeneralSOPClassUID', - x0008001b: 'OriginalSpecializedSOPClassUID', - x00080020: 'StudyDate', - x00080021: 'SeriesDate', - x00080022: 'AcquisitionDate', - x00080023: 'ContentDate', - x00080024: 'OverlayDate', - x00080025: 'CurveDate', - x0008002a: 'AcquisitionDateTime', - x00080030: 'StudyTime', - x00080031: 'SeriesTime', - x00080032: 'AcquisitionTime', - x00080033: 'ContentTime', - x00080034: 'OverlayTime', - x00080035: 'CurveTime', - x00080040: 'DataSetType', - x00080041: 'DataSetSubtype', - x00080042: 'NuclearMedicineSeriesType', - x00080050: 'AccessionNumber', - x00080052: 'QueryRetrieveLevel', - x00080054: 'RetrieveAETitle', - x00080056: 'InstanceAvailability', - x00080058: 'FailedSOPInstanceUIDList', - x00080060: 'Modality', - x00080061: 'ModalitiesInStudy', - x00080062: 'SOPClassesInStudy', - x00080064: 'ConversionType', - x00080068: 'PresentationIntentType', - x00080070: 'Manufacturer', - x00080080: 'InstitutionName', - x00080081: 'InstitutionAddress', - x00080082: 'InstitutionCodeSequence', - x00080090: 'ReferringPhysicianName', - x00080092: 'ReferringPhysicianAddress', - x00080094: 'ReferringPhysicianTelephoneNumber', - x00080096: 'ReferringPhysicianIDSequence', - x00080100: 'CodeValue', - x00080102: 'CodingSchemeDesignator', - x00080103: 'CodingSchemeVersion', - x00080104: 'CodeMeaning', - x00080105: 'MappingResource', - x00080106: 'ContextGroupVersion', - x00080107: 'ContextGroupLocalVersion', - x0008010b: 'ContextGroupExtensionFlag', - x0008010c: 'CodingSchemeUID', - x0008010d: 'ContextGroupExtensionCreatorUID', - x0008010f: 'ContextIdentifier', - x00080110: 'CodingSchemeIDSequence', - x00080112: 'CodingSchemeRegistry', - x00080114: 'CodingSchemeExternalID', - x00080115: 'CodingSchemeName', - x00080116: 'CodingSchemeResponsibleOrganization', - x00080117: 'ContextUID', - x00080201: 'TimezoneOffsetFromUTC', - x00081000: 'NetworkID', - x00081010: 'StationName', - x00081030: 'StudyDescription', - x00081032: 'ProcedureCodeSequence', - x0008103e: 'SeriesDescription', - x00081040: 'InstitutionalDepartmentName', - x00081048: 'PhysiciansOfRecord', - x00081049: 'PhysiciansOfRecordIDSequence', - x00081050: 'PerformingPhysicianName', - x00081052: 'PerformingPhysicianIDSequence', - x00081060: 'NameOfPhysicianReadingStudy', - x00081062: 'PhysicianReadingStudyIDSequence', - x00081070: 'OperatorsName', - x00081072: 'OperatorIDSequence', - x00081080: 'AdmittingDiagnosesDescription', - x00081084: 'AdmittingDiagnosesCodeSequence', - x00081090: 'ManufacturersModelName', - x00081100: 'ReferencedResultsSequence', - x00081110: 'ReferencedStudySequence', - x00081111: 'ReferencedPerformedProcedureStepSequence', - x00081115: 'ReferencedSeriesSequence', - x00081120: 'ReferencedPatientSequence', - x00081125: 'ReferencedVisitSequence', - x00081130: 'ReferencedOverlaySequence', - x0008113a: 'ReferencedWaveformSequence', - x00081140: 'ReferencedImageSequence', - x00081145: 'ReferencedCurveSequence', - x0008114a: 'ReferencedInstanceSequence', - x00081150: 'ReferencedSOPClassUID', - x00081155: 'ReferencedSOPInstanceUID', - x0008115a: 'SOPClassesSupported', - x00081160: 'ReferencedFrameNumber', - x00081161: 'SimpleFrameList', - x00081162: 'CalculatedFrameList', - x00081163: 'TimeRange', - x00081164: 'FrameExtractionSequence', - x00081195: 'TransactionUID', - x00081197: 'FailureReason', - x00081198: 'FailedSOPSequence', - x00081199: 'ReferencedSOPSequence', - x00081200: 'OtherReferencedStudiesSequence', - x00081250: 'RelatedSeriesSequence', - x00082110: 'LossyImageCompressionRetired', - x00082111: 'DerivationDescription', - x00082112: 'SourceImageSequence', - x00082120: 'StageName', - x00082122: 'StageNumber', - x00082124: 'NumberOfStages', - x00082127: 'ViewName', - x00082128: 'ViewNumber', - x00082129: 'NumberOfEventTimers', - x0008212a: 'NumberOfViewsInStage', - x00082130: 'EventElapsedTimes', - x00082132: 'EventTimerNames', - x00082133: 'EventTimerSequence', - x00082134: 'EventTimeOffset', - x00082135: 'EventCodeSequence', - x00082142: 'StartTrim', - x00082143: 'StopTrim', - x00082144: 'RecommendedDisplayFrameRate', - x00082200: 'TransducerPosition', - x00082204: 'TransducerOrientation', - x00082208: 'AnatomicStructure', - x00082218: 'AnatomicRegionSequence', - x00082220: 'AnatomicRegionModifierSequence', - x00082228: 'PrimaryAnatomicStructureSequence', - x00082229: 'AnatomicStructureOrRegionSequence', - x00082230: 'AnatomicStructureModifierSequence', - x00082240: 'TransducerPositionSequence', - x00082242: 'TransducerPositionModifierSequence', - x00082244: 'TransducerOrientationSequence', - x00082246: 'TransducerOrientationModifierSeq', - x00082253: 'AnatomicEntrancePortalCodeSeqTrial', - x00082255: 'AnatomicApproachDirCodeSeqTrial', - x00082256: 'AnatomicPerspectiveDescrTrial', - x00082257: 'AnatomicPerspectiveCodeSeqTrial', - x00083001: 'AlternateRepresentationSequence', - x00083010: 'IrradiationEventUID', - x00084000: 'IdentifyingComments', - x00089007: 'FrameType', - x00089092: 'ReferencedImageEvidenceSequence', - x00089121: 'ReferencedRawDataSequence', - x00089123: 'CreatorVersionUID', - x00089124: 'DerivationImageSequence', - x00089154: 'SourceImageEvidenceSequence', - x00089205: 'PixelPresentation', - x00089206: 'VolumetricProperties', - x00089207: 'VolumeBasedCalculationTechnique', - x00089208: 'ComplexImageComponent', - x00089209: 'AcquisitionContrast', - x00089215: 'DerivationCodeSequence', - x00089237: 'GrayscalePresentationStateSequence', - x00089410: 'ReferencedOtherPlaneSequence', - x00089458: 'FrameDisplaySequence', - x00089459: 'RecommendedDisplayFrameRateInFloat', - x00089460: 'SkipFrameRangeFlag', - // x00091001: 'FullFidelity', - // x00091002: 'SuiteID', - // x00091004: 'ProductID', - // x00091027: 'ImageActualDate', - // x00091030: 'ServiceID', - // x00091031: 'MobileLocationNumber', - // x000910e3: 'EquipmentUID', - // x000910e6: 'GenesisVersionNow', - // x000910e7: 'ExamRecordChecksum', - // x000910e9: 'ActualSeriesDataTimeStamp', - x00100000: 'PatientGroupLength', - x00100010: 'PatientName', - x00100020: 'PatientID', - x00100021: 'IssuerOfPatientID', - x00100022: 'TypeOfPatientID', - x00100030: 'PatientBirthDate', - x00100032: 'PatientBirthTime', - x00100040: 'PatientSex', - x00100050: 'PatientInsurancePlanCodeSequence', - x00100101: 'PatientPrimaryLanguageCodeSeq', - x00100102: 'PatientPrimaryLanguageCodeModSeq', - x00101000: 'OtherPatientIDs', - x00101001: 'OtherPatientNames', - x00101002: 'OtherPatientIDsSequence', - x00101005: 'PatientBirthName', - x00101010: 'PatientAge', - x00101020: 'PatientSize', - x00101030: 'PatientWeight', - x00101040: 'PatientAddress', - x00101050: 'InsurancePlanIdentification', - x00101060: 'PatientMotherBirthName', - x00101080: 'MilitaryRank', - x00101081: 'BranchOfService', - x00101090: 'MedicalRecordLocator', - x00102000: 'MedicalAlerts', - x00102110: 'Allergies', - x00102150: 'CountryOfResidence', - x00102152: 'RegionOfResidence', - x00102154: 'PatientTelephoneNumbers', - x00102160: 'EthnicGroup', - x00102180: 'Occupation', - x001021a0: 'SmokingStatus', - x001021b0: 'AdditionalPatientHistory', - x001021c0: 'PregnancyStatus', - x001021d0: 'LastMenstrualDate', - x001021f0: 'PatientReligiousPreference', - x00102201: 'PatientSpeciesDescription', - x00102202: 'PatientSpeciesCodeSequence', - x00102203: 'PatientSexNeutered', - x00102210: 'AnatomicalOrientationType', - x00102292: 'PatientBreedDescription', - x00102293: 'PatientBreedCodeSequence', - x00102294: 'BreedRegistrationSequence', - x00102295: 'BreedRegistrationNumber', - x00102296: 'BreedRegistryCodeSequence', - x00102297: 'ResponsiblePerson', - x00102298: 'ResponsiblePersonRole', - x00102299: 'ResponsibleOrganization', - x00104000: 'PatientComments', - x00109431: 'ExaminedBodyThickness', - x00111010: 'PatientStatus', - x00120010: 'ClinicalTrialSponsorName', - x00120020: 'ClinicalTrialProtocolID', - x00120021: 'ClinicalTrialProtocolName', - x00120030: 'ClinicalTrialSiteID', - x00120031: 'ClinicalTrialSiteName', - x00120040: 'ClinicalTrialSubjectID', - x00120042: 'ClinicalTrialSubjectReadingID', - x00120050: 'ClinicalTrialTimePointID', - x00120051: 'ClinicalTrialTimePointDescription', - x00120060: 'ClinicalTrialCoordinatingCenter', - x00120062: 'PatientIdentityRemoved', - x00120063: 'DeidentificationMethod', - x00120064: 'DeidentificationMethodCodeSequence', - x00120071: 'ClinicalTrialSeriesID', - x00120072: 'ClinicalTrialSeriesDescription', - x00120084: 'DistributionType', - x00120085: 'ConsentForDistributionFlag', - x00180000: 'AcquisitionGroupLength', - x00180010: 'ContrastBolusAgent', - x00180012: 'ContrastBolusAgentSequence', - x00180014: 'ContrastBolusAdministrationRoute', - x00180015: 'BodyPartExamined', - x00180020: 'ScanningSequence', - x00180021: 'SequenceVariant', - x00180022: 'ScanOptions', - x00180023: 'MRAcquisitionType', - x00180024: 'SequenceName', - x00180025: 'AngioFlag', - x00180026: 'InterventionDrugInformationSeq', - x00180027: 'InterventionDrugStopTime', - x00180028: 'InterventionDrugDose', - x00180029: 'InterventionDrugSequence', - x0018002a: 'AdditionalDrugSequence', - x00180030: 'Radionuclide', - x00180031: 'Radiopharmaceutical', - x00180032: 'EnergyWindowCenterline', - x00180033: 'EnergyWindowTotalWidth', - x00180034: 'InterventionDrugName', - x00180035: 'InterventionDrugStartTime', - x00180036: 'InterventionSequence', - x00180037: 'TherapyType', - x00180038: 'InterventionStatus', - x00180039: 'TherapyDescription', - x0018003a: 'InterventionDescription', - x00180040: 'CineRate', - x00180042: 'InitialCineRunState', - x00180050: 'SliceThickness', - x00180060: 'KVP', - x00180070: 'CountsAccumulated', - x00180071: 'AcquisitionTerminationCondition', - x00180072: 'EffectiveDuration', - x00180073: 'AcquisitionStartCondition', - x00180074: 'AcquisitionStartConditionData', - x00180075: 'AcquisitionEndConditionData', - x00180080: 'RepetitionTime', - x00180081: 'EchoTime', - x00180082: 'InversionTime', - x00180083: 'NumberOfAverages', - x00180084: 'ImagingFrequency', - x00180085: 'ImagedNucleus', - x00180086: 'EchoNumber', - x00180087: 'MagneticFieldStrength', - x00180088: 'SpacingBetweenSlices', - x00180089: 'NumberOfPhaseEncodingSteps', - x00180090: 'DataCollectionDiameter', - x00180091: 'EchoTrainLength', - x00180093: 'PercentSampling', - x00180094: 'PercentPhaseFieldOfView', - x00180095: 'PixelBandwidth', - x00181000: 'DeviceSerialNumber', - x00181002: 'DeviceUID', - x00181003: 'DeviceID', - x00181004: 'PlateID', - x00181005: 'GeneratorID', - x00181006: 'GridID', - x00181007: 'CassetteID', - x00181008: 'GantryID', - x00181010: 'SecondaryCaptureDeviceID', - x00181011: 'HardcopyCreationDeviceID', - x00181012: 'DateOfSecondaryCapture', - x00181014: 'TimeOfSecondaryCapture', - x00181016: 'SecondaryCaptureDeviceManufacturer', - x00181017: 'HardcopyDeviceManufacturer', - x00181018: 'SecondaryCaptureDeviceModelName', - x00181019: 'SecondaryCaptureDeviceSoftwareVers', - x0018101a: 'HardcopyDeviceSoftwareVersion', - x0018101b: 'HardcopyDeviceModelName', - x00181020: 'SoftwareVersion', - x00181022: 'VideoImageFormatAcquired', - x00181023: 'DigitalImageFormatAcquired', - x00181030: 'ProtocolName', - x00181040: 'ContrastBolusRoute', - x00181041: 'ContrastBolusVolume', - x00181042: 'ContrastBolusStartTime', - x00181043: 'ContrastBolusStopTime', - x00181044: 'ContrastBolusTotalDose', - x00181045: 'SyringeCounts', - x00181046: 'ContrastFlowRate', - x00181047: 'ContrastFlowDuration', - x00181048: 'ContrastBolusIngredient', - x00181049: 'ContrastBolusConcentration', - x00181050: 'SpatialResolution', - x00181060: 'TriggerTime', - x00181061: 'TriggerSourceOrType', - x00181062: 'NominalInterval', - x00181063: 'FrameTime', - x00181064: 'CardiacFramingType', - x00181065: 'FrameTimeVector', - x00181066: 'FrameDelay', - x00181067: 'ImageTriggerDelay', - x00181068: 'MultiplexGroupTimeOffset', - x00181069: 'TriggerTimeOffset', - x0018106a: 'SynchronizationTrigger', - x0018106c: 'SynchronizationChannel', - x0018106e: 'TriggerSamplePosition', - x00181070: 'RadiopharmaceuticalRoute', - x00181071: 'RadiopharmaceuticalVolume', - x00181072: 'RadiopharmaceuticalStartTime', - x00181073: 'RadiopharmaceuticalStopTime', - x00181074: 'RadionuclideTotalDose', - x00181075: 'RadionuclideHalfLife', - x00181076: 'RadionuclidePositronFraction', - x00181077: 'RadiopharmaceuticalSpecActivity', - x00181078: 'RadiopharmaceuticalStartDateTime', - x00181079: 'RadiopharmaceuticalStopDateTime', - x00181080: 'BeatRejectionFlag', - x00181081: 'LowRRValue', - x00181082: 'HighRRValue', - x00181083: 'IntervalsAcquired', - x00181084: 'IntervalsRejected', - x00181085: 'PVCRejection', - x00181086: 'SkipBeats', - x00181088: 'HeartRate', - x00181090: 'CardiacNumberOfImages', - x00181094: 'TriggerWindow', - x00181100: 'ReconstructionDiameter', - x00181110: 'DistanceSourceToDetector', - x00181111: 'DistanceSourceToPatient', - x00181114: 'EstimatedRadiographicMagnification', - x00181120: 'GantryDetectorTilt', - x00181121: 'GantryDetectorSlew', - x00181130: 'TableHeight', - x00181131: 'TableTraverse', - x00181134: 'TableMotion', - x00181135: 'TableVerticalIncrement', - x00181136: 'TableLateralIncrement', - x00181137: 'TableLongitudinalIncrement', - x00181138: 'TableAngle', - x0018113a: 'TableType', - x00181140: 'RotationDirection', - x00181141: 'AngularPosition', - x00181142: 'RadialPosition', - x00181143: 'ScanArc', - x00181144: 'AngularStep', - x00181145: 'CenterOfRotationOffset', - x00181146: 'RotationOffset', - x00181147: 'FieldOfViewShape', - x00181149: 'FieldOfViewDimensions', - x00181150: 'ExposureTime', - x00181151: 'XRayTubeCurrent', - x00181152: 'Exposure', - x00181153: 'ExposureInMicroAmpSec', - x00181154: 'AveragePulseWidth', - x00181155: 'RadiationSetting', - x00181156: 'RectificationType', - x0018115a: 'RadiationMode', - x0018115e: 'ImageAreaDoseProduct', - x00181160: 'FilterType', - x00181161: 'TypeOfFilters', - x00181162: 'IntensifierSize', - x00181164: 'ImagerPixelSpacing', - x00181166: 'Grid', - x00181170: 'GeneratorPower', - x00181180: 'CollimatorGridName', - x00181181: 'CollimatorType', - x00181182: 'FocalDistance', - x00181183: 'XFocusCenter', - x00181184: 'YFocusCenter', - x00181190: 'FocalSpots', - x00181191: 'AnodeTargetMaterial', - x001811a0: 'BodyPartThickness', - x001811a2: 'CompressionForce', - x00181200: 'DateOfLastCalibration', - x00181201: 'TimeOfLastCalibration', - x00181210: 'ConvolutionKernel', - x00181240: 'UpperLowerPixelValues', - x00181242: 'ActualFrameDuration', - x00181243: 'CountRate', - x00181244: 'PreferredPlaybackSequencing', - x00181250: 'ReceiveCoilName', - x00181251: 'TransmitCoilName', - x00181260: 'PlateType', - x00181261: 'PhosphorType', - x00181300: 'ScanVelocity', - x00181301: 'WholeBodyTechnique', - x00181302: 'ScanLength', - x00181310: 'AcquisitionMatrix', - x00181312: 'InPlanePhaseEncodingDirection', - x00181314: 'FlipAngle', - x00181315: 'VariableFlipAngleFlag', - x00181316: 'SAR', - x00181318: 'DB-Dt', - x00181400: 'AcquisitionDeviceProcessingDescr', - x00181401: 'AcquisitionDeviceProcessingCode', - x00181402: 'CassetteOrientation', - x00181403: 'CassetteSize', - x00181404: 'ExposuresOnPlate', - x00181405: 'RelativeXRayExposure', - x00181450: 'ColumnAngulation', - x00181460: 'TomoLayerHeight', - x00181470: 'TomoAngle', - x00181480: 'TomoTime', - x00181490: 'TomoType', - x00181491: 'TomoClass', - x00181495: 'NumberOfTomosynthesisSourceImages', - x00181500: 'PositionerMotion', - x00181508: 'PositionerType', - x00181510: 'PositionerPrimaryAngle', - x00181511: 'PositionerSecondaryAngle', - x00181520: 'PositionerPrimaryAngleIncrement', - x00181521: 'PositionerSecondaryAngleIncrement', - x00181530: 'DetectorPrimaryAngle', - x00181531: 'DetectorSecondaryAngle', - x00181600: 'ShutterShape', - x00181602: 'ShutterLeftVerticalEdge', - x00181604: 'ShutterRightVerticalEdge', - x00181606: 'ShutterUpperHorizontalEdge', - x00181608: 'ShutterLowerHorizontalEdge', - x00181610: 'CenterOfCircularShutter', - x00181612: 'RadiusOfCircularShutter', - x00181620: 'VerticesOfPolygonalShutter', - x00181622: 'ShutterPresentationValue', - x00181623: 'ShutterOverlayGroup', - x00181624: 'ShutterPresentationColorCIELabVal', - x00181700: 'CollimatorShape', - x00181702: 'CollimatorLeftVerticalEdge', - x00181704: 'CollimatorRightVerticalEdge', - x00181706: 'CollimatorUpperHorizontalEdge', - x00181708: 'CollimatorLowerHorizontalEdge', - x00181710: 'CenterOfCircularCollimator', - x00181712: 'RadiusOfCircularCollimator', - x00181720: 'VerticesOfPolygonalCollimator', - x00181800: 'AcquisitionTimeSynchronized', - x00181801: 'TimeSource', - x00181802: 'TimeDistributionProtocol', - x00181803: 'NTPSourceAddress', - x00182001: 'PageNumberVector', - x00182002: 'FrameLabelVector', - x00182003: 'FramePrimaryAngleVector', - x00182004: 'FrameSecondaryAngleVector', - x00182005: 'SliceLocationVector', - x00182006: 'DisplayWindowLabelVector', - x00182010: 'NominalScannedPixelSpacing', - x00182020: 'DigitizingDeviceTransportDirection', - x00182030: 'RotationOfScannedFilm', - x00183100: 'IVUSAcquisition', - x00183101: 'IVUSPullbackRate', - x00183102: 'IVUSGatedRate', - x00183103: 'IVUSPullbackStartFrameNumber', - x00183104: 'IVUSPullbackStopFrameNumber', - x00183105: 'LesionNumber', - x00184000: 'AcquisitionComments', - x00185000: 'OutputPower', - x00185010: 'TransducerData', - x00185012: 'FocusDepth', - x00185020: 'ProcessingFunction', - x00185021: 'PostprocessingFunction', - x00185022: 'MechanicalIndex', - x00185024: 'BoneThermalIndex', - x00185026: 'CranialThermalIndex', - x00185027: 'SoftTissueThermalIndex', - x00185028: 'SoftTissueFocusThermalIndex', - x00185029: 'SoftTissueSurfaceThermalIndex', - x00185030: 'DynamicRange', - x00185040: 'TotalGain', - x00185050: 'DepthOfScanField', - x00185100: 'PatientPosition', - x00185101: 'ViewPosition', - x00185104: 'ProjectionEponymousNameCodeSeq', - x00185210: 'ImageTransformationMatrix', - x00185212: 'ImageTranslationVector', - x00186000: 'Sensitivity', - x00186011: 'SequenceOfUltrasoundRegions', - x00186012: 'RegionSpatialFormat', - x00186014: 'RegionDataType', - x00186016: 'RegionFlags', - x00186018: 'RegionLocationMinX0', - x0018601a: 'RegionLocationMinY0', - x0018601c: 'RegionLocationMaxX1', - x0018601e: 'RegionLocationMaxY1', - x00186020: 'ReferencePixelX0', - x00186022: 'ReferencePixelY0', - x00186024: 'PhysicalUnitsXDirection', - x00186026: 'PhysicalUnitsYDirection', - x00186028: 'ReferencePixelPhysicalValueX', - x0018602a: 'ReferencePixelPhysicalValueY', - x0018602c: 'PhysicalDeltaX', - x0018602e: 'PhysicalDeltaY', - x00186030: 'TransducerFrequency', - x00186031: 'TransducerType', - x00186032: 'PulseRepetitionFrequency', - x00186034: 'DopplerCorrectionAngle', - x00186036: 'SteeringAngle', - x00186038: 'DopplerSampleVolumeXPosRetired', - x00186039: 'DopplerSampleVolumeXPosition', - x0018603a: 'DopplerSampleVolumeYPosRetired', - x0018603b: 'DopplerSampleVolumeYPosition', - x0018603c: 'TMLinePositionX0Retired', - x0018603d: 'TMLinePositionX0', - x0018603e: 'TMLinePositionY0Retired', - x0018603f: 'TMLinePositionY0', - x00186040: 'TMLinePositionX1Retired', - x00186041: 'TMLinePositionX1', - x00186042: 'TMLinePositionY1Retired', - x00186043: 'TMLinePositionY1', - x00186044: 'PixelComponentOrganization', - x00186046: 'PixelComponentMask', - x00186048: 'PixelComponentRangeStart', - x0018604a: 'PixelComponentRangeStop', - x0018604c: 'PixelComponentPhysicalUnits', - x0018604e: 'PixelComponentDataType', - x00186050: 'NumberOfTableBreakPoints', - x00186052: 'TableOfXBreakPoints', - x00186054: 'TableOfYBreakPoints', - x00186056: 'NumberOfTableEntries', - x00186058: 'TableOfPixelValues', - x0018605a: 'TableOfParameterValues', - x00186060: 'RWaveTimeVector', - x00187000: 'DetectorConditionsNominalFlag', - x00187001: 'DetectorTemperature', - x00187004: 'DetectorType', - x00187005: 'DetectorConfiguration', - x00187006: 'DetectorDescription', - x00187008: 'DetectorMode', - x0018700a: 'DetectorID', - x0018700c: 'DateOfLastDetectorCalibration', - x0018700e: 'TimeOfLastDetectorCalibration', - x00187010: 'DetectorExposuresSinceCalibration', - x00187011: 'DetectorExposuresSinceManufactured', - x00187012: 'DetectorTimeSinceLastExposure', - x00187014: 'DetectorActiveTime', - x00187016: 'DetectorActiveOffsetFromExposure', - x0018701a: 'DetectorBinning', - x00187020: 'DetectorElementPhysicalSize', - x00187022: 'DetectorElementSpacing', - x00187024: 'DetectorActiveShape', - x00187026: 'DetectorActiveDimensions', - x00187028: 'DetectorActiveOrigin', - x0018702a: 'DetectorManufacturerName', - x0018702b: 'DetectorManufacturersModelName', - x00187030: 'FieldOfViewOrigin', - x00187032: 'FieldOfViewRotation', - x00187034: 'FieldOfViewHorizontalFlip', - x00187040: 'GridAbsorbingMaterial', - x00187041: 'GridSpacingMaterial', - x00187042: 'GridThickness', - x00187044: 'GridPitch', - x00187046: 'GridAspectRatio', - x00187048: 'GridPeriod', - x0018704c: 'GridFocalDistance', - x00187050: 'FilterMaterial', - x00187052: 'FilterThicknessMinimum', - x00187054: 'FilterThicknessMaximum', - x00187060: 'ExposureControlMode', - x00187062: 'ExposureControlModeDescription', - x00187064: 'ExposureStatus', - x00187065: 'PhototimerSetting', - x00188150: 'ExposureTimeInMicroSec', - x00188151: 'XRayTubeCurrentInMicroAmps', - x00189004: 'ContentQualification', - x00189005: 'PulseSequenceName', - x00189006: 'MRImagingModifierSequence', - x00189008: 'EchoPulseSequence', - x00189009: 'InversionRecovery', - x00189010: 'FlowCompensation', - x00189011: 'MultipleSpinEcho', - x00189012: 'MultiPlanarExcitation', - x00189014: 'PhaseContrast', - x00189015: 'TimeOfFlightContrast', - x00189016: 'Spoiling', - x00189017: 'SteadyStatePulseSequence', - x00189018: 'EchoPlanarPulseSequence', - x00189019: 'TagAngleFirstAxis', - x00189020: 'MagnetizationTransfer', - x00189021: 'T2Preparation', - x00189022: 'BloodSignalNulling', - x00189024: 'SaturationRecovery', - x00189025: 'SpectrallySelectedSuppression', - x00189026: 'SpectrallySelectedExcitation', - x00189027: 'SpatialPresaturation', - x00189028: 'Tagging', - x00189029: 'OversamplingPhase', - x00189030: 'TagSpacingFirstDimension', - x00189032: 'GeometryOfKSpaceTraversal', - x00189033: 'SegmentedKSpaceTraversal', - x00189034: 'RectilinearPhaseEncodeReordering', - x00189035: 'TagThickness', - x00189036: 'PartialFourierDirection', - x00189037: 'CardiacSynchronizationTechnique', - x00189041: 'ReceiveCoilManufacturerName', - x00189042: 'MRReceiveCoilSequence', - x00189043: 'ReceiveCoilType', - x00189044: 'QuadratureReceiveCoil', - x00189045: 'MultiCoilDefinitionSequence', - x00189046: 'MultiCoilConfiguration', - x00189047: 'MultiCoilElementName', - x00189048: 'MultiCoilElementUsed', - x00189049: 'MRTransmitCoilSequence', - x00189050: 'TransmitCoilManufacturerName', - x00189051: 'TransmitCoilType', - x00189052: 'SpectralWidth', - x00189053: 'ChemicalShiftReference', - x00189054: 'VolumeLocalizationTechnique', - x00189058: 'MRAcquisitionFrequencyEncodeSteps', - x00189059: 'Decoupling', - x00189060: 'DecoupledNucleus', - x00189061: 'DecouplingFrequency', - x00189062: 'DecouplingMethod', - x00189063: 'DecouplingChemicalShiftReference', - x00189064: 'KSpaceFiltering', - x00189065: 'TimeDomainFiltering', - x00189066: 'NumberOfZeroFills', - x00189067: 'BaselineCorrection', - x00189069: 'ParallelReductionFactorInPlane', - x00189070: 'CardiacRRIntervalSpecified', - x00189073: 'AcquisitionDuration', - x00189074: 'FrameAcquisitionDateTime', - x00189075: 'DiffusionDirectionality', - x00189076: 'DiffusionGradientDirectionSequence', - x00189077: 'ParallelAcquisition', - x00189078: 'ParallelAcquisitionTechnique', - x00189079: 'InversionTimes', - x00189080: 'MetaboliteMapDescription', - x00189081: 'PartialFourier', - x00189082: 'EffectiveEchoTime', - x00189083: 'MetaboliteMapCodeSequence', - x00189084: 'ChemicalShiftSequence', - x00189085: 'CardiacSignalSource', - x00189087: 'DiffusionBValue', - x00189089: 'DiffusionGradientOrientation', - x00189090: 'VelocityEncodingDirection', - x00189091: 'VelocityEncodingMinimumValue', - x00189093: 'NumberOfKSpaceTrajectories', - x00189094: 'CoverageOfKSpace', - x00189095: 'SpectroscopyAcquisitionPhaseRows', - x00189096: 'ParallelReductFactorInPlaneRetired', - x00189098: 'TransmitterFrequency', - x00189100: 'ResonantNucleus', - x00189101: 'FrequencyCorrection', - x00189103: 'MRSpectroscopyFOV-GeometrySequence', - x00189104: 'SlabThickness', - x00189105: 'SlabOrientation', - x00189106: 'MidSlabPosition', - x00189107: 'MRSpatialSaturationSequence', - x00189112: 'MRTimingAndRelatedParametersSeq', - x00189114: 'MREchoSequence', - x00189115: 'MRModifierSequence', - x00189117: 'MRDiffusionSequence', - x00189118: 'CardiacTriggerSequence', - x00189119: 'MRAveragesSequence', - x00189125: 'MRFOV-GeometrySequence', - x00189126: 'VolumeLocalizationSequence', - x00189127: 'SpectroscopyAcquisitionDataColumns', - x00189147: 'DiffusionAnisotropyType', - x00189151: 'FrameReferenceDateTime', - x00189152: 'MRMetaboliteMapSequence', - x00189155: 'ParallelReductionFactorOutOfPlane', - x00189159: 'SpectroscopyOutOfPlanePhaseSteps', - x00189166: 'BulkMotionStatus', - x00189168: 'ParallelReductionFactSecondInPlane', - x00189169: 'CardiacBeatRejectionTechnique', - x00189170: 'RespiratoryMotionCompTechnique', - x00189171: 'RespiratorySignalSource', - x00189172: 'BulkMotionCompensationTechnique', - x00189173: 'BulkMotionSignalSource', - x00189174: 'ApplicableSafetyStandardAgency', - x00189175: 'ApplicableSafetyStandardDescr', - x00189176: 'OperatingModeSequence', - x00189177: 'OperatingModeType', - x00189178: 'OperatingMode', - x00189179: 'SpecificAbsorptionRateDefinition', - x00189180: 'GradientOutputType', - x00189181: 'SpecificAbsorptionRateValue', - x00189182: 'GradientOutput', - x00189183: 'FlowCompensationDirection', - x00189184: 'TaggingDelay', - x00189185: 'RespiratoryMotionCompTechDescr', - x00189186: 'RespiratorySignalSourceID', - x00189195: 'ChemicalShiftsMinIntegrateLimitHz', - x00189196: 'ChemicalShiftsMaxIntegrateLimitHz', - x00189197: 'MRVelocityEncodingSequence', - x00189198: 'FirstOrderPhaseCorrection', - x00189199: 'WaterReferencedPhaseCorrection', - x00189200: 'MRSpectroscopyAcquisitionType', - x00189214: 'RespiratoryCyclePosition', - x00189217: 'VelocityEncodingMaximumValue', - x00189218: 'TagSpacingSecondDimension', - x00189219: 'TagAngleSecondAxis', - x00189220: 'FrameAcquisitionDuration', - x00189226: 'MRImageFrameTypeSequence', - x00189227: 'MRSpectroscopyFrameTypeSequence', - x00189231: 'MRAcqPhaseEncodingStepsInPlane', - x00189232: 'MRAcqPhaseEncodingStepsOutOfPlane', - x00189234: 'SpectroscopyAcqPhaseColumns', - x00189236: 'CardiacCyclePosition', - x00189239: 'SpecificAbsorptionRateSequence', - x00189240: 'RFEchoTrainLength', - x00189241: 'GradientEchoTrainLength', - x00189295: 'ChemicalShiftsMinIntegrateLimitPPM', - x00189296: 'ChemicalShiftsMaxIntegrateLimitPPM', - x00189301: 'CTAcquisitionTypeSequence', - x00189302: 'AcquisitionType', - x00189303: 'TubeAngle', - x00189304: 'CTAcquisitionDetailsSequence', - x00189305: 'RevolutionTime', - x00189306: 'SingleCollimationWidth', - x00189307: 'TotalCollimationWidth', - x00189308: 'CTTableDynamicsSequence', - x00189309: 'TableSpeed', - x00189310: 'TableFeedPerRotation', - x00189311: 'SpiralPitchFactor', - x00189312: 'CTGeometrySequence', - x00189313: 'DataCollectionCenterPatient', - x00189314: 'CTReconstructionSequence', - x00189315: 'ReconstructionAlgorithm', - x00189316: 'ConvolutionKernelGroup', - x00189317: 'ReconstructionFieldOfView', - x00189318: 'ReconstructionTargetCenterPatient', - x00189319: 'ReconstructionAngle', - x00189320: 'ImageFilter', - x00189321: 'CTExposureSequence', - x00189322: 'ReconstructionPixelSpacing', - x00189323: 'ExposureModulationType', - x00189324: 'EstimatedDoseSaving', - x00189325: 'CTXRayDetailsSequence', - x00189326: 'CTPositionSequence', - x00189327: 'TablePosition', - x00189328: 'ExposureTimeInMilliSec', - x00189329: 'CTImageFrameTypeSequence', - x00189330: 'XRayTubeCurrentInMilliAmps', - x00189332: 'ExposureInMilliAmpSec', - x00189333: 'ConstantVolumeFlag', - x00189334: 'FluoroscopyFlag', - x00189335: 'SourceToDataCollectionCenterDist', - x00189337: 'ContrastBolusAgentNumber', - x00189338: 'ContrastBolusIngredientCodeSeq', - x00189340: 'ContrastAdministrationProfileSeq', - x00189341: 'ContrastBolusUsageSequence', - x00189342: 'ContrastBolusAgentAdministered', - x00189343: 'ContrastBolusAgentDetected', - x00189344: 'ContrastBolusAgentPhase', - x00189345: 'CTDIvol', - x00189346: 'CTDIPhantomTypeCodeSequence', - x00189351: 'CalciumScoringMassFactorPatient', - x00189352: 'CalciumScoringMassFactorDevice', - x00189353: 'EnergyWeightingFactor', - x00189360: 'CTAdditionalXRaySourceSequence', - x00189401: 'ProjectionPixelCalibrationSequence', - x00189402: 'DistanceSourceToIsocenter', - x00189403: 'DistanceObjectToTableTop', - x00189404: 'ObjectPixelSpacingInCenterOfBeam', - x00189405: 'PositionerPositionSequence', - x00189406: 'TablePositionSequence', - x00189407: 'CollimatorShapeSequence', - x00189412: 'XA-XRFFrameCharacteristicsSequence', - x00189417: 'FrameAcquisitionSequence', - x00189420: 'XRayReceptorType', - x00189423: 'AcquisitionProtocolName', - x00189424: 'AcquisitionProtocolDescription', - x00189425: 'ContrastBolusIngredientOpaque', - x00189426: 'DistanceReceptorPlaneToDetHousing', - x00189427: 'IntensifierActiveShape', - x00189428: 'IntensifierActiveDimensions', - x00189429: 'PhysicalDetectorSize', - x00189430: 'PositionOfIsocenterProjection', - x00189432: 'FieldOfViewSequence', - x00189433: 'FieldOfViewDescription', - x00189434: 'ExposureControlSensingRegionsSeq', - x00189435: 'ExposureControlSensingRegionShape', - x00189436: 'ExposureControlSensRegionLeftEdge', - x00189437: 'ExposureControlSensRegionRightEdge', - x00189440: 'CenterOfCircExposControlSensRegion', - x00189441: 'RadiusOfCircExposControlSensRegion', - x00189447: 'ColumnAngulationPatient', - x00189449: 'BeamAngle', - x00189451: 'FrameDetectorParametersSequence', - x00189452: 'CalculatedAnatomyThickness', - x00189455: 'CalibrationSequence', - x00189456: 'ObjectThicknessSequence', - x00189457: 'PlaneIdentification', - x00189461: 'FieldOfViewDimensionsInFloat', - x00189462: 'IsocenterReferenceSystemSequence', - x00189463: 'PositionerIsocenterPrimaryAngle', - x00189464: 'PositionerIsocenterSecondaryAngle', - x00189465: 'PositionerIsocenterDetRotAngle', - x00189466: 'TableXPositionToIsocenter', - x00189467: 'TableYPositionToIsocenter', - x00189468: 'TableZPositionToIsocenter', - x00189469: 'TableHorizontalRotationAngle', - x00189470: 'TableHeadTiltAngle', - x00189471: 'TableCradleTiltAngle', - x00189472: 'FrameDisplayShutterSequence', - x00189473: 'AcquiredImageAreaDoseProduct', - x00189474: 'CArmPositionerTabletopRelationship', - x00189476: 'XRayGeometrySequence', - x00189477: 'IrradiationEventIDSequence', - x00189504: 'XRay3DFrameTypeSequence', - x00189506: 'ContributingSourcesSequence', - x00189507: 'XRay3DAcquisitionSequence', - x00189508: 'PrimaryPositionerScanArc', - x00189509: 'SecondaryPositionerScanArc', - x00189510: 'PrimaryPositionerScanStartAngle', - x00189511: 'SecondaryPositionerScanStartAngle', - x00189514: 'PrimaryPositionerIncrement', - x00189515: 'SecondaryPositionerIncrement', - x00189516: 'StartAcquisitionDateTime', - x00189517: 'EndAcquisitionDateTime', - x00189524: 'ApplicationName', - x00189525: 'ApplicationVersion', - x00189526: 'ApplicationManufacturer', - x00189527: 'AlgorithmType', - x00189528: 'AlgorithmDescription', - x00189530: 'XRay3DReconstructionSequence', - x00189531: 'ReconstructionDescription', - x00189538: 'PerProjectionAcquisitionSequence', - x00189601: 'DiffusionBMatrixSequence', - x00189602: 'DiffusionBValueXX', - x00189603: 'DiffusionBValueXY', - x00189604: 'DiffusionBValueXZ', - x00189605: 'DiffusionBValueYY', - x00189606: 'DiffusionBValueYZ', - x00189607: 'DiffusionBValueZZ', - x00189701: 'DecayCorrectionDateTime', - x00189715: 'StartDensityThreshold', - x00189722: 'TerminationTimeThreshold', - x00189725: 'DetectorGeometry', - x00189727: 'AxialDetectorDimension', - x00189735: 'PETPositionSequence', - x00189739: 'NumberOfIterations', - x00189740: 'NumberOfSubsets', - x00189751: 'PETFrameTypeSequence', - x00189756: 'ReconstructionType', - x00189758: 'DecayCorrected', - x00189759: 'AttenuationCorrected', - x00189760: 'ScatterCorrected', - x00189761: 'DeadTimeCorrected', - x00189762: 'GantryMotionCorrected', - x00189763: 'PatientMotionCorrected', - x00189765: 'RandomsCorrected', - x00189767: 'SensitivityCalibrated', - x00189801: 'DepthsOfFocus', - x00189804: 'ExclusionStartDatetime', - x00189805: 'ExclusionDuration', - x00189807: 'ImageDataTypeSequence', - x00189808: 'DataType', - x0018980b: 'AliasedDataType', - x0018a001: 'ContributingEquipmentSequence', - x0018a002: 'ContributionDateTime', - x0018a003: 'ContributionDescription', - // x00191002: 'NumberOfCellsIInDetector', - // x00191003: 'CellNumberAtTheta', - // x00191004: 'CellSpacing', - // x0019100f: 'HorizFrameOfRef', - // x00191011: 'SeriesContrast', - // x00191012: 'LastPseq', - // x00191013: 'StartNumberForBaseline', - // x00191014: 'EndNumberForBaseline', - // x00191015: 'StartNumberForEnhancedScans', - // x00191016: 'EndNumberForEnhancedScans', - // x00191017: 'SeriesPlane', - // x00191018: 'FirstScanRas', - // x00191019: 'FirstScanLocation', - // x0019101a: 'LastScanRas', - // x0019101b: 'LastScanLoc', - // x0019101e: 'DisplayFieldOfView', - // x00191023: 'TableSpeed', - // x00191024: 'MidScanTime', - // x00191025: 'MidScanFlag', - // x00191026: 'DegreesOfAzimuth', - // x00191027: 'GantryPeriod', - // x0019102a: 'XRayOnPosition', - // x0019102b: 'XRayOffPosition', - // x0019102c: 'NumberOfTriggers', - // x0019102e: 'AngleOfFirstView', - // x0019102f: 'TriggerFrequency', - // x00191039: 'ScanFOVType', - // x00191040: 'StatReconFlag', - // x00191041: 'ComputeType', - // x00191042: 'SegmentNumber', - // x00191043: 'TotalSegmentsRequested', - // x00191044: 'InterscanDelay', - // x00191047: 'ViewCompressionFactor', - // x0019104a: 'TotalNoOfRefChannels', - // x0019104b: 'DataSizeForScanData', - // x00191052: 'ReconPostProcflag', - // x00191057: 'CTWaterNumber', - // x00191058: 'CTBoneNumber', - // x0019105a: 'AcquisitionDuration', - // x0019105e: 'NumberOfChannels', - // x0019105f: 'IncrementBetweenChannels', - // x00191060: 'StartingView', - // x00191061: 'NumberOfViews', - // x00191062: 'IncrementBetweenViews', - // x0019106a: 'DependantOnNoViewsProcessed', - // x0019106b: 'FieldOfViewInDetectorCells', - // x00191070: 'ValueOfBackProjectionButton', - // x00191071: 'SetIfFatqEstimatesWereUsed', - // x00191072: 'ZChanAvgOverViews', - // x00191073: 'AvgOfLeftRefChansOverViews', - // x00191074: 'MaxLeftChanOverViews', - // x00191075: 'AvgOfRightRefChansOverViews', - // x00191076: 'MaxRightChanOverViews', - // x0019107d: 'SecondEcho', - // x0019107e: 'NumberOfEchoes', - // x0019107f: 'TableDelta', - // x00191081: 'Contiguous', - // x00191084: 'PeakSAR', - // x00191085: 'MonitorSAR', - // x00191087: 'CardiacRepetitionTime', - // x00191088: 'ImagesPerCardiacCycle', - // x0019108a: 'ActualReceiveGainAnalog', - // x0019108b: 'ActualReceiveGainDigital', - // x0019108d: 'DelayAfterTrigger', - // x0019108f: 'Swappf', - // x00191090: 'PauseInterval', - // x00191091: 'PulseTime', - // x00191092: 'SliceOffsetOnFreqAxis', - // x00191093: 'CenterFrequency', - // x00191094: 'TransmitGain', - // x00191095: 'AnalogReceiverGain', - // x00191096: 'DigitalReceiverGain', - // x00191097: 'BitmapDefiningCVs', - // x00191098: 'CenterFreqMethod', - // x0019109b: 'PulseSeqMode', - // x0019109c: 'PulseSeqName', - // x0019109d: 'PulseSeqDate', - // x0019109e: 'InternalPulseSeqName', - // x0019109f: 'TransmittingCoil', - // x001910a0: 'SurfaceCoilType', - // x001910a1: 'ExtremityCoilFlag', - // x001910a2: 'RawDataRunNumber', - // x001910a3: 'CalibratedFieldStrength', - // x001910a4: 'SATFatWaterBone', - // x001910a5: 'ReceiveBandwidth', - // x001910a7: 'UserData01', - // x001910a8: 'UserData02', - // x001910a9: 'UserData03', - // x001910aa: 'UserData04', - // x001910ab: 'UserData05', - // x001910ac: 'UserData06', - // x001910ad: 'UserData07', - // x001910ae: 'UserData08', - // x001910af: 'UserData09', - // x001910b0: 'UserData10', - // x001910b1: 'UserData11', - // x001910b2: 'UserData12', - // x001910b3: 'UserData13', - // x001910b4: 'UserData14', - // x001910b5: 'UserData15', - // x001910b6: 'UserData16', - // x001910b7: 'UserData17', - // x001910b8: 'UserData18', - // x001910b9: 'UserData19', - // x001910ba: 'UserData20', - // x001910bb: 'UserData21', - // x001910bc: 'UserData22', - // x001910bd: 'UserData23', - // x001910be: 'ProjectionAngle', - // x001910c0: 'SaturationPlanes', - // x001910c1: 'SurfaceCoilIntensity', - // x001910c2: 'SATLocationR', - // x001910c3: 'SATLocationL', - // x001910c4: 'SATLocationA', - // x001910c5: 'SATLocationP', - // x001910c6: 'SATLocationH', - // x001910c7: 'SATLocationF', - // x001910c8: 'SATThicknessR-L', - // x001910c9: 'SATThicknessA-P', - // x001910ca: 'SATThicknessH-F', - // x001910cb: 'PrescribedFlowAxis', - // x001910cc: 'VelocityEncoding', - // x001910cd: 'ThicknessDisclaimer', - // x001910ce: 'PrescanType', - // x001910cf: 'PrescanStatus', - // x001910d0: 'RawDataType', - // x001910d2: 'ProjectionAlgorithm', - // x001910d3: 'ProjectionAlgorithm', - // x001910d5: 'FractionalEcho', - // x001910d6: 'PrepPulse', - // x001910d7: 'CardiacPhases', - // x001910d8: 'VariableEchoflag', - // x001910d9: 'ConcatenatedSAT', - // x001910da: 'ReferenceChannelUsed', - // x001910db: 'BackProjectorCoefficient', - // x001910dc: 'PrimarySpeedCorrectionUsed', - // x001910dd: 'OverrangeCorrectionUsed', - // x001910de: 'DynamicZAlphaValue', - // x001910df: 'UserData', - // x001910e0: 'UserData', - // x001910e2: 'VelocityEncodeScale', - // x001910f2: 'FastPhases', - // x001910f9: 'TransmissionGain', - x00200000: 'RelationshipGroupLength', - x0020000d: 'StudyInstanceUID', - x0020000e: 'SeriesInstanceUID', - x00200010: 'StudyID', - x00200011: 'SeriesNumber', - x00200012: 'AcquisitionNumber', - x00200013: 'InstanceNumber', - x00200014: 'IsotopeNumber', - x00200015: 'PhaseNumber', - x00200016: 'IntervalNumber', - x00200017: 'TimeSlotNumber', - x00200018: 'AngleNumber', - x00200019: 'ItemNumber', - x00200020: 'PatientOrientation', - x00200022: 'OverlayNumber', - x00200024: 'CurveNumber', - x00200026: 'LookupTableNumber', - x00200030: 'ImagePosition', - x00200032: 'ImagePositionPatient', - x00200035: 'ImageOrientation', - x00200037: 'ImageOrientationPatient', - x00200050: 'Location', - x00200052: 'FrameOfReferenceUID', - x00200060: 'Laterality', - x00200062: 'ImageLaterality', - x00200070: 'ImageGeometryType', - x00200080: 'MaskingImage', - x00200100: 'TemporalPositionIdentifier', - x00200105: 'NumberOfTemporalPositions', - x00200110: 'TemporalResolution', - x00200200: 'SynchronizationFrameOfReferenceUID', - x00201000: 'SeriesInStudy', - x00201001: 'AcquisitionsInSeries', - x00201002: 'ImagesInAcquisition', - x00201003: 'ImagesInSeries', - x00201004: 'AcquisitionsInStudy', - x00201005: 'ImagesInStudy', - x00201020: 'Reference', - x00201040: 'PositionReferenceIndicator', - x00201041: 'SliceLocation', - x00201070: 'OtherStudyNumbers', - x00201200: 'NumberOfPatientRelatedStudies', - x00201202: 'NumberOfPatientRelatedSeries', - x00201204: 'NumberOfPatientRelatedInstances', - x00201206: 'NumberOfStudyRelatedSeries', - x00201208: 'NumberOfStudyRelatedInstances', - x00201209: 'NumberOfSeriesRelatedInstances', - x002031xx: 'SourceImageIDs', - x00203401: 'ModifyingDeviceID', - x00203402: 'ModifiedImageID', - x00203403: 'ModifiedImageDate', - x00203404: 'ModifyingDeviceManufacturer', - x00203405: 'ModifiedImageTime', - x00203406: 'ModifiedImageDescription', - x00204000: 'ImageComments', - x00205000: 'OriginalImageIdentification', - x00205002: 'OriginalImageIdentNomenclature', - x00209056: 'StackID', - x00209057: 'InStackPositionNumber', - x00209071: 'FrameAnatomySequence', - x00209072: 'FrameLaterality', - x00209111: 'FrameContentSequence', - x00209113: 'PlanePositionSequence', - x00209116: 'PlaneOrientationSequence', - x00209128: 'TemporalPositionIndex', - x00209153: 'TriggerDelayTime', - x00209156: 'FrameAcquisitionNumber', - x00209157: 'DimensionIndexValues', - x00209158: 'FrameComments', - x00209161: 'ConcatenationUID', - x00209162: 'InConcatenationNumber', - x00209163: 'InConcatenationTotalNumber', - x00209164: 'DimensionOrganizationUID', - x00209165: 'DimensionIndexPointer', - x00209167: 'FunctionalGroupPointer', - x00209213: 'DimensionIndexPrivateCreator', - x00209221: 'DimensionOrganizationSequence', - x00209222: 'DimensionIndexSequence', - x00209228: 'ConcatenationFrameOffsetNumber', - x00209238: 'FunctionalGroupPrivateCreator', - x00209241: 'NominalPercentageOfCardiacPhase', - x00209245: 'NominalPercentOfRespiratoryPhase', - x00209246: 'StartingRespiratoryAmplitude', - x00209247: 'StartingRespiratoryPhase', - x00209248: 'EndingRespiratoryAmplitude', - x00209249: 'EndingRespiratoryPhase', - x00209250: 'RespiratoryTriggerType', - x00209251: 'RRIntervalTimeNominal', - x00209252: 'ActualCardiacTriggerDelayTime', - x00209253: 'RespiratorySynchronizationSequence', - x00209254: 'RespiratoryIntervalTime', - x00209255: 'NominalRespiratoryTriggerDelayTime', - x00209256: 'RespiratoryTriggerDelayThreshold', - x00209257: 'ActualRespiratoryTriggerDelayTime', - x00209301: 'ImagePositionVolume', - x00209302: 'ImageOrientationVolume', - x00209308: 'ApexPosition', - x00209421: 'DimensionDescriptionLabel', - x00209450: 'PatientOrientationInFrameSequence', - x00209453: 'FrameLabel', - x00209518: 'AcquisitionIndex', - x00209529: 'ContributingSOPInstancesRefSeq', - x00209536: 'ReconstructionIndex', - // x00211003: 'SeriesFromWhichPrescribed', - // x00211005: 'GenesisVersionNow', - // x00211007: 'SeriesRecordChecksum', - // x00211018: 'GenesisVersionNow', - // x00211019: 'AcqreconRecordChecksum', - // x00211020: 'TableStartLocation', - // x00211035: 'SeriesFromWhichPrescribed', - // x00211036: 'ImageFromWhichPrescribed', - // x00211037: 'ScreenFormat', - // x0021104a: 'AnatomicalReferenceForScout', - // x0021104f: 'LocationsInAcquisition', - // x00211050: 'GraphicallyPrescribed', - // x00211051: 'RotationFromSourceXRot', - // x00211052: 'RotationFromSourceYRot', - // x00211053: 'RotationFromSourceZRot', - // x00211054: 'ImagePosition', - // x00211055: 'ImageOrientation', - // x00211056: 'IntegerSlop', - // x00211057: 'IntegerSlop', - // x00211058: 'IntegerSlop', - // x00211059: 'IntegerSlop', - // x0021105a: 'IntegerSlop', - // x0021105b: 'FloatSlop', - // x0021105c: 'FloatSlop', - // x0021105d: 'FloatSlop', - // x0021105e: 'FloatSlop', - // x0021105f: 'FloatSlop', - // x00211081: 'AutoWindowLevelAlpha', - // x00211082: 'AutoWindowLevelBeta', - // x00211083: 'AutoWindowLevelWindow', - // x00211084: 'ToWindowLevelLevel', - // x00211090: 'TubeFocalSpotPosition', - // x00211091: 'BiopsyPosition', - // x00211092: 'BiopsyTLocation', - // x00211093: 'BiopsyRefLocation', - x00220001: 'LightPathFilterPassThroughWavelen', - x00220002: 'LightPathFilterPassBand', - x00220003: 'ImagePathFilterPassThroughWavelen', - x00220004: 'ImagePathFilterPassBand', - x00220005: 'PatientEyeMovementCommanded', - x00220006: 'PatientEyeMovementCommandCodeSeq', - x00220007: 'SphericalLensPower', - x00220008: 'CylinderLensPower', - x00220009: 'CylinderAxis', - x0022000a: 'EmmetropicMagnification', - x0022000b: 'IntraOcularPressure', - x0022000c: 'HorizontalFieldOfView', - x0022000d: 'PupilDilated', - x0022000e: 'DegreeOfDilation', - x00220010: 'StereoBaselineAngle', - x00220011: 'StereoBaselineDisplacement', - x00220012: 'StereoHorizontalPixelOffset', - x00220013: 'StereoVerticalPixelOffset', - x00220014: 'StereoRotation', - x00220015: 'AcquisitionDeviceTypeCodeSequence', - x00220016: 'IlluminationTypeCodeSequence', - x00220017: 'LightPathFilterTypeStackCodeSeq', - x00220018: 'ImagePathFilterTypeStackCodeSeq', - x00220019: 'LensesCodeSequence', - x0022001a: 'ChannelDescriptionCodeSequence', - x0022001b: 'RefractiveStateSequence', - x0022001c: 'MydriaticAgentCodeSequence', - x0022001d: 'RelativeImagePositionCodeSequence', - x00220020: 'StereoPairsSequence', - x00220021: 'LeftImageSequence', - x00220022: 'RightImageSequence', - x00220030: 'AxialLengthOfTheEye', - x00220031: 'OphthalmicFrameLocationSequence', - x00220032: 'ReferenceCoordinates', - x00220035: 'DepthSpatialResolution', - x00220036: 'MaximumDepthDistortion', - x00220037: 'AlongScanSpatialResolution', - x00220038: 'MaximumAlongScanDistortion', - x00220039: 'OphthalmicImageOrientation', - x00220041: 'DepthOfTransverseImage', - x00220042: 'MydriaticAgentConcUnitsSeq', - x00220048: 'AcrossScanSpatialResolution', - x00220049: 'MaximumAcrossScanDistortion', - x0022004e: 'MydriaticAgentConcentration', - x00220055: 'IlluminationWaveLength', - x00220056: 'IlluminationPower', - x00220057: 'IlluminationBandwidth', - x00220058: 'MydriaticAgentSequence', - // x00231001: 'NumberOfSeriesInStudy', - // x00231002: 'NumberOfUnarchivedSeries', - // x00231010: 'ReferenceImageField', - // x00231050: 'SummaryImage', - // x00231070: 'StartTimeSecsInFirstAxial', - // x00231074: 'NoofUpdatesToHeader', - // x0023107d: 'IndicatesIfTheStudyHasCompleteInfo', - // x00251006: 'LastPulseSequenceUsed', - // x00251007: 'ImagesInSeries', - // x00251010: 'LandmarkCounter', - // x00251011: 'NumberOfAcquisitions', - // x00251014: 'IndicatesNoofUpdatesToHeader', - // x00251017: 'SeriesCompleteFlag', - // x00251018: 'NumberOfImagesArchived', - // x00251019: 'LastImageNumberUsed', - // x0025101a: 'PrimaryReceiverSuiteAndHost', - // x00271006: 'ImageArchiveFlag', - // x00271010: 'ScoutType', - // x0027101c: 'VmaMamp', - // x0027101d: 'VmaPhase', - // x0027101e: 'VmaMod', - // x0027101f: 'VmaClip', - // x00271020: 'SmartScanOnOffFlag', - // x00271030: 'ForeignImageRevision', - // x00271031: 'ImagingMode', - // x00271032: 'PulseSequence', - // x00271033: 'ImagingOptions', - // x00271035: 'PlaneType', - // x00271036: 'ObliquePlane', - // x00271040: 'RASLetterOfImageLocation', - // x00271041: 'ImageLocation', - // x00271042: 'CenterRCoordOfPlaneImage', - // x00271043: 'CenterACoordOfPlaneImage', - // x00271044: 'CenterSCoordOfPlaneImage', - // x00271045: 'NormalRCoord', - // x00271046: 'NormalACoord', - // x00271047: 'NormalSCoord', - // x00271048: 'RCoordOfTopRightCorner', - // x00271049: 'ACoordOfTopRightCorner', - // x0027104a: 'SCoordOfTopRightCorner', - // x0027104b: 'RCoordOfBottomRightCorner', - // x0027104c: 'ACoordOfBottomRightCorner', - // x0027104d: 'SCoordOfBottomRightCorner', - // x00271050: 'TableStartLocation', - // x00271051: 'TableEndLocation', - // x00271052: 'RASLetterForSideOfImage', - // x00271053: 'RASLetterForAnteriorPosterior', - // x00271054: 'RASLetterForScoutStartLoc', - // x00271055: 'RASLetterForScoutEndLoc', - // x00271060: 'ImageDimensionX', - // x00271061: 'ImageDimensionY', - // x00271062: 'NumberOfExcitations', - x00280000: 'ImagePresentationGroupLength', - x00280002: 'SamplesPerPixel', - x00280003: 'SamplesPerPixelUsed', - x00280004: 'PhotometricInterpretation', - x00280005: 'ImageDimensions', - x00280006: 'PlanarConfiguration', - x00280008: 'NumberOfFrames', - x00280009: 'FrameIncrementPointer', - x0028000a: 'FrameDimensionPointer', - x00280010: 'Rows', - x00280011: 'Columns', - x00280012: 'Planes', - x00280014: 'UltrasoundColorDataPresent', - x00280030: 'PixelSpacing', - x00280031: 'ZoomFactor', - x00280032: 'ZoomCenter', - x00280034: 'PixelAspectRatio', - x00280040: 'ImageFormat', - x00280050: 'ManipulatedImage', - x00280051: 'CorrectedImage', - x0028005f: 'CompressionRecognitionCode', - x00280060: 'CompressionCode', - x00280061: 'CompressionOriginator', - x00280062: 'CompressionLabel', - x00280063: 'CompressionDescription', - x00280065: 'CompressionSequence', - x00280066: 'CompressionStepPointers', - x00280068: 'RepeatInterval', - x00280069: 'BitsGrouped', - x00280070: 'PerimeterTable', - x00280071: 'PerimeterValue', - x00280080: 'PredictorRows', - x00280081: 'PredictorColumns', - x00280082: 'PredictorConstants', - x00280090: 'BlockedPixels', - x00280091: 'BlockRows', - x00280092: 'BlockColumns', - x00280093: 'RowOverlap', - x00280094: 'ColumnOverlap', - x00280100: 'BitsAllocated', - x00280101: 'BitsStored', - x00280102: 'HighBit', - x00280103: 'PixelRepresentation', - x00280104: 'SmallestValidPixelValue', - x00280105: 'LargestValidPixelValue', - x00280106: 'SmallestImagePixelValue', - x00280107: 'LargestImagePixelValue', - x00280108: 'SmallestPixelValueInSeries', - x00280109: 'LargestPixelValueInSeries', - x00280110: 'SmallestImagePixelValueInPlane', - x00280111: 'LargestImagePixelValueInPlane', - x00280120: 'PixelPaddingValue', - x00280121: 'PixelPaddingRangeLimit', - x00280200: 'ImageLocation', - x00280300: 'QualityControlImage', - x00280301: 'BurnedInAnnotation', - x00280400: 'TransformLabel', - x00280401: 'TransformVersionNumber', - x00280402: 'NumberOfTransformSteps', - x00280403: 'SequenceOfCompressedData', - x00280404: 'DetailsOfCoefficients', - x002804x2: 'CoefficientCoding', - x002804x3: 'CoefficientCodingPointers', - x00280700: 'DCTLabel', - x00280701: 'DataBlockDescription', - x00280702: 'DataBlock', - x00280710: 'NormalizationFactorFormat', - x00280720: 'ZonalMapNumberFormat', - x00280721: 'ZonalMapLocation', - x00280722: 'ZonalMapFormat', - x00280730: 'AdaptiveMapFormat', - x00280740: 'CodeNumberFormat', - x002808x0: 'CodeLabel', - x002808x2: 'NumberOfTables', - x002808x3: 'CodeTableLocation', - x002808x4: 'BitsForCodeWord', - x002808x8: 'ImageDataLocation', - x00280a02: 'PixelSpacingCalibrationType', - x00280a04: 'PixelSpacingCalibrationDescription', - x00281040: 'PixelIntensityRelationship', - x00281041: 'PixelIntensityRelationshipSign', - x00281050: 'WindowCenter', - x00281051: 'WindowWidth', - x00281052: 'RescaleIntercept', - x00281053: 'RescaleSlope', - x00281054: 'RescaleType', - x00281055: 'WindowCenterAndWidthExplanation', - x00281056: 'VOI_LUTFunction', - x00281080: 'GrayScale', - x00281090: 'RecommendedViewingMode', - x00281100: 'GrayLookupTableDescriptor', - x00281101: 'RedPaletteColorTableDescriptor', - x00281102: 'GreenPaletteColorTableDescriptor', - x00281103: 'BluePaletteColorTableDescriptor', - x00281111: 'LargeRedPaletteColorTableDescr', - x00281112: 'LargeGreenPaletteColorTableDescr', - x00281113: 'LargeBluePaletteColorTableDescr', - x00281199: 'PaletteColorTableUID', - x00281200: 'GrayLookupTableData', - x00281201: 'RedPaletteColorTableData', - x00281202: 'GreenPaletteColorTableData', - x00281203: 'BluePaletteColorTableData', - x00281211: 'LargeRedPaletteColorTableData', - x00281212: 'LargeGreenPaletteColorTableData', - x00281213: 'LargeBluePaletteColorTableData', - x00281214: 'LargePaletteColorLookupTableUID', - x00281221: 'SegmentedRedColorTableData', - x00281222: 'SegmentedGreenColorTableData', - x00281223: 'SegmentedBlueColorTableData', - x00281300: 'BreastImplantPresent', - x00281350: 'PartialView', - x00281351: 'PartialViewDescription', - x00281352: 'PartialViewCodeSequence', - x0028135a: 'SpatialLocationsPreserved', - x00281402: 'DataPathAssignment', - x00281404: 'BlendingLUT1Sequence', - x00281406: 'BlendingWeightConstant', - x00281408: 'BlendingLookupTableData', - x0028140c: 'BlendingLUT2Sequence', - x0028140e: 'DataPathID', - x0028140f: 'RGBLUTTransferFunction', - x00281410: 'AlphaLUTTransferFunction', - x00282000: 'ICCProfile', - x00282110: 'LossyImageCompression', - x00282112: 'LossyImageCompressionRatio', - x00282114: 'LossyImageCompressionMethod', - x00283000: 'ModalityLUTSequence', - x00283002: 'LUTDescriptor', - x00283003: 'LUTExplanation', - x00283004: 'ModalityLUTType', - x00283006: 'LUTData', - x00283010: 'VOILUTSequence', - x00283110: 'SoftcopyVOILUTSequence', - x00284000: 'ImagePresentationComments', - x00285000: 'BiPlaneAcquisitionSequence', - x00286010: 'RepresentativeFrameNumber', - x00286020: 'FrameNumbersOfInterest', - x00286022: 'FrameOfInterestDescription', - x00286023: 'FrameOfInterestType', - x00286030: 'MaskPointers', - x00286040: 'RWavePointer', - x00286100: 'MaskSubtractionSequence', - x00286101: 'MaskOperation', - x00286102: 'ApplicableFrameRange', - x00286110: 'MaskFrameNumbers', - x00286112: 'ContrastFrameAveraging', - x00286114: 'MaskSubPixelShift', - x00286120: 'TIDOffset', - x00286190: 'MaskOperationExplanation', - x00287fe0: 'PixelDataProviderURL', - x00289001: 'DataPointRows', - x00289002: 'DataPointColumns', - x00289003: 'SignalDomainColumns', - x00289099: 'LargestMonochromePixelValue', - x00289108: 'DataRepresentation', - x00289110: 'PixelMeasuresSequence', - x00289132: 'FrameVOILUTSequence', - x00289145: 'PixelValueTransformationSequence', - x00289235: 'SignalDomainRows', - x00289411: 'DisplayFilterPercentage', - x00289415: 'FramePixelShiftSequence', - x00289416: 'SubtractionItemID', - x00289422: 'PixelIntensityRelationshipLUTSeq', - x00289443: 'FramePixelDataPropertiesSequence', - x00289444: 'GeometricalProperties', - x00289445: 'GeometricMaximumDistortion', - x00289446: 'ImageProcessingApplied', - x00289454: 'MaskSelectionMode', - x00289474: 'LUTFunction', - x00289478: 'MaskVisibilityPercentage', - x00289501: 'PixelShiftSequence', - x00289502: 'RegionPixelShiftSequence', - x00289503: 'VerticesOfTheRegion', - x00289506: 'PixelShiftFrameRange', - x00289507: 'LUTFrameRange', - x00289520: 'ImageToEquipmentMappingMatrix', - x00289537: 'EquipmentCoordinateSystemID', - // x00291004: 'LowerRangeOfPixels1a', - // x00291005: 'LowerRangeOfPixels1b', - // x00291006: 'LowerRangeOfPixels1c', - // x00291007: 'LowerRangeOfPixels1d', - // x00291008: 'LowerRangeOfPixels1e', - // x00291009: 'LowerRangeOfPixels1f', - // x0029100a: 'LowerRangeOfPixels1g', - // x00291015: 'LowerRangeOfPixels1h', - // x00291016: 'LowerRangeOfPixels1i', - // x00291017: 'LowerRangeOfPixels2', - // x00291018: 'UpperRangeOfPixels2', - // x0029101a: 'LenOfTotHdrInBytes', - // x00291026: 'VersionOfTheHdrStruct', - // x00291034: 'AdvantageCompOverflow', - // x00291035: 'AdvantageCompUnderflow', - x00320000: 'StudyGroupLength', - x0032000a: 'StudyStatusID', - x0032000c: 'StudyPriorityID', - x00320012: 'StudyIDIssuer', - x00320032: 'StudyVerifiedDate', - x00320033: 'StudyVerifiedTime', - x00320034: 'StudyReadDate', - x00320035: 'StudyReadTime', - x00321000: 'ScheduledStudyStartDate', - x00321001: 'ScheduledStudyStartTime', - x00321010: 'ScheduledStudyStopDate', - x00321011: 'ScheduledStudyStopTime', - x00321020: 'ScheduledStudyLocation', - x00321021: 'ScheduledStudyLocationAETitle', - x00321030: 'ReasonForStudy', - x00321031: 'RequestingPhysicianIDSequence', - x00321032: 'RequestingPhysician', - x00321033: 'RequestingService', - x00321040: 'StudyArrivalDate', - x00321041: 'StudyArrivalTime', - x00321050: 'StudyCompletionDate', - x00321051: 'StudyCompletionTime', - x00321055: 'StudyComponentStatusID', - x00321060: 'RequestedProcedureDescription', - x00321064: 'RequestedProcedureCodeSequence', - x00321070: 'RequestedContrastAgent', - x00324000: 'StudyComments', - x00380004: 'ReferencedPatientAliasSequence', - x00380008: 'VisitStatusID', - x00380010: 'AdmissionID', - x00380011: 'IssuerOfAdmissionID', - x00380016: 'RouteOfAdmissions', - x0038001a: 'ScheduledAdmissionDate', - x0038001b: 'ScheduledAdmissionTime', - x0038001c: 'ScheduledDischargeDate', - x0038001d: 'ScheduledDischargeTime', - x0038001e: 'ScheduledPatientInstitResidence', - x00380020: 'AdmittingDate', - x00380021: 'AdmittingTime', - x00380030: 'DischargeDate', - x00380032: 'DischargeTime', - x00380040: 'DischargeDiagnosisDescription', - x00380044: 'DischargeDiagnosisCodeSequence', - x00380050: 'SpecialNeeds', - x00380060: 'ServiceEpisodeID', - x00380061: 'IssuerOfServiceEpisodeID', - x00380062: 'ServiceEpisodeDescription', - x00380100: 'PertinentDocumentsSequence', - x00380300: 'CurrentPatientLocation', - x00380400: 'PatientInstitutionResidence', - x00380500: 'PatientState', - x00380502: 'PatientClinicalTrialParticipSeq', - x00384000: 'VisitComments', - x003a0004: 'WaveformOriginality', - x003a0005: 'NumberOfWaveformChannels', - x003a0010: 'NumberOfWaveformSamples', - x003a001a: 'SamplingFrequency', - x003a0020: 'MultiplexGroupLabel', - x003a0200: 'ChannelDefinitionSequence', - x003a0202: 'WaveformChannelNumber', - x003a0203: 'ChannelLabel', - x003a0205: 'ChannelStatus', - x003a0208: 'ChannelSourceSequence', - x003a0209: 'ChannelSourceModifiersSequence', - x003a020a: 'SourceWaveformSequence', - x003a020c: 'ChannelDerivationDescription', - x003a0210: 'ChannelSensitivity', - x003a0211: 'ChannelSensitivityUnitsSequence', - x003a0212: 'ChannelSensitivityCorrectionFactor', - x003a0213: 'ChannelBaseline', - x003a0214: 'ChannelTimeSkew', - x003a0215: 'ChannelSampleSkew', - x003a0218: 'ChannelOffset', - x003a021a: 'WaveformBitsStored', - x003a0220: 'FilterLowFrequency', - x003a0221: 'FilterHighFrequency', - x003a0222: 'NotchFilterFrequency', - x003a0223: 'NotchFilterBandwidth', - x003a0230: 'WaveformDataDisplayScale', - x003a0231: 'WaveformDisplayBkgCIELabValue', - x003a0240: 'WaveformPresentationGroupSequence', - x003a0241: 'PresentationGroupNumber', - x003a0242: 'ChannelDisplaySequence', - x003a0244: 'ChannelRecommendDisplayCIELabValue', - x003a0245: 'ChannelPosition', - x003a0246: 'DisplayShadingFlag', - x003a0247: 'FractionalChannelDisplayScale', - x003a0248: 'AbsoluteChannelDisplayScale', - x003a0300: 'MultiplexAudioChannelsDescrCodeSeq', - x003a0301: 'ChannelIdentificationCode', - x003a0302: 'ChannelMode', - x00400001: 'ScheduledStationAETitle', - x00400002: 'ScheduledProcedureStepStartDate', - x00400003: 'ScheduledProcedureStepStartTime', - x00400004: 'ScheduledProcedureStepEndDate', - x00400005: 'ScheduledProcedureStepEndTime', - x00400006: 'ScheduledPerformingPhysiciansName', - x00400007: 'ScheduledProcedureStepDescription', - x00400008: 'ScheduledProtocolCodeSequence', - x00400009: 'ScheduledProcedureStepID', - x0040000a: 'StageCodeSequence', - x0040000b: 'ScheduledPerformingPhysicianIDSeq', - x00400010: 'ScheduledStationName', - x00400011: 'ScheduledProcedureStepLocation', - x00400012: 'PreMedication', - x00400020: 'ScheduledProcedureStepStatus', - x00400031: 'LocalNamespaceEntityID', - x00400032: 'UniversalEntityID', - x00400033: 'UniversalEntityIDType', - x00400035: 'IdentifierTypeCode', - x00400036: 'AssigningFacilitySequence', - x00400100: 'ScheduledProcedureStepSequence', - x00400220: 'ReferencedNonImageCompositeSOPSeq', - x00400241: 'PerformedStationAETitle', - x00400242: 'PerformedStationName', - x00400243: 'PerformedLocation', - x00400244: 'PerformedProcedureStepStartDate', - x00400245: 'PerformedProcedureStepStartTime', - x00400250: 'PerformedProcedureStepEndDate', - x00400251: 'PerformedProcedureStepEndTime', - x00400252: 'PerformedProcedureStepStatus', - x00400253: 'PerformedProcedureStepID', - x00400254: 'PerformedProcedureStepDescription', - x00400255: 'PerformedProcedureTypeDescription', - x00400260: 'PerformedProtocolCodeSequence', - x00400261: 'PerformedProtocolType', - x00400270: 'ScheduledStepAttributesSequence', - x00400275: 'RequestAttributesSequence', - x00400280: 'CommentsOnPerformedProcedureStep', - x00400281: 'ProcStepDiscontinueReasonCodeSeq', - x00400293: 'QuantitySequence', - x00400294: 'Quantity', - x00400295: 'MeasuringUnitsSequence', - x00400296: 'BillingItemSequence', - x00400300: 'TotalTimeOfFluoroscopy', - x00400301: 'TotalNumberOfExposures', - x00400302: 'EntranceDose', - x00400303: 'ExposedArea', - x00400306: 'DistanceSourceToEntrance', - x00400307: 'DistanceSourceToSupport', - x0040030e: 'ExposureDoseSequence', - x00400310: 'CommentsOnRadiationDose', - x00400312: 'XRayOutput', - x00400314: 'HalfValueLayer', - x00400316: 'OrganDose', - x00400318: 'OrganExposed', - x00400320: 'BillingProcedureStepSequence', - x00400321: 'FilmConsumptionSequence', - x00400324: 'BillingSuppliesAndDevicesSequence', - x00400330: 'ReferencedProcedureStepSequence', - x00400340: 'PerformedSeriesSequence', - x00400400: 'CommentsOnScheduledProcedureStep', - x00400440: 'ProtocolContextSequence', - x00400441: 'ContentItemModifierSequence', - x0040050a: 'SpecimenAccessionNumber', - x00400512: 'ContainerIdentifier', - x0040051a: 'ContainerDescription', - x00400550: 'SpecimenSequence', - x00400551: 'SpecimenIdentifier', - x00400552: 'SpecimenDescriptionSequenceTrial', - x00400553: 'SpecimenDescriptionTrial', - x00400554: 'SpecimenUID', - x00400555: 'AcquisitionContextSequence', - x00400556: 'AcquisitionContextDescription', - x0040059a: 'SpecimenTypeCodeSequence', - x00400600: 'SpecimenShortDescription', - x004006fa: 'SlideIdentifier', - x0040071a: 'ImageCenterPointCoordinatesSeq', - x0040072a: 'XOffsetInSlideCoordinateSystem', - x0040073a: 'YOffsetInSlideCoordinateSystem', - x0040074a: 'ZOffsetInSlideCoordinateSystem', - x004008d8: 'PixelSpacingSequence', - x004008da: 'CoordinateSystemAxisCodeSequence', - x004008ea: 'MeasurementUnitsCodeSequence', - x004009f8: 'VitalStainCodeSequenceTrial', - x00401001: 'RequestedProcedureID', - x00401002: 'ReasonForRequestedProcedure', - x00401003: 'RequestedProcedurePriority', - x00401004: 'PatientTransportArrangements', - x00401005: 'RequestedProcedureLocation', - x00401006: 'PlacerOrderNumber-Procedure', - x00401007: 'FillerOrderNumber-Procedure', - x00401008: 'ConfidentialityCode', - x00401009: 'ReportingPriority', - x0040100a: 'ReasonForRequestedProcedureCodeSeq', - x00401010: 'NamesOfIntendedRecipientsOfResults', - x00401011: 'IntendedRecipientsOfResultsIDSeq', - x00401101: 'PersonIdentificationCodeSequence', - x00401102: 'PersonAddress', - x00401103: 'PersonTelephoneNumbers', - x00401400: 'RequestedProcedureComments', - x00402001: 'ReasonForImagingServiceRequest', - x00402004: 'IssueDateOfImagingServiceRequest', - x00402005: 'IssueTimeOfImagingServiceRequest', - x00402006: 'PlacerOrderNumberImagingServiceRequestRetired', - x00402007: 'FillerOrderNumberImagingServiceRequestRetired', - x00402008: 'OrderEnteredBy', - x00402009: 'OrderEntererLocation', - x00402010: 'OrderCallbackPhoneNumber', - x00402016: 'PlacerOrderNum-ImagingServiceReq', - x00402017: 'FillerOrderNum-ImagingServiceReq', - x00402400: 'ImagingServiceRequestComments', - x00403001: 'ConfidentialityOnPatientDataDescr', - x00404001: 'GenPurposeScheduledProcStepStatus', - x00404002: 'GenPurposePerformedProcStepStatus', - x00404003: 'GenPurposeSchedProcStepPriority', - x00404004: 'SchedProcessingApplicationsCodeSeq', - x00404005: 'SchedProcedureStepStartDateAndTime', - x00404006: 'MultipleCopiesFlag', - x00404007: 'PerformedProcessingAppsCodeSeq', - x00404009: 'HumanPerformerCodeSequence', - x00404010: 'SchedProcStepModificationDateTime', - x00404011: 'ExpectedCompletionDateAndTime', - x00404015: 'ResultingGenPurposePerfProcStepSeq', - x00404016: 'RefGenPurposeSchedProcStepSeq', - x00404018: 'ScheduledWorkitemCodeSequence', - x00404019: 'PerformedWorkitemCodeSequence', - x00404020: 'InputAvailabilityFlag', - x00404021: 'InputInformationSequence', - x00404022: 'RelevantInformationSequence', - x00404023: 'RefGenPurSchedProcStepTransUID', - x00404025: 'ScheduledStationNameCodeSequence', - x00404026: 'ScheduledStationClassCodeSequence', - x00404027: 'SchedStationGeographicLocCodeSeq', - x00404028: 'PerformedStationNameCodeSequence', - x00404029: 'PerformedStationClassCodeSequence', - x00404030: 'PerformedStationGeogLocCodeSeq', - x00404031: 'RequestedSubsequentWorkItemCodeSeq', - x00404032: 'NonDICOMOutputCodeSequence', - x00404033: 'OutputInformationSequence', - x00404034: 'ScheduledHumanPerformersSequence', - x00404035: 'ActualHumanPerformersSequence', - x00404036: 'HumanPerformersOrganization', - x00404037: 'HumanPerformerName', - x00404040: 'RawDataHandling', - x00408302: 'EntranceDoseInMilliGy', - x00409094: 'RefImageRealWorldValueMappingSeq', - x00409096: 'RealWorldValueMappingSequence', - x00409098: 'PixelValueMappingCodeSequence', - x00409210: 'LUTLabel', - x00409211: 'RealWorldValueLastValueMapped', - x00409212: 'RealWorldValueLUTData', - x00409216: 'RealWorldValueFirstValueMapped', - x00409224: 'RealWorldValueIntercept', - x00409225: 'RealWorldValueSlope', - x0040a010: 'RelationshipType', - x0040a027: 'VerifyingOrganization', - x0040a030: 'VerificationDateTime', - x0040a032: 'ObservationDateTime', - x0040a040: 'ValueType', - x0040a043: 'ConceptNameCodeSequence', - x0040a050: 'ContinuityOfContent', - x0040a073: 'VerifyingObserverSequence', - x0040a075: 'VerifyingObserverName', - x0040a078: 'AuthorObserverSequence', - x0040a07a: 'ParticipantSequence', - x0040a07c: 'CustodialOrganizationSequence', - x0040a080: 'ParticipationType', - x0040a082: 'ParticipationDateTime', - x0040a084: 'ObserverType', - x0040a088: 'VerifyingObserverIdentCodeSequence', - x0040a090: 'EquivalentCDADocumentSequence', - x0040a0b0: 'ReferencedWaveformChannels', - x0040a120: 'DateTime', - x0040a121: 'Date', - x0040a122: 'Time', - x0040a123: 'PersonName', - x0040a124: 'UID', - x0040a130: 'TemporalRangeType', - x0040a132: 'ReferencedSamplePositions', - x0040a136: 'ReferencedFrameNumbers', - x0040a138: 'ReferencedTimeOffsets', - x0040a13a: 'ReferencedDateTime', - x0040a160: 'TextValue', - x0040a168: 'ConceptCodeSequence', - x0040a170: 'PurposeOfReferenceCodeSequence', - x0040a180: 'AnnotationGroupNumber', - x0040a195: 'ModifierCodeSequence', - x0040a300: 'MeasuredValueSequence', - x0040a301: 'NumericValueQualifierCodeSequence', - x0040a30a: 'NumericValue', - x0040a353: 'AddressTrial', - x0040a354: 'TelephoneNumberTrial', - x0040a360: 'PredecessorDocumentsSequence', - x0040a370: 'ReferencedRequestSequence', - x0040a372: 'PerformedProcedureCodeSequence', - x0040a375: 'CurrentRequestedProcEvidenceSeq', - x0040a385: 'PertinentOtherEvidenceSequence', - x0040a390: 'HL7StructuredDocumentRefSeq', - x0040a491: 'CompletionFlag', - x0040a492: 'CompletionFlagDescription', - x0040a493: 'VerificationFlag', - x0040a494: 'ArchiveRequested', - x0040a496: 'PreliminaryFlag', - x0040a504: 'ContentTemplateSequence', - x0040a525: 'IdenticalDocumentsSequence', - x0040a730: 'ContentSequence', - x0040b020: 'AnnotationSequence', - x0040db00: 'TemplateIdentifier', - x0040db06: 'TemplateVersion', - x0040db07: 'TemplateLocalVersion', - x0040db0b: 'TemplateExtensionFlag', - x0040db0c: 'TemplateExtensionOrganizationUID', - x0040db0d: 'TemplateExtensionCreatorUID', - x0040db73: 'ReferencedContentItemIdentifier', - x0040e001: 'HL7InstanceIdentifier', - x0040e004: 'HL7DocumentEffectiveTime', - x0040e006: 'HL7DocumentTypeCodeSequence', - x0040e010: 'RetrieveURI', - x0040e011: 'RetrieveLocationUID', - x00420010: 'DocumentTitle', - x00420011: 'EncapsulatedDocument', - x00420012: 'MIMETypeOfEncapsulatedDocument', - x00420013: 'SourceInstanceSequence', - x00420014: 'ListOfMIMETypes', - // x00431001: 'BitmapOfPrescanOptions', - // x00431002: 'GradientOffsetInX', - // x00431003: 'GradientOffsetInY', - // x00431004: 'GradientOffsetInZ', - // x00431005: 'ImgIsOriginalOrUnoriginal', - // x00431006: 'NumberOfEPIShots', - // x00431007: 'ViewsPerSegment', - // x00431008: 'RespiratoryRateBpm', - // x00431009: 'RespiratoryTriggerPoint', - // x0043100a: 'TypeOfReceiverUsed', - // x0043100b: 'PeakRateOfChangeOfGradientField', - // x0043100c: 'LimitsInUnitsOfPercent', - // x0043100d: 'PSDEstimatedLimit', - // x0043100e: 'PSDEstimatedLimitInTeslaPerSecond', - // x0043100f: 'Saravghead', - // x00431010: 'WindowValue', - // x00431011: 'TotalInputViews', - // x00431012: 'X-RayChain', - // x00431013: 'DeconKernelParameters', - // x00431014: 'CalibrationParameters', - // x00431015: 'TotalOutputViews', - // x00431016: 'NumberOfOverranges', - // x00431017: 'IBHImageScaleFactors', - // x00431018: 'BBHCoefficients', - // x00431019: 'NumberOfBBHChainsToBlend', - // x0043101a: 'StartingChannelNumber', - // x0043101b: 'PpscanParameters', - // x0043101c: 'GEImageIntegrity', - // x0043101d: 'LevelValue', - // x0043101e: 'DeltaStartTime', - // x0043101f: 'MaxOverrangesInAView', - // x00431020: 'AvgOverrangesAllViews', - // x00431021: 'CorrectedAfterGlowTerms', - // x00431025: 'ReferenceChannels', - // x00431026: 'NoViewsRefChansBlocked', - // x00431027: 'ScanPitchRatio', - // x00431028: 'UniqueImageIden', - // x00431029: 'HistogramTables', - // x0043102a: 'UserDefinedData', - // x0043102b: 'PrivateScanOptions', - // x0043102c: 'EffectiveEchoSpacing', - // x0043102d: 'StringSlopField1', - // x0043102e: 'StringSlopField2', - // x0043102f: 'RawDataType', - // x00431030: 'RawDataType', - // x00431031: 'RACordOfTargetReconCenter', - // x00431032: 'RawDataType', - // x00431033: 'NegScanspacing', - // x00431034: 'OffsetFrequency', - // x00431035: 'UserUsageTag', - // x00431036: 'UserFillMapMSW', - // x00431037: 'UserFillMapLSW', - // x00431038: 'User25-48', - // x00431039: 'SlopInt6-9', - // x00431040: 'TriggerOnPosition', - // x00431041: 'DegreeOfRotation', - // x00431042: 'DASTriggerSource', - // x00431043: 'DASFpaGain', - // x00431044: 'DASOutputSource', - // x00431045: 'DASAdInput', - // x00431046: 'DASCalMode', - // x00431047: 'DASCalFrequency', - // x00431048: 'DASRegXm', - // x00431049: 'DASAutoZero', - // x0043104a: 'StartingChannelOfView', - // x0043104b: 'DASXmPattern', - // x0043104c: 'TGGCTriggerMode', - // x0043104d: 'StartScanToXrayOnDelay', - // x0043104e: 'DurationOfXrayOn', - // x00431060: 'SlopInt10-17', - // x00431061: 'ScannerStudyEntityUID', - // x00431062: 'ScannerStudyID', - // x0043106f: 'ScannerTableEntry', - x00440001: 'ProductPackageIdentifier', - x00440002: 'SubstanceAdministrationApproval', - x00440003: 'ApprovalStatusFurtherDescription', - x00440004: 'ApprovalStatusDateTime', - x00440007: 'ProductTypeCodeSequence', - x00440008: 'ProductName', - x00440009: 'ProductDescription', - x0044000a: 'ProductLotIdentifier', - x0044000b: 'ProductExpirationDateTime', - x00440010: 'SubstanceAdministrationDateTime', - x00440011: 'SubstanceAdministrationNotes', - x00440012: 'SubstanceAdministrationDeviceID', - x00440013: 'ProductParameterSequence', - x00440019: 'SubstanceAdminParameterSeq', - // x00451001: 'NumberOfMacroRowsInDetector', - // x00451002: 'MacroWidthAtISOCenter', - // x00451003: 'DASType', - // x00451004: 'DASGain', - // x00451005: 'DASTemperature', - // x00451006: 'TableDirectionInOrOut', - // x00451007: 'ZSmoothingFactor', - // x00451008: 'ViewWeightingMode', - // x00451009: 'SigmaRowNumberWhichRowsWereUsed', - // x0045100a: 'MinimumDasValueFoundInTheScanData', - // x0045100b: 'MaximumOffsetShiftValueUsed', - // x0045100c: 'NumberOfViewsShifted', - // x0045100d: 'ZTrackingFlag', - // x0045100e: 'MeanZError', - // x0045100f: 'ZTrackingMaximumError', - // x00451010: 'StartingViewForRow2a', - // x00451011: 'NumberOfViewsInRow2a', - // x00451012: 'StartingViewForRow1a', - // x00451013: 'SigmaMode', - // x00451014: 'NumberOfViewsInRow1a', - // x00451015: 'StartingViewForRow2b', - // x00451016: 'NumberOfViewsInRow2b', - // x00451017: 'StartingViewForRow1b', - // x00451018: 'NumberOfViewsInRow1b', - // x00451019: 'AirFilterCalibrationDate', - // x0045101a: 'AirFilterCalibrationTime', - // x0045101b: 'PhantomCalibrationDate', - // x0045101c: 'PhantomCalibrationTime', - // x0045101d: 'ZSlopeCalibrationDate', - // x0045101e: 'ZSlopeCalibrationTime', - // x0045101f: 'CrosstalkCalibrationDate', - // x00451020: 'CrosstalkCalibrationTime', - // x00451021: 'IterboneOptionFlag', - // x00451022: 'PeristalticFlagOption', - x00460012: 'LensDescription', - x00460014: 'RightLensSequence', - x00460015: 'LeftLensSequence', - x00460018: 'CylinderSequence', - x00460028: 'PrismSequence', - x00460030: 'HorizontalPrismPower', - x00460032: 'HorizontalPrismBase', - x00460034: 'VerticalPrismPower', - x00460036: 'VerticalPrismBase', - x00460038: 'LensSegmentType', - x00460040: 'OpticalTransmittance', - x00460042: 'ChannelWidth', - x00460044: 'PupilSize', - x00460046: 'CornealSize', - x00460060: 'DistancePupillaryDistance', - x00460062: 'NearPupillaryDistance', - x00460064: 'OtherPupillaryDistance', - x00460075: 'RadiusOfCurvature', - x00460076: 'KeratometricPower', - x00460077: 'KeratometricAxis', - x00460092: 'BackgroundColor', - x00460094: 'Optotype', - x00460095: 'OptotypePresentation', - x00460100: 'AddNearSequence', - x00460101: 'AddIntermediateSequence', - x00460102: 'AddOtherSequence', - x00460104: 'AddPower', - x00460106: 'ViewingDistance', - x00460125: 'ViewingDistanceType', - x00460135: 'VisualAcuityModifiers', - x00460137: 'DecimalVisualAcuity', - x00460139: 'OptotypeDetailedDefinition', - x00460146: 'SpherePower', - x00460147: 'CylinderPower', - x00500004: 'CalibrationImage', - x00500010: 'DeviceSequence', - x00500014: 'DeviceLength', - x00500015: 'ContainerComponentWidth', - x00500016: 'DeviceDiameter', - x00500017: 'DeviceDiameterUnits', - x00500018: 'DeviceVolume', - x00500019: 'InterMarkerDistance', - x0050001b: 'ContainerComponentID', - x00500020: 'DeviceDescription', - x00540010: 'EnergyWindowVector', - x00540011: 'NumberOfEnergyWindows', - x00540012: 'EnergyWindowInformationSequence', - x00540013: 'EnergyWindowRangeSequence', - x00540014: 'EnergyWindowLowerLimit', - x00540015: 'EnergyWindowUpperLimit', - x00540016: 'RadiopharmaceuticalInformationSeq', - x00540017: 'ResidualSyringeCounts', - x00540018: 'EnergyWindowName', - x00540020: 'DetectorVector', - x00540021: 'NumberOfDetectors', - x00540022: 'DetectorInformationSequence', - x00540030: 'PhaseVector', - x00540031: 'NumberOfPhases', - x00540032: 'PhaseInformationSequence', - x00540033: 'NumberOfFramesInPhase', - x00540036: 'PhaseDelay', - x00540038: 'PauseBetweenFrames', - x00540039: 'PhaseDescription', - x00540050: 'RotationVector', - x00540051: 'NumberOfRotations', - x00540052: 'RotationInformationSequence', - x00540053: 'NumberOfFramesInRotation', - x00540060: 'RRIntervalVector', - x00540061: 'NumberOfRRIntervals', - x00540062: 'GatedInformationSequence', - x00540063: 'DataInformationSequence', - x00540070: 'TimeSlotVector', - x00540071: 'NumberOfTimeSlots', - x00540072: 'TimeSlotInformationSequence', - x00540073: 'TimeSlotTime', - x00540080: 'SliceVector', - x00540081: 'NumberOfSlices', - x00540090: 'AngularViewVector', - x00540100: 'TimeSliceVector', - x00540101: 'NumberOfTimeSlices', - x00540200: 'StartAngle', - x00540202: 'TypeOfDetectorMotion', - x00540210: 'TriggerVector', - x00540211: 'NumberOfTriggersInPhase', - x00540220: 'ViewCodeSequence', - x00540222: 'ViewModifierCodeSequence', - x00540300: 'RadionuclideCodeSequence', - x00540302: 'AdministrationRouteCodeSequence', - x00540304: 'RadiopharmaceuticalCodeSequence', - x00540306: 'CalibrationDataSequence', - x00540308: 'EnergyWindowNumber', - x00540400: 'ImageID', - x00540410: 'PatientOrientationCodeSequence', - x00540412: 'PatientOrientationModifierCodeSeq', - x00540414: 'PatientGantryRelationshipCodeSeq', - x00540500: 'SliceProgressionDirection', - x00541000: 'SeriesType', - x00541001: 'Units', - x00541002: 'CountsSource', - x00541004: 'ReprojectionMethod', - x00541100: 'RandomsCorrectionMethod', - x00541101: 'AttenuationCorrectionMethod', - x00541102: 'DecayCorrection', - x00541103: 'ReconstructionMethod', - x00541104: 'DetectorLinesOfResponseUsed', - x00541105: 'ScatterCorrectionMethod', - x00541200: 'AxialAcceptance', - x00541201: 'AxialMash', - x00541202: 'TransverseMash', - x00541203: 'DetectorElementSize', - x00541210: 'CoincidenceWindowWidth', - x00541220: 'SecondaryCountsType', - x00541300: 'FrameReferenceTime', - x00541310: 'PrimaryCountsAccumulated', - x00541311: 'SecondaryCountsAccumulated', - x00541320: 'SliceSensitivityFactor', - x00541321: 'DecayFactor', - x00541322: 'DoseCalibrationFactor', - x00541323: 'ScatterFractionFactor', - x00541324: 'DeadTimeFactor', - x00541330: 'ImageIndex', - x00541400: 'CountsIncluded', - x00541401: 'DeadTimeCorrectionFlag', - x00603000: 'HistogramSequence', - x00603002: 'HistogramNumberOfBins', - x00603004: 'HistogramFirstBinValue', - x00603006: 'HistogramLastBinValue', - x00603008: 'HistogramBinWidth', - x00603010: 'HistogramExplanation', - x00603020: 'HistogramData', - x00620001: 'SegmentationType', - x00620002: 'SegmentSequence', - x00620003: 'SegmentedPropertyCategoryCodeSeq', - x00620004: 'SegmentNumber', - x00620005: 'SegmentLabel', - x00620006: 'SegmentDescription', - x00620008: 'SegmentAlgorithmType', - x00620009: 'SegmentAlgorithmName', - x0062000a: 'SegmentIdentificationSequence', - x0062000b: 'ReferencedSegmentNumber', - x0062000c: 'RecommendedDisplayGrayscaleValue', - x0062000d: 'RecommendedDisplayCIELabValue', - x0062000e: 'MaximumFractionalValue', - x0062000f: 'SegmentedPropertyTypeCodeSequence', - x00620010: 'SegmentationFractionalType', - x00640002: 'DeformableRegistrationSequence', - x00640003: 'SourceFrameOfReferenceUID', - x00640005: 'DeformableRegistrationGridSequence', - x00640007: 'GridDimensions', - x00640008: 'GridResolution', - x00640009: 'VectorGridData', - x0064000f: 'PreDeformationMatrixRegistSeq', - x00640010: 'PostDeformationMatrixRegistSeq', - x00660001: 'NumberOfSurfaces', - x00660002: 'SurfaceSequence', - x00660003: 'SurfaceNumber', - x00660004: 'SurfaceComments', - x00660009: 'SurfaceProcessing', - x0066000a: 'SurfaceProcessingRatio', - x0066000e: 'FiniteVolume', - x00660010: 'Manifold', - x00660011: 'SurfacePointsSequence', - x00660015: 'NumberOfSurfacePoints', - x00660016: 'PointCoordinatesData', - x00660017: 'PointPositionAccuracy', - x00660018: 'MeanPointDistance', - x00660019: 'MaximumPointDistance', - x0066001b: 'AxisOfRotation', - x0066001c: 'CenterOfRotation', - x0066001e: 'NumberOfVectors', - x0066001f: 'VectorDimensionality', - x00660020: 'VectorAccuracy', - x00660021: 'VectorCoordinateData', - x00660023: 'TrianglePointIndexList', - x00660024: 'EdgePointIndexList', - x00660025: 'VertexPointIndexList', - x00660026: 'TriangleStripSequence', - x00660027: 'TriangleFanSequence', - x00660028: 'LineSequence', - x00660029: 'PrimitivePointIndexList', - x0066002a: 'SurfaceCount', - x0066002f: 'AlgorithmFamilyCodeSequ', - x00660031: 'AlgorithmVersion', - x00660032: 'AlgorithmParameters', - x00660034: 'FacetSequence', - x00660036: 'AlgorithmName', - x00700001: 'GraphicAnnotationSequence', - x00700002: 'GraphicLayer', - x00700003: 'BoundingBoxAnnotationUnits', - x00700004: 'AnchorPointAnnotationUnits', - x00700005: 'GraphicAnnotationUnits', - x00700006: 'UnformattedTextValue', - x00700008: 'TextObjectSequence', - x00700009: 'GraphicObjectSequence', - x00700010: 'BoundingBoxTopLeftHandCorner', - x00700011: 'BoundingBoxBottomRightHandCorner', - x00700012: 'BoundingBoxTextHorizJustification', - x00700014: 'AnchorPoint', - x00700015: 'AnchorPointVisibility', - x00700020: 'GraphicDimensions', - x00700021: 'NumberOfGraphicPoints', - x00700022: 'GraphicData', - x00700023: 'GraphicType', - x00700024: 'GraphicFilled', - x00700040: 'ImageRotationRetired', - x00700041: 'ImageHorizontalFlip', - x00700042: 'ImageRotation', - x00700050: 'DisplayedAreaTopLeftTrial', - x00700051: 'DisplayedAreaBottomRightTrial', - x00700052: 'DisplayedAreaTopLeft', - x00700053: 'DisplayedAreaBottomRight', - x0070005a: 'DisplayedAreaSelectionSequence', - x00700060: 'GraphicLayerSequence', - x00700062: 'GraphicLayerOrder', - x00700066: 'GraphicLayerRecDisplayGraysclValue', - x00700067: 'GraphicLayerRecDisplayRGBValue', - x00700068: 'GraphicLayerDescription', - x00700080: 'ContentLabel', - x00700081: 'ContentDescription', - x00700082: 'PresentationCreationDate', - x00700083: 'PresentationCreationTime', - x00700084: 'ContentCreatorName', - x00700086: 'ContentCreatorIDCodeSequence', - x00700100: 'PresentationSizeMode', - x00700101: 'PresentationPixelSpacing', - x00700102: 'PresentationPixelAspectRatio', - x00700103: 'PresentationPixelMagRatio', - x00700306: 'ShapeType', - x00700308: 'RegistrationSequence', - x00700309: 'MatrixRegistrationSequence', - x0070030a: 'MatrixSequence', - x0070030c: 'FrameOfRefTransformationMatrixType', - x0070030d: 'RegistrationTypeCodeSequence', - x0070030f: 'FiducialDescription', - x00700310: 'FiducialIdentifier', - x00700311: 'FiducialIdentifierCodeSequence', - x00700312: 'ContourUncertaintyRadius', - x00700314: 'UsedFiducialsSequence', - x00700318: 'GraphicCoordinatesDataSequence', - x0070031a: 'FiducialUID', - x0070031c: 'FiducialSetSequence', - x0070031e: 'FiducialSequence', - x00700401: 'GraphicLayerRecomDisplayCIELabVal', - x00700402: 'BlendingSequence', - x00700403: 'RelativeOpacity', - x00700404: 'ReferencedSpatialRegistrationSeq', - x00700405: 'BlendingPosition', - x00720002: 'HangingProtocolName', - x00720004: 'HangingProtocolDescription', - x00720006: 'HangingProtocolLevel', - x00720008: 'HangingProtocolCreator', - x0072000a: 'HangingProtocolCreationDateTime', - x0072000c: 'HangingProtocolDefinitionSequence', - x0072000e: 'HangingProtocolUserIDCodeSequence', - x00720010: 'HangingProtocolUserGroupName', - x00720012: 'SourceHangingProtocolSequence', - x00720014: 'NumberOfPriorsReferenced', - x00720020: 'ImageSetsSequence', - x00720022: 'ImageSetSelectorSequence', - x00720024: 'ImageSetSelectorUsageFlag', - x00720026: 'SelectorAttribute', - x00720028: 'SelectorValueNumber', - x00720030: 'TimeBasedImageSetsSequence', - x00720032: 'ImageSetNumber', - x00720034: 'ImageSetSelectorCategory', - x00720038: 'RelativeTime', - x0072003a: 'RelativeTimeUnits', - x0072003c: 'AbstractPriorValue', - x0072003e: 'AbstractPriorCodeSequence', - x00720040: 'ImageSetLabel', - x00720050: 'SelectorAttributeVR', - x00720052: 'SelectorSequencePointer', - x00720054: 'SelectorSeqPointerPrivateCreator', - x00720056: 'SelectorAttributePrivateCreator', - x00720060: 'SelectorATValue', - x00720062: 'SelectorCSValue', - x00720064: 'SelectorISValue', - x00720066: 'SelectorLOValue', - x00720068: 'SelectorLTValue', - x0072006a: 'SelectorPNValue', - x0072006c: 'SelectorSHValue', - x0072006e: 'SelectorSTValue', - x00720070: 'SelectorUTValue', - x00720072: 'SelectorDSValue', - x00720074: 'SelectorFDValue', - x00720076: 'SelectorFLValue', - x00720078: 'SelectorULValue', - x0072007a: 'SelectorUSValue', - x0072007c: 'SelectorSLValue', - x0072007e: 'SelectorSSValue', - x00720080: 'SelectorCodeSequenceValue', - x00720100: 'NumberOfScreens', - x00720102: 'NominalScreenDefinitionSequence', - x00720104: 'NumberOfVerticalPixels', - x00720106: 'NumberOfHorizontalPixels', - x00720108: 'DisplayEnvironmentSpatialPosition', - x0072010a: 'ScreenMinimumGrayscaleBitDepth', - x0072010c: 'ScreenMinimumColorBitDepth', - x0072010e: 'ApplicationMaximumRepaintTime', - x00720200: 'DisplaySetsSequence', - x00720202: 'DisplaySetNumber', - x00720203: 'DisplaySetLabel', - x00720204: 'DisplaySetPresentationGroup', - x00720206: 'DisplaySetPresentationGroupDescr', - x00720208: 'PartialDataDisplayHandling', - x00720210: 'SynchronizedScrollingSequence', - x00720212: 'DisplaySetScrollingGroup', - x00720214: 'NavigationIndicatorSequence', - x00720216: 'NavigationDisplaySet', - x00720218: 'ReferenceDisplaySets', - x00720300: 'ImageBoxesSequence', - x00720302: 'ImageBoxNumber', - x00720304: 'ImageBoxLayoutType', - x00720306: 'ImageBoxTileHorizontalDimension', - x00720308: 'ImageBoxTileVerticalDimension', - x00720310: 'ImageBoxScrollDirection', - x00720312: 'ImageBoxSmallScrollType', - x00720314: 'ImageBoxSmallScrollAmount', - x00720316: 'ImageBoxLargeScrollType', - x00720318: 'ImageBoxLargeScrollAmount', - x00720320: 'ImageBoxOverlapPriority', - x00720330: 'CineRelativeToRealTime', - x00720400: 'FilterOperationsSequence', - x00720402: 'FilterByCategory', - x00720404: 'FilterByAttributePresence', - x00720406: 'FilterByOperator', - x00720432: 'SynchronizedImageBoxList', - x00720434: 'TypeOfSynchronization', - x00720500: 'BlendingOperationType', - x00720510: 'ReformattingOperationType', - x00720512: 'ReformattingThickness', - x00720514: 'ReformattingInterval', - x00720516: 'ReformattingOpInitialViewDir', - x00720520: 'RenderingType3D', - x00720600: 'SortingOperationsSequence', - x00720602: 'SortByCategory', - x00720604: 'SortingDirection', - x00720700: 'DisplaySetPatientOrientation', - x00720702: 'VOIType', - x00720704: 'PseudoColorType', - x00720706: 'ShowGrayscaleInverted', - x00720710: 'ShowImageTrueSizeFlag', - x00720712: 'ShowGraphicAnnotationFlag', - x00720714: 'ShowPatientDemographicsFlag', - x00720716: 'ShowAcquisitionTechniquesFlag', - x00720717: 'DisplaySetHorizontalJustification', - x00720718: 'DisplaySetVerticalJustification', - x00741000: 'UnifiedProcedureStepState', - x00741002: 'UPSProgressInformationSequence', - x00741004: 'UnifiedProcedureStepProgress', - x00741006: 'UnifiedProcedureStepProgressDescr', - x00741008: 'UnifiedProcedureStepComURISeq', - x0074100a: 'ContactURI', - x0074100c: 'ContactDisplayName', - x00741020: 'BeamTaskSequence', - x00741022: 'BeamTaskType', - x00741024: 'BeamOrderIndex', - x00741030: 'DeliveryVerificationImageSequence', - x00741032: 'VerificationImageTiming', - x00741034: 'DoubleExposureFlag', - x00741036: 'DoubleExposureOrdering', - x00741038: 'DoubleExposureMeterset', - x0074103a: 'DoubleExposureFieldDelta', - x00741040: 'RelatedReferenceRTImageSequence', - x00741042: 'GeneralMachineVerificationSequence', - x00741044: 'ConventionalMachineVerificationSeq', - x00741046: 'IonMachineVerificationSequence', - x00741048: 'FailedAttributesSequence', - x0074104a: 'OverriddenAttributesSequence', - x0074104c: 'ConventionalControlPointVerifySeq', - x0074104e: 'IonControlPointVerificationSeq', - x00741050: 'AttributeOccurrenceSequence', - x00741052: 'AttributeOccurrencePointer', - x00741054: 'AttributeItemSelector', - x00741056: 'AttributeOccurrencePrivateCreator', - x00741200: 'ScheduledProcedureStepPriority', - x00741202: 'StudyListLabel', - x00741204: 'ProcedureStepLabel', - x00741210: 'ScheduledProcessingParametersSeq', - x00741212: 'PerformedProcessingParametersSeq', - x00741216: 'UPSPerformedProcedureSequence', - x00741220: 'RelatedProcedureStepSequence', - x00741222: 'ProcedureStepRelationshipType', - x00741230: 'DeletionLock', - x00741234: 'ReceivingAE', - x00741236: 'RequestingAE', - x00741238: 'ReasonForCancellation', - x00741242: 'SCPStatus', - x00741244: 'SubscriptionListStatus', - x00741246: 'UPSListStatus', - x00880130: 'StorageMediaFileSetID', - x00880140: 'StorageMediaFileSetUID', - x00880200: 'IconImageSequence', - x00880904: 'TopicTitle', - x00880906: 'TopicSubject', - x00880910: 'TopicAuthor', - x00880912: 'TopicKeywords', - x01000410: 'SOPInstanceStatus', - x01000420: 'SOPAuthorizationDateAndTime', - x01000424: 'SOPAuthorizationComment', - x01000426: 'AuthorizationEquipmentCertNumber', - x04000005: 'MACIDNumber', - x04000010: 'MACCalculationTransferSyntaxUID', - x04000015: 'MACAlgorithm', - x04000020: 'DataElementsSigned', - x04000100: 'DigitalSignatureUID', - x04000105: 'DigitalSignatureDateTime', - x04000110: 'CertificateType', - x04000115: 'CertificateOfSigner', - x04000120: 'Signature', - x04000305: 'CertifiedTimestampType', - x04000310: 'CertifiedTimestamp', - x04000401: 'DigitalSignaturePurposeCodeSeq', - x04000402: 'ReferencedDigitalSignatureSeq', - x04000403: 'ReferencedSOPInstanceMACSeq', - x04000404: 'MAC', - x04000500: 'EncryptedAttributesSequence', - x04000510: 'EncryptedContentTransferSyntaxUID', - x04000520: 'EncryptedContent', - x04000550: 'ModifiedAttributesSequence', - x04000561: 'OriginalAttributesSequence', - x04000562: 'AttributeModificationDateTime', - x04000563: 'ModifyingSystem', - x04000564: 'SourceOfPreviousValues', - x04000565: 'ReasonForTheAttributeModification', - x1000xxx0: 'EscapeTriplet', - x1000xxx1: 'RunLengthTriplet', - x1000xxx2: 'HuffmanTableSize', - x1000xxx3: 'HuffmanTableTriplet', - x1000xxx4: 'ShiftTableSize', - x1000xxx5: 'ShiftTableTriplet', - x1010xxxx: 'ZonalMap', - x20000010: 'NumberOfCopies', - x2000001e: 'PrinterConfigurationSequence', - x20000020: 'PrintPriority', - x20000030: 'MediumType', - x20000040: 'FilmDestination', - x20000050: 'FilmSessionLabel', - x20000060: 'MemoryAllocation', - x20000061: 'MaximumMemoryAllocation', - x20000062: 'ColorImagePrintingFlag', - x20000063: 'CollationFlag', - x20000065: 'AnnotationFlag', - x20000067: 'ImageOverlayFlag', - x20000069: 'PresentationLUTFlag', - x2000006a: 'ImageBoxPresentationLUTFlag', - x200000a0: 'MemoryBitDepth', - x200000a1: 'PrintingBitDepth', - x200000a2: 'MediaInstalledSequence', - x200000a4: 'OtherMediaAvailableSequence', - x200000a8: 'SupportedImageDisplayFormatSeq', - x20000500: 'ReferencedFilmBoxSequence', - x20000510: 'ReferencedStoredPrintSequence', - x20100010: 'ImageDisplayFormat', - x20100030: 'AnnotationDisplayFormatID', - x20100040: 'FilmOrientation', - x20100050: 'FilmSizeID', - x20100052: 'PrinterResolutionID', - x20100054: 'DefaultPrinterResolutionID', - x20100060: 'MagnificationType', - x20100080: 'SmoothingType', - x201000a6: 'DefaultMagnificationType', - x201000a7: 'OtherMagnificationTypesAvailable', - x201000a8: 'DefaultSmoothingType', - x201000a9: 'OtherSmoothingTypesAvailable', - x20100100: 'BorderDensity', - x20100110: 'EmptyImageDensity', - x20100120: 'MinDensity', - x20100130: 'MaxDensity', - x20100140: 'Trim', - x20100150: 'ConfigurationInformation', - x20100152: 'ConfigurationInformationDescr', - x20100154: 'MaximumCollatedFilms', - x2010015e: 'Illumination', - x20100160: 'ReflectedAmbientLight', - x20100376: 'PrinterPixelSpacing', - x20100500: 'ReferencedFilmSessionSequence', - x20100510: 'ReferencedImageBoxSequence', - x20100520: 'ReferencedBasicAnnotationBoxSeq', - x20200010: 'ImageBoxPosition', - x20200020: 'Polarity', - x20200030: 'RequestedImageSize', - x20200040: 'RequestedDecimate-CropBehavior', - x20200050: 'RequestedResolutionID', - x202000a0: 'RequestedImageSizeFlag', - x202000a2: 'DecimateCropResult', - x20200110: 'BasicGrayscaleImageSequence', - x20200111: 'BasicColorImageSequence', - x20200130: 'ReferencedImageOverlayBoxSequence', - x20200140: 'ReferencedVOILUTBoxSequence', - x20300010: 'AnnotationPosition', - x20300020: 'TextString', - x20400010: 'ReferencedOverlayPlaneSequence', - x20400011: 'ReferencedOverlayPlaneGroups', - x20400020: 'OverlayPixelDataSequence', - x20400060: 'OverlayMagnificationType', - x20400070: 'OverlaySmoothingType', - x20400072: 'OverlayOrImageMagnification', - x20400074: 'MagnifyToNumberOfColumns', - x20400080: 'OverlayForegroundDensity', - x20400082: 'OverlayBackgroundDensity', - x20400090: 'OverlayMode', - x20400100: 'ThresholdDensity', - x20400500: 'ReferencedImageBoxSequenceRetired', - x20500010: 'PresentationLUTSequence', - x20500020: 'PresentationLUTShape', - x20500500: 'ReferencedPresentationLUTSequence', - x21000010: 'PrintJobID', - x21000020: 'ExecutionStatus', - x21000030: 'ExecutionStatusInfo', - x21000040: 'CreationDate', - x21000050: 'CreationTime', - x21000070: 'Originator', - x21000140: 'DestinationAE', - x21000160: 'OwnerID', - x21000170: 'NumberOfFilms', - x21000500: 'ReferencedPrintJobSequencePullStoredPrint', - x21100010: 'PrinterStatus', - x21100020: 'PrinterStatusInfo', - x21100030: 'PrinterName', - x21100099: 'PrintQueueID', - x21200010: 'QueueStatus', - x21200050: 'PrintJobDescriptionSequence', - x21200070: 'ReferencedPrintJobSequence', - x21300010: 'PrintManagementCapabilitiesSeq', - x21300015: 'PrinterCharacteristicsSequence', - x21300030: 'FilmBoxContentSequence', - x21300040: 'ImageBoxContentSequence', - x21300050: 'AnnotationContentSequence', - x21300060: 'ImageOverlayBoxContentSequence', - x21300080: 'PresentationLUTContentSequence', - x213000a0: 'ProposedStudySequence', - x213000c0: 'OriginalImageSequence', - x22000001: 'LabelFromInfoExtractedFromInstance', - x22000002: 'LabelText', - x22000003: 'LabelStyleSelection', - x22000004: 'MediaDisposition', - x22000005: 'BarcodeValue', - x22000006: 'BarcodeSymbology', - x22000007: 'AllowMediaSplitting', - x22000008: 'IncludeNonDICOMObjects', - x22000009: 'IncludeDisplayApplication', - x2200000a: 'SaveCompInstancesAfterMediaCreate', - x2200000b: 'TotalNumberMediaPiecesCreated', - x2200000c: 'RequestedMediaApplicationProfile', - x2200000d: 'ReferencedStorageMediaSequence', - x2200000e: 'FailureAttributes', - x2200000f: 'AllowLossyCompression', - x22000020: 'RequestPriority', - x30020002: 'RTImageLabel', - x30020003: 'RTImageName', - x30020004: 'RTImageDescription', - x3002000a: 'ReportedValuesOrigin', - x3002000c: 'RTImagePlane', - x3002000d: 'XRayImageReceptorTranslation', - x3002000e: 'XRayImageReceptorAngle', - x30020010: 'RTImageOrientation', - x30020011: 'ImagePlanePixelSpacing', - x30020012: 'RTImagePosition', - x30020020: 'RadiationMachineName', - x30020022: 'RadiationMachineSAD', - x30020024: 'RadiationMachineSSD', - x30020026: 'RTImageSID', - x30020028: 'SourceToReferenceObjectDistance', - x30020029: 'FractionNumber', - x30020030: 'ExposureSequence', - x30020032: 'MetersetExposure', - x30020034: 'DiaphragmPosition', - x30020040: 'FluenceMapSequence', - x30020041: 'FluenceDataSource', - x30020042: 'FluenceDataScale', - x30020051: 'FluenceMode', - x30020052: 'FluenceModeID', - x30040001: 'DVHType', - x30040002: 'DoseUnits', - x30040004: 'DoseType', - x30040006: 'DoseComment', - x30040008: 'NormalizationPoint', - x3004000a: 'DoseSummationType', - x3004000c: 'GridFrameOffsetVector', - x3004000e: 'DoseGridScaling', - x30040010: 'RTDoseROISequence', - x30040012: 'DoseValue', - x30040014: 'TissueHeterogeneityCorrection', - x30040040: 'DVHNormalizationPoint', - x30040042: 'DVHNormalizationDoseValue', - x30040050: 'DVHSequence', - x30040052: 'DVHDoseScaling', - x30040054: 'DVHVolumeUnits', - x30040056: 'DVHNumberOfBins', - x30040058: 'DVHData', - x30040060: 'DVHReferencedROISequence', - x30040062: 'DVHROIContributionType', - x30040070: 'DVHMinimumDose', - x30040072: 'DVHMaximumDose', - x30040074: 'DVHMeanDose', - x30060002: 'StructureSetLabel', - x30060004: 'StructureSetName', - x30060006: 'StructureSetDescription', - x30060008: 'StructureSetDate', - x30060009: 'StructureSetTime', - x30060010: 'ReferencedFrameOfReferenceSequence', - x30060012: 'RTReferencedStudySequence', - x30060014: 'RTReferencedSeriesSequence', - x30060016: 'ContourImageSequence', - x30060020: 'StructureSetROISequence', - x30060022: 'ROINumber', - x30060024: 'ReferencedFrameOfReferenceUID', - x30060026: 'ROIName', - x30060028: 'ROIDescription', - x3006002a: 'ROIDisplayColor', - x3006002c: 'ROIVolume', - x30060030: 'RTRelatedROISequence', - x30060033: 'RTROIRelationship', - x30060036: 'ROIGenerationAlgorithm', - x30060038: 'ROIGenerationDescription', - x30060039: 'ROIContourSequence', - x30060040: 'ContourSequence', - x30060042: 'ContourGeometricType', - x30060044: 'ContourSlabThickness', - x30060045: 'ContourOffsetVector', - x30060046: 'NumberOfContourPoints', - x30060048: 'ContourNumber', - x30060049: 'AttachedContours', - x30060050: 'ContourData', - x30060080: 'RTROIObservationsSequence', - x30060082: 'ObservationNumber', - x30060084: 'ReferencedROINumber', - x30060085: 'ROIObservationLabel', - x30060086: 'RTROIIdentificationCodeSequence', - x30060088: 'ROIObservationDescription', - x300600a0: 'RelatedRTROIObservationsSequence', - x300600a4: 'RTROIInterpretedType', - x300600a6: 'ROIInterpreter', - x300600b0: 'ROIPhysicalPropertiesSequence', - x300600b2: 'ROIPhysicalProperty', - x300600b4: 'ROIPhysicalPropertyValue', - x300600b6: 'ROIElementalCompositionSequence', - x300600b7: 'ROIElementalCompAtomicNumber', - x300600b8: 'ROIElementalCompAtomicMassFraction', - x300600c0: 'FrameOfReferenceRelationshipSeq', - x300600c2: 'RelatedFrameOfReferenceUID', - x300600c4: 'FrameOfReferenceTransformType', - x300600c6: 'FrameOfReferenceTransformMatrix', - x300600c8: 'FrameOfReferenceTransformComment', - x30080010: 'MeasuredDoseReferenceSequence', - x30080012: 'MeasuredDoseDescription', - x30080014: 'MeasuredDoseType', - x30080016: 'MeasuredDoseValue', - x30080020: 'TreatmentSessionBeamSequence', - x30080021: 'TreatmentSessionIonBeamSequence', - x30080022: 'CurrentFractionNumber', - x30080024: 'TreatmentControlPointDate', - x30080025: 'TreatmentControlPointTime', - x3008002a: 'TreatmentTerminationStatus', - x3008002b: 'TreatmentTerminationCode', - x3008002c: 'TreatmentVerificationStatus', - x30080030: 'ReferencedTreatmentRecordSequence', - x30080032: 'SpecifiedPrimaryMeterset', - x30080033: 'SpecifiedSecondaryMeterset', - x30080036: 'DeliveredPrimaryMeterset', - x30080037: 'DeliveredSecondaryMeterset', - x3008003a: 'SpecifiedTreatmentTime', - x3008003b: 'DeliveredTreatmentTime', - x30080040: 'ControlPointDeliverySequence', - x30080041: 'IonControlPointDeliverySequence', - x30080042: 'SpecifiedMeterset', - x30080044: 'DeliveredMeterset', - x30080045: 'MetersetRateSet', - x30080046: 'MetersetRateDelivered', - x30080047: 'ScanSpotMetersetsDelivered', - x30080048: 'DoseRateDelivered', - x30080050: 'TreatmentSummaryCalcDoseRefSeq', - x30080052: 'CumulativeDoseToDoseReference', - x30080054: 'FirstTreatmentDate', - x30080056: 'MostRecentTreatmentDate', - x3008005a: 'NumberOfFractionsDelivered', - x30080060: 'OverrideSequence', - x30080061: 'ParameterSequencePointer', - x30080062: 'OverrideParameterPointer', - x30080063: 'ParameterItemIndex', - x30080064: 'MeasuredDoseReferenceNumber', - x30080065: 'ParameterPointer', - x30080066: 'OverrideReason', - x30080068: 'CorrectedParameterSequence', - x3008006a: 'CorrectionValue', - x30080070: 'CalculatedDoseReferenceSequence', - x30080072: 'CalculatedDoseReferenceNumber', - x30080074: 'CalculatedDoseReferenceDescription', - x30080076: 'CalculatedDoseReferenceDoseValue', - x30080078: 'StartMeterset', - x3008007a: 'EndMeterset', - x30080080: 'ReferencedMeasuredDoseReferenceSeq', - x30080082: 'ReferencedMeasuredDoseReferenceNum', - x30080090: 'ReferencedCalculatedDoseRefSeq', - x30080092: 'ReferencedCalculatedDoseRefNumber', - x300800a0: 'BeamLimitingDeviceLeafPairsSeq', - x300800b0: 'RecordedWedgeSequence', - x300800c0: 'RecordedCompensatorSequence', - x300800d0: 'RecordedBlockSequence', - x300800e0: 'TreatmentSummaryMeasuredDoseRefSeq', - x300800f0: 'RecordedSnoutSequence', - x300800f2: 'RecordedRangeShifterSequence', - x300800f4: 'RecordedLateralSpreadingDeviceSeq', - x300800f6: 'RecordedRangeModulatorSequence', - x30080100: 'RecordedSourceSequence', - x30080105: 'SourceSerialNumber', - x30080110: 'TreatmentSessionAppSetupSeq', - x30080116: 'ApplicationSetupCheck', - x30080120: 'RecordedBrachyAccessoryDeviceSeq', - x30080122: 'ReferencedBrachyAccessoryDeviceNum', - x30080130: 'RecordedChannelSequence', - x30080132: 'SpecifiedChannelTotalTime', - x30080134: 'DeliveredChannelTotalTime', - x30080136: 'SpecifiedNumberOfPulses', - x30080138: 'DeliveredNumberOfPulses', - x3008013a: 'SpecifiedPulseRepetitionInterval', - x3008013c: 'DeliveredPulseRepetitionInterval', - x30080140: 'RecordedSourceApplicatorSequence', - x30080142: 'ReferencedSourceApplicatorNumber', - x30080150: 'RecordedChannelShieldSequence', - x30080152: 'ReferencedChannelShieldNumber', - x30080160: 'BrachyControlPointDeliveredSeq', - x30080162: 'SafePositionExitDate', - x30080164: 'SafePositionExitTime', - x30080166: 'SafePositionReturnDate', - x30080168: 'SafePositionReturnTime', - x30080200: 'CurrentTreatmentStatus', - x30080202: 'TreatmentStatusComment', - x30080220: 'FractionGroupSummarySequence', - x30080223: 'ReferencedFractionNumber', - x30080224: 'FractionGroupType', - x30080230: 'BeamStopperPosition', - x30080240: 'FractionStatusSummarySequence', - x30080250: 'TreatmentDate', - x30080251: 'TreatmentTime', - x300a0002: 'RTPlanLabel', - x300a0003: 'RTPlanName', - x300a0004: 'RTPlanDescription', - x300a0006: 'RTPlanDate', - x300a0007: 'RTPlanTime', - x300a0009: 'TreatmentProtocols', - x300a000a: 'PlanIntent', - x300a000b: 'TreatmentSites', - x300a000c: 'RTPlanGeometry', - x300a000e: 'PrescriptionDescription', - x300a0010: 'DoseReferenceSequence', - x300a0012: 'DoseReferenceNumber', - x300a0013: 'DoseReferenceUID', - x300a0014: 'DoseReferenceStructureType', - x300a0015: 'NominalBeamEnergyUnit', - x300a0016: 'DoseReferenceDescription', - x300a0018: 'DoseReferencePointCoordinates', - x300a001a: 'NominalPriorDose', - x300a0020: 'DoseReferenceType', - x300a0021: 'ConstraintWeight', - x300a0022: 'DeliveryWarningDose', - x300a0023: 'DeliveryMaximumDose', - x300a0025: 'TargetMinimumDose', - x300a0026: 'TargetPrescriptionDose', - x300a0027: 'TargetMaximumDose', - x300a0028: 'TargetUnderdoseVolumeFraction', - x300a002a: 'OrganAtRiskFullVolumeDose', - x300a002b: 'OrganAtRiskLimitDose', - x300a002c: 'OrganAtRiskMaximumDose', - x300a002d: 'OrganAtRiskOverdoseVolumeFraction', - x300a0040: 'ToleranceTableSequence', - x300a0042: 'ToleranceTableNumber', - x300a0043: 'ToleranceTableLabel', - x300a0044: 'GantryAngleTolerance', - x300a0046: 'BeamLimitingDeviceAngleTolerance', - x300a0048: 'BeamLimitingDeviceToleranceSeq', - x300a004a: 'BeamLimitingDevicePositionTol', - x300a004b: 'SnoutPositionTolerance', - x300a004c: 'PatientSupportAngleTolerance', - x300a004e: 'TableTopEccentricAngleTolerance', - x300a004f: 'TableTopPitchAngleTolerance', - x300a0050: 'TableTopRollAngleTolerance', - x300a0051: 'TableTopVerticalPositionTolerance', - x300a0052: 'TableTopLongitudinalPositionTol', - x300a0053: 'TableTopLateralPositionTolerance', - x300a0055: 'RTPlanRelationship', - x300a0070: 'FractionGroupSequence', - x300a0071: 'FractionGroupNumber', - x300a0072: 'FractionGroupDescription', - x300a0078: 'NumberOfFractionsPlanned', - x300a0079: 'NumberFractionPatternDigitsPerDay', - x300a007a: 'RepeatFractionCycleLength', - x300a007b: 'FractionPattern', - x300a0080: 'NumberOfBeams', - x300a0082: 'BeamDoseSpecificationPoint', - x300a0084: 'BeamDose', - x300a0086: 'BeamMeterset', - x300a0088: 'BeamDosePointDepth', - x300a0089: 'BeamDosePointEquivalentDepth', - x300a008a: 'BeamDosePointSSD', - x300a00a0: 'NumberOfBrachyApplicationSetups', - x300a00a2: 'BrachyAppSetupDoseSpecPoint', - x300a00a4: 'BrachyApplicationSetupDose', - x300a00b0: 'BeamSequence', - x300a00b2: 'TreatmentMachineName', - x300a00b3: 'PrimaryDosimeterUnit', - x300a00b4: 'SourceAxisDistance', - x300a00b6: 'BeamLimitingDeviceSequence', - x300a00b8: 'RTBeamLimitingDeviceType', - x300a00ba: 'SourceToBeamLimitingDeviceDistance', - x300a00bb: 'IsocenterToBeamLimitingDeviceDist', - x300a00bc: 'NumberOfLeafJawPairs', - x300a00be: 'LeafPositionBoundaries', - x300a00c0: 'BeamNumber', - x300a00c2: 'BeamName', - x300a00c3: 'BeamDescription', - x300a00c4: 'BeamType', - x300a00c6: 'RadiationType', - x300a00c7: 'HighDoseTechniqueType', - x300a00c8: 'ReferenceImageNumber', - x300a00ca: 'PlannedVerificationImageSequence', - x300a00cc: 'ImagingDeviceSpecificAcqParams', - x300a00ce: 'TreatmentDeliveryType', - x300a00d0: 'NumberOfWedges', - x300a00d1: 'WedgeSequence', - x300a00d2: 'WedgeNumber', - x300a00d3: 'WedgeType', - x300a00d4: 'WedgeID', - x300a00d5: 'WedgeAngle', - x300a00d6: 'WedgeFactor', - x300a00d7: 'TotalWedgeTrayWaterEquivThickness', - x300a00d8: 'WedgeOrientation', - x300a00d9: 'IsocenterToWedgeTrayDistance', - x300a00da: 'SourceToWedgeTrayDistance', - x300a00db: 'WedgeThinEdgePosition', - x300a00dc: 'BolusID', - x300a00dd: 'BolusDescription', - x300a00e0: 'NumberOfCompensators', - x300a00e1: 'MaterialID', - x300a00e2: 'TotalCompensatorTrayFactor', - x300a00e3: 'CompensatorSequence', - x300a00e4: 'CompensatorNumber', - x300a00e5: 'CompensatorID', - x300a00e6: 'SourceToCompensatorTrayDistance', - x300a00e7: 'CompensatorRows', - x300a00e8: 'CompensatorColumns', - x300a00e9: 'CompensatorPixelSpacing', - x300a00ea: 'CompensatorPosition', - x300a00eb: 'CompensatorTransmissionData', - x300a00ec: 'CompensatorThicknessData', - x300a00ed: 'NumberOfBoli', - x300a00ee: 'CompensatorType', - x300a00f0: 'NumberOfBlocks', - x300a00f2: 'TotalBlockTrayFactor', - x300a00f3: 'TotalBlockTrayWaterEquivThickness', - x300a00f4: 'BlockSequence', - x300a00f5: 'BlockTrayID', - x300a00f6: 'SourceToBlockTrayDistance', - x300a00f7: 'IsocenterToBlockTrayDistance', - x300a00f8: 'BlockType', - x300a00f9: 'AccessoryCode', - x300a00fa: 'BlockDivergence', - x300a00fb: 'BlockMountingPosition', - x300a00fc: 'BlockNumber', - x300a00fe: 'BlockName', - x300a0100: 'BlockThickness', - x300a0102: 'BlockTransmission', - x300a0104: 'BlockNumberOfPoints', - x300a0106: 'BlockData', - x300a0107: 'ApplicatorSequence', - x300a0108: 'ApplicatorID', - x300a0109: 'ApplicatorType', - x300a010a: 'ApplicatorDescription', - x300a010c: 'CumulativeDoseReferenceCoefficient', - x300a010e: 'FinalCumulativeMetersetWeight', - x300a0110: 'NumberOfControlPoints', - x300a0111: 'ControlPointSequence', - x300a0112: 'ControlPointIndex', - x300a0114: 'NominalBeamEnergy', - x300a0115: 'DoseRateSet', - x300a0116: 'WedgePositionSequence', - x300a0118: 'WedgePosition', - x300a011a: 'BeamLimitingDevicePositionSequence', - x300a011c: 'LeafJawPositions', - x300a011e: 'GantryAngle', - x300a011f: 'GantryRotationDirection', - x300a0120: 'BeamLimitingDeviceAngle', - x300a0121: 'BeamLimitingDeviceRotateDirection', - x300a0122: 'PatientSupportAngle', - x300a0123: 'PatientSupportRotationDirection', - x300a0124: 'TableTopEccentricAxisDistance', - x300a0125: 'TableTopEccentricAngle', - x300a0126: 'TableTopEccentricRotateDirection', - x300a0128: 'TableTopVerticalPosition', - x300a0129: 'TableTopLongitudinalPosition', - x300a012a: 'TableTopLateralPosition', - x300a012c: 'IsocenterPosition', - x300a012e: 'SurfaceEntryPoint', - x300a0130: 'SourceToSurfaceDistance', - x300a0134: 'CumulativeMetersetWeight', - x300a0140: 'TableTopPitchAngle', - x300a0142: 'TableTopPitchRotationDirection', - x300a0144: 'TableTopRollAngle', - x300a0146: 'TableTopRollRotationDirection', - x300a0148: 'HeadFixationAngle', - x300a014a: 'GantryPitchAngle', - x300a014c: 'GantryPitchRotationDirection', - x300a014e: 'GantryPitchAngleTolerance', - x300a0180: 'PatientSetupSequence', - x300a0182: 'PatientSetupNumber', - x300a0183: 'PatientSetupLabel', - x300a0184: 'PatientAdditionalPosition', - x300a0190: 'FixationDeviceSequence', - x300a0192: 'FixationDeviceType', - x300a0194: 'FixationDeviceLabel', - x300a0196: 'FixationDeviceDescription', - x300a0198: 'FixationDevicePosition', - x300a0199: 'FixationDevicePitchAngle', - x300a019a: 'FixationDeviceRollAngle', - x300a01a0: 'ShieldingDeviceSequence', - x300a01a2: 'ShieldingDeviceType', - x300a01a4: 'ShieldingDeviceLabel', - x300a01a6: 'ShieldingDeviceDescription', - x300a01a8: 'ShieldingDevicePosition', - x300a01b0: 'SetupTechnique', - x300a01b2: 'SetupTechniqueDescription', - x300a01b4: 'SetupDeviceSequence', - x300a01b6: 'SetupDeviceType', - x300a01b8: 'SetupDeviceLabel', - x300a01ba: 'SetupDeviceDescription', - x300a01bc: 'SetupDeviceParameter', - x300a01d0: 'SetupReferenceDescription', - x300a01d2: 'TableTopVerticalSetupDisplacement', - x300a01d4: 'TableTopLongitudinalSetupDisplace', - x300a01d6: 'TableTopLateralSetupDisplacement', - x300a0200: 'BrachyTreatmentTechnique', - x300a0202: 'BrachyTreatmentType', - x300a0206: 'TreatmentMachineSequence', - x300a0210: 'SourceSequence', - x300a0212: 'SourceNumber', - x300a0214: 'SourceType', - x300a0216: 'SourceManufacturer', - x300a0218: 'ActiveSourceDiameter', - x300a021a: 'ActiveSourceLength', - x300a0222: 'SourceEncapsulationNomThickness', - x300a0224: 'SourceEncapsulationNomTransmission', - x300a0226: 'SourceIsotopeName', - x300a0228: 'SourceIsotopeHalfLife', - x300a0229: 'SourceStrengthUnits', - x300a022a: 'ReferenceAirKermaRate', - x300a022b: 'SourceStrength', - x300a022c: 'SourceStrengthReferenceDate', - x300a022e: 'SourceStrengthReferenceTime', - x300a0230: 'ApplicationSetupSequence', - x300a0232: 'ApplicationSetupType', - x300a0234: 'ApplicationSetupNumber', - x300a0236: 'ApplicationSetupName', - x300a0238: 'ApplicationSetupManufacturer', - x300a0240: 'TemplateNumber', - x300a0242: 'TemplateType', - x300a0244: 'TemplateName', - x300a0250: 'TotalReferenceAirKerma', - x300a0260: 'BrachyAccessoryDeviceSequence', - x300a0262: 'BrachyAccessoryDeviceNumber', - x300a0263: 'BrachyAccessoryDeviceID', - x300a0264: 'BrachyAccessoryDeviceType', - x300a0266: 'BrachyAccessoryDeviceName', - x300a026a: 'BrachyAccessoryDeviceNomThickness', - x300a026c: 'BrachyAccessoryDevNomTransmission', - x300a0280: 'ChannelSequence', - x300a0282: 'ChannelNumber', - x300a0284: 'ChannelLength', - x300a0286: 'ChannelTotalTime', - x300a0288: 'SourceMovementType', - x300a028a: 'NumberOfPulses', - x300a028c: 'PulseRepetitionInterval', - x300a0290: 'SourceApplicatorNumber', - x300a0291: 'SourceApplicatorID', - x300a0292: 'SourceApplicatorType', - x300a0294: 'SourceApplicatorName', - x300a0296: 'SourceApplicatorLength', - x300a0298: 'SourceApplicatorManufacturer', - x300a029c: 'SourceApplicatorWallNomThickness', - x300a029e: 'SourceApplicatorWallNomTrans', - x300a02a0: 'SourceApplicatorStepSize', - x300a02a2: 'TransferTubeNumber', - x300a02a4: 'TransferTubeLength', - x300a02b0: 'ChannelShieldSequence', - x300a02b2: 'ChannelShieldNumber', - x300a02b3: 'ChannelShieldID', - x300a02b4: 'ChannelShieldName', - x300a02b8: 'ChannelShieldNominalThickness', - x300a02ba: 'ChannelShieldNominalTransmission', - x300a02c8: 'FinalCumulativeTimeWeight', - x300a02d0: 'BrachyControlPointSequence', - x300a02d2: 'ControlPointRelativePosition', - x300a02d4: 'ControlPoint3DPosition', - x300a02d6: 'CumulativeTimeWeight', - x300a02e0: 'CompensatorDivergence', - x300a02e1: 'CompensatorMountingPosition', - x300a02e2: 'SourceToCompensatorDistance', - x300a02e3: 'TotalCompTrayWaterEquivThickness', - x300a02e4: 'IsocenterToCompensatorTrayDistance', - x300a02e5: 'CompensatorColumnOffset', - x300a02e6: 'IsocenterToCompensatorDistances', - x300a02e7: 'CompensatorRelStoppingPowerRatio', - x300a02e8: 'CompensatorMillingToolDiameter', - x300a02ea: 'IonRangeCompensatorSequence', - x300a02eb: 'CompensatorDescription', - x300a0302: 'RadiationMassNumber', - x300a0304: 'RadiationAtomicNumber', - x300a0306: 'RadiationChargeState', - x300a0308: 'ScanMode', - x300a030a: 'VirtualSourceAxisDistances', - x300a030c: 'SnoutSequence', - x300a030d: 'SnoutPosition', - x300a030f: 'SnoutID', - x300a0312: 'NumberOfRangeShifters', - x300a0314: 'RangeShifterSequence', - x300a0316: 'RangeShifterNumber', - x300a0318: 'RangeShifterID', - x300a0320: 'RangeShifterType', - x300a0322: 'RangeShifterDescription', - x300a0330: 'NumberOfLateralSpreadingDevices', - x300a0332: 'LateralSpreadingDeviceSequence', - x300a0334: 'LateralSpreadingDeviceNumber', - x300a0336: 'LateralSpreadingDeviceID', - x300a0338: 'LateralSpreadingDeviceType', - x300a033a: 'LateralSpreadingDeviceDescription', - x300a033c: 'LateralSpreadingDevWaterEquivThick', - x300a0340: 'NumberOfRangeModulators', - x300a0342: 'RangeModulatorSequence', - x300a0344: 'RangeModulatorNumber', - x300a0346: 'RangeModulatorID', - x300a0348: 'RangeModulatorType', - x300a034a: 'RangeModulatorDescription', - x300a034c: 'BeamCurrentModulationID', - x300a0350: 'PatientSupportType', - x300a0352: 'PatientSupportID', - x300a0354: 'PatientSupportAccessoryCode', - x300a0356: 'FixationLightAzimuthalAngle', - x300a0358: 'FixationLightPolarAngle', - x300a035a: 'MetersetRate', - x300a0360: 'RangeShifterSettingsSequence', - x300a0362: 'RangeShifterSetting', - x300a0364: 'IsocenterToRangeShifterDistance', - x300a0366: 'RangeShifterWaterEquivThickness', - x300a0370: 'LateralSpreadingDeviceSettingsSeq', - x300a0372: 'LateralSpreadingDeviceSetting', - x300a0374: 'IsocenterToLateralSpreadingDevDist', - x300a0380: 'RangeModulatorSettingsSequence', - x300a0382: 'RangeModulatorGatingStartValue', - x300a0384: 'RangeModulatorGatingStopValue', - x300a038a: 'IsocenterToRangeModulatorDistance', - x300a0390: 'ScanSpotTuneID', - x300a0392: 'NumberOfScanSpotPositions', - x300a0394: 'ScanSpotPositionMap', - x300a0396: 'ScanSpotMetersetWeights', - x300a0398: 'ScanningSpotSize', - x300a039a: 'NumberOfPaintings', - x300a03a0: 'IonToleranceTableSequence', - x300a03a2: 'IonBeamSequence', - x300a03a4: 'IonBeamLimitingDeviceSequence', - x300a03a6: 'IonBlockSequence', - x300a03a8: 'IonControlPointSequence', - x300a03aa: 'IonWedgeSequence', - x300a03ac: 'IonWedgePositionSequence', - x300a0401: 'ReferencedSetupImageSequence', - x300a0402: 'SetupImageComment', - x300a0410: 'MotionSynchronizationSequence', - x300a0412: 'ControlPointOrientation', - x300a0420: 'GeneralAccessorySequence', - x300a0421: 'GeneralAccessoryID', - x300a0422: 'GeneralAccessoryDescription', - x300a0423: 'GeneralAccessoryType', - x300a0424: 'GeneralAccessoryNumber', - x300c0002: 'ReferencedRTPlanSequence', - x300c0004: 'ReferencedBeamSequence', - x300c0006: 'ReferencedBeamNumber', - x300c0007: 'ReferencedReferenceImageNumber', - x300c0008: 'StartCumulativeMetersetWeight', - x300c0009: 'EndCumulativeMetersetWeight', - x300c000a: 'ReferencedBrachyAppSetupSeq', - x300c000c: 'ReferencedBrachyAppSetupNumber', - x300c000e: 'ReferencedSourceNumber', - x300c0020: 'ReferencedFractionGroupSequence', - x300c0022: 'ReferencedFractionGroupNumber', - x300c0040: 'ReferencedVerificationImageSeq', - x300c0042: 'ReferencedReferenceImageSequence', - x300c0050: 'ReferencedDoseReferenceSequence', - x300c0051: 'ReferencedDoseReferenceNumber', - x300c0055: 'BrachyReferencedDoseReferenceSeq', - x300c0060: 'ReferencedStructureSetSequence', - x300c006a: 'ReferencedPatientSetupNumber', - x300c0080: 'ReferencedDoseSequence', - x300c00a0: 'ReferencedToleranceTableNumber', - x300c00b0: 'ReferencedBolusSequence', - x300c00c0: 'ReferencedWedgeNumber', - x300c00d0: 'ReferencedCompensatorNumber', - x300c00e0: 'ReferencedBlockNumber', - x300c00f0: 'ReferencedControlPointIndex', - x300c00f2: 'ReferencedControlPointSequence', - x300c00f4: 'ReferencedStartControlPointIndex', - x300c00f6: 'ReferencedStopControlPointIndex', - x300c0100: 'ReferencedRangeShifterNumber', - x300c0102: 'ReferencedLateralSpreadingDevNum', - x300c0104: 'ReferencedRangeModulatorNumber', - x300e0002: 'ApprovalStatus', - x300e0004: 'ReviewDate', - x300e0005: 'ReviewTime', - x300e0008: 'ReviewerName', - x40000000: 'TextGroupLength', - x40000010: 'Arbitrary', - x40004000: 'TextComments', - x40080040: 'ResultsID', - x40080042: 'ResultsIDIssuer', - x40080050: 'ReferencedInterpretationSequence', - x40080100: 'InterpretationRecordedDate', - x40080101: 'InterpretationRecordedTime', - x40080102: 'InterpretationRecorder', - x40080103: 'ReferenceToRecordedSound', - x40080108: 'InterpretationTranscriptionDate', - x40080109: 'InterpretationTranscriptionTime', - x4008010a: 'InterpretationTranscriber', - x4008010b: 'InterpretationText', - x4008010c: 'InterpretationAuthor', - x40080111: 'InterpretationApproverSequence', - x40080112: 'InterpretationApprovalDate', - x40080113: 'InterpretationApprovalTime', - x40080114: 'PhysicianApprovingInterpretation', - x40080115: 'InterpretationDiagnosisDescription', - x40080117: 'InterpretationDiagnosisCodeSeq', - x40080118: 'ResultsDistributionListSequence', - x40080119: 'DistributionName', - x4008011a: 'DistributionAddress', - x40080200: 'InterpretationID', - x40080202: 'InterpretationIDIssuer', - x40080210: 'InterpretationTypeID', - x40080212: 'InterpretationStatusID', - x40080300: 'Impressions', - x40084000: 'ResultsComments', - x4ffe0001: 'MACParametersSequence', - x50xx0005: 'CurveDimensions', - x50xx0010: 'NumberOfPoints', - x50xx0020: 'TypeOfData', - x50xx0022: 'CurveDescription', - x50xx0030: 'AxisUnits', - x50xx0040: 'AxisLabels', - x50xx0103: 'DataValueRepresentation', - x50xx0104: 'MinimumCoordinateValue', - x50xx0105: 'MaximumCoordinateValue', - x50xx0106: 'CurveRange', - x50xx0110: 'CurveDataDescriptor', - x50xx0112: 'CoordinateStartValue', - x50xx0114: 'CoordinateStepValue', - x50xx1001: 'CurveActivationLayer', - x50xx2000: 'AudioType', - x50xx2002: 'AudioSampleFormat', - x50xx2004: 'NumberOfChannels', - x50xx2006: 'NumberOfSamples', - x50xx2008: 'SampleRate', - x50xx200a: 'TotalTime', - x50xx200c: 'AudioSampleData', - x50xx200e: 'AudioComments', - x50xx2500: 'CurveLabel', - x50xx2600: 'CurveReferencedOverlaySequence', - x50xx2610: 'ReferencedOverlayGroup', - x50xx3000: 'CurveData', - x52009229: 'SharedFunctionalGroupsSequence', - x52009230: 'PerFrameFunctionalGroupsSequence', - x54000100: 'WaveformSequence', - x54000110: 'ChannelMinimumValue', - x54000112: 'ChannelMaximumValue', - x54001004: 'WaveformBitsAllocated', - x54001006: 'WaveformSampleInterpretation', - x5400100a: 'WaveformPaddingValue', - x54001010: 'WaveformData', - x56000010: 'FirstOrderPhaseCorrectionAngle', - x56000020: 'SpectroscopyData', - x60000000: 'OverlayGroupLength', - x60xx0010: 'OverlayRows', - x60xx0011: 'OverlayColumns', - x60xx0012: 'OverlayPlanes', - x60xx0015: 'NumberOfFramesInOverlay', - x60xx0022: 'OverlayDescription', - x60xx0040: 'OverlayType', - x60xx0045: 'OverlaySubtype', - x60xx0050: 'OverlayOrigin', - x60xx0051: 'ImageFrameOrigin', - x60xx0052: 'OverlayPlaneOrigin', - x60xx0060: 'OverlayCompressionCode', - x60xx0061: 'OverlayCompressionOriginator', - x60xx0062: 'OverlayCompressionLabel', - x60xx0063: 'OverlayCompressionDescription', - x60xx0066: 'OverlayCompressionStepPointers', - x60xx0068: 'OverlayRepeatInterval', - x60xx0069: 'OverlayBitsGrouped', - x60xx0100: 'OverlayBitsAllocated', - x60xx0102: 'OverlayBitPosition', - x60xx0110: 'OverlayFormat', - x60xx0200: 'OverlayLocation', - x60xx0800: 'OverlayCodeLabel', - x60xx0802: 'OverlayNumberOfTables', - x60xx0803: 'OverlayCodeTableLocation', - x60xx0804: 'OverlayBitsForCodeWord', - x60xx1001: 'OverlayActivationLayer', - x60xx1100: 'OverlayDescriptorGray', - x60xx1101: 'OverlayDescriptorRed', - x60xx1102: 'OverlayDescriptorGreen', - x60xx1103: 'OverlayDescriptorBlue', - x60xx1200: 'OverlaysGray', - x60xx1201: 'OverlaysRed', - x60xx1202: 'OverlaysGreen', - x60xx1203: 'OverlaysBlue', - x60xx1301: 'ROIArea', - x60xx1302: 'ROIMean', - x60xx1303: 'ROIStandardDeviation', - x60xx1500: 'OverlayLabel', - x60xx3000: 'OverlayData', - x60xx4000: 'OverlayComments', - x7fxx0000: 'PixelDataGroupLength', - x7fxx0010: 'PixelData', - x7fxx0011: 'VariableNextDataGroup', - x7fxx0020: 'VariableCoefficientsSDVN', - x7fxx0030: 'VariableCoefficientsSDHN', - x7fxx0040: 'VariableCoefficientsSDDN', - xfffafffa: 'DigitalSignaturesSequence', - xfffcfffc: 'DataSetTrailingPadding', - xfffee000: 'StartOfItem', - xfffee00d: 'EndOfItems', - xfffee0dd: 'EndOfSequence', -}; - -DICOMTagDescriptions.init(initialTagDescriptionMap); - -// Discard original map... -initialTagDescriptionMap = null; - -export { DICOMTagDescriptions }; diff --git a/platform/core/src/utils/StackManager.js b/platform/core/src/utils/StackManager.js index 4a675a19c..ed48ffadf 100644 --- a/platform/core/src/utils/StackManager.js +++ b/platform/core/src/utils/StackManager.js @@ -1,9 +1,9 @@ import OHIFError from '../classes/OHIFError.js'; import getImageId from './getImageId'; +import metadataProvider from '../classes/MetadataProvider.js'; let stackMap = {}; let configuration = {}; -let stackManagerMetaDataProvider; const stackUpdatedCallbacks = []; /** @@ -15,13 +15,7 @@ const stackUpdatedCallbacks = []; * @param {Object} displaySet The set of images to make the stack from * @return {Array} Array with image IDs */ -function createAndAddStack( - stackMap, - study, - displaySet, - stackUpdatedCallbacks, - metadataProvider -) { +function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) { const images = displaySet.images; if (!images) { return; @@ -41,31 +35,56 @@ function createAndAddStack( imageIndex: imageIndex + 1, }; - const numberOfFrames = image.numberOfFrames; - if (numberOfFrames > 1) { - for (let i = 0; i < numberOfFrames; i++) { + const NumberOfFrames = image.NumberOfFrames; + + if (NumberOfFrames > 1) { + for (let i = 0; i < NumberOfFrames; i++) { metaData.frameNumber = i; imageId = getImageId(image, i); imageIds.push(imageId); - metadataProvider.addMetadata(imageId, metaData); + + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + } = instance.getData().metadata; + + metadataProvider.addImageIdToUIDs(imageId, { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + }); } } else { metaData.frameNumber = 1; imageId = getImageId(image); imageIds.push(imageId); - metadataProvider.addMetadata(imageId, metaData); + + const naturalizedInstance = instance.getData().metadata; + + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + } = naturalizedInstance; + + metadataProvider.addImageIdToUIDs(imageId, { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + }); } }); const stack = { - studyInstanceUid: study.studyInstanceUid, - displaySetInstanceUid: displaySet.displaySetInstanceUid, + StudyInstanceUID: study.StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, imageIds, frameRate: displaySet.frameRate, isClip: displaySet.isClip, }; - stackMap[displaySet.displaySetInstanceUid] = stack; + stackMap[displaySet.displaySetInstanceUID] = stack; return stack; } @@ -80,9 +99,6 @@ configuration = { * come in, you can register a callback with addStackUpdatedCallback. */ const StackManager = { - setMetadataProvider(provider) { - stackManagerMetaDataProvider = provider; - }, /** * Removes all current stacks */ @@ -96,27 +112,20 @@ const StackManager = { * @return {Array} Array with image IDs */ makeAndAddStack(study, displaySet) { - if (!stackManagerMetaDataProvider) { - throw new Error( - 'Please call StackManager.setMetadataProvider(provider) first.' - ); - } - return configuration.createAndAddStack( stackMap, study, displaySet, - stackUpdatedCallbacks, - stackManagerMetaDataProvider + stackUpdatedCallbacks ); }, /** * Find a stack from the currently created stacks. - * @param displaySetInstanceUid The UID of the stack to find. + * @param displaySetInstanceUID The UID of the stack to find. * @returns {*} undefined if not found, otherwise the stack object is returned. */ - findStack(displaySetInstanceUid) { - return stackMap[displaySetInstanceUid]; + findStack(displaySetInstanceUID) { + return stackMap[displaySetInstanceUID]; }, /** * Find a stack or reate one if it has not been created yet @@ -125,7 +134,7 @@ const StackManager = { * @return {Array} Array with image IDs */ findOrCreateStack(study, displaySet) { - let stack = this.findStack(displaySet.displaySetInstanceUid); + let stack = this.findStack(displaySet.displaySetInstanceUID); if (!stack || !stack.imageIds) { stack = this.makeAndAddStack(study, displaySet); @@ -134,9 +143,9 @@ const StackManager = { return stack; }, /** - * Gets the underlying map of displaySetInstanceUid to stack object. + * Gets the underlying map of displaySetInstanceUID to stack object. * WARNING: Do not change this object. It directly affects the manager. - * @returns {{}} map of displaySetInstanceUid -> stack. + * @returns {{}} map of displaySetInstanceUID -> stack. */ getAllStacks() { return stackMap; diff --git a/platform/core/src/utils/createStacks.draft-test.js b/platform/core/src/utils/createStacks.draft-test.js index b8a29b06e..0cf727280 100644 --- a/platform/core/src/utils/createStacks.draft-test.js +++ b/platform/core/src/utils/createStacks.draft-test.js @@ -6,13 +6,13 @@ // { // getInstanceCount: jest.fn().mockReturnValue(1), // getData: jest.fn().mockReturnValue({ -// seriesDate: '2019-06-04', +// SeriesDate: '2019-06-04', // }), // }, // { // getInstanceCount: jest.fn().mockReturnValue(1), // getData: jest.fn().mockReturnValue({ -// seriesDate: '2018-06-04', +// SeriesDate: '2018-06-04', // }), // }, // ]; @@ -25,7 +25,7 @@ // getStudyInstanceUID: jest.fn(), // }; -// it('sorts displaySets by seriesNumber, then by seriesDate', () => { +// it('sorts displaySets by SeriesNumber, then by SeriesDate', () => { // const displaySets = createStacks(studyMetadata); // expect(displaySets.length).toBe(2); diff --git a/platform/core/src/utils/dicomLoaderService.js b/platform/core/src/utils/dicomLoaderService.js index 9abaaee6f..42d3faad7 100644 --- a/platform/core/src/utils/dicomLoaderService.js +++ b/platform/core/src/utils/dicomLoaderService.js @@ -13,15 +13,15 @@ const getImageId = imageObj => { : imageObj.url; }; -const findImageIdOnStudies = (studies, displaySetInstanceUid) => { +const findImageIdOnStudies = (studies, displaySetInstanceUID) => { const study = studies.find(study => { const displaySet = study.displaySets.some( - displaySet => displaySet.displaySetInstanceUid === displaySetInstanceUid + displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID ); return displaySet; }); - const { seriesList = [] } = study; - const { instances = [] } = seriesList[0] || {}; + const { series = [] } = study; + const { instances = [] } = series[0] || {}; const instance = instances[0]; return getImageId(instance); @@ -85,7 +85,7 @@ const getImageLoaderType = imageId => { ); }; -const DicomLoaderService = new (class { +class DicomLoaderService { getLocalData(dataset, studies) { if (dataset && dataset.localFile) { // Use referenced imageInstance @@ -94,7 +94,7 @@ const DicomLoaderService = new (class { // or Try to get it from studies if (someInvalidStrings(imageId)) { - imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUid); + imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUID); } if (!someInvalidStrings(imageId)) { @@ -155,9 +155,9 @@ const DicomLoaderService = new (class { getDataByDatasetType(dataset) { const { - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, authorizationHeaders, wadoRoot, wadoUri, @@ -166,9 +166,9 @@ const DicomLoaderService = new (class { if (!someInvalidStrings(wadoRoot)) { return wadorsRetriever( wadoRoot, - studyInstanceUid, - seriesInstanceUid, - sopInstanceUid, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, authorizationHeaders ); } else if (!someInvalidStrings(wadoUri)) { @@ -194,6 +194,8 @@ const DicomLoaderService = new (class { // in case of no valid loader throw new Error('Invalid dicom data loader'); } -})(); +} -export default DicomLoaderService; +const dicomLoaderService = new DicomLoaderService(); + +export default dicomLoaderService; diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 3f9cdcddb..2530ef8b2 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -1,4 +1,3 @@ -import { DICOMTagDescriptions } from './DICOMTagDescriptions'; import ObjectPath from './objectPath'; import StackManager from './StackManager.js'; import absoluteUrl from './absoluteUrl'; @@ -6,7 +5,6 @@ import addServers from './addServers'; import guid from './guid'; import sortBy from './sortBy.js'; import studyMetadataManager from './studyMetadataManager'; -import updateMetaDataManager from './updateMetaDataManager.js'; import writeScript from './writeScript.js'; import DicomLoaderService from './dicomLoaderService.js'; import b64toBlob from './b64toBlob.js'; @@ -24,9 +22,6 @@ const utils = { b64toBlob, StackManager, studyMetadataManager, - // Updates WADO-RS metaDataManager - updateMetaDataManager, - DICOMTagDescriptions, DicomLoaderService, urlUtil, makeCancelable, @@ -43,9 +38,6 @@ export { b64toBlob, StackManager, studyMetadataManager, - // Updates WADO-RS metaDataManager - updateMetaDataManager, - DICOMTagDescriptions, DicomLoaderService, urlUtil, makeCancelable, diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index 36909e965..103d9cf95 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -12,9 +12,6 @@ describe('Top level exports', () => { 'b64toBlob', 'StackManager', 'studyMetadataManager', - // Updates WADO-RS metaDataManager - 'updateMetaDataManager', - 'DICOMTagDescriptions', 'DicomLoaderService', 'urlUtil', 'makeCancelable', diff --git a/platform/core/src/utils/isDisplaySetReconstructable.js b/platform/core/src/utils/isDisplaySetReconstructable.js index 44783cb22..0d0f250b1 100644 --- a/platform/core/src/utils/isDisplaySetReconstructable.js +++ b/platform/core/src/utils/isDisplaySetReconstructable.js @@ -1,21 +1,25 @@ /** * Checks if a series is reconstructable to a 3D volume. * - * @param {Object} series The `OHIFSeriesMetadata` object. - * @param {Object[]} instances The `OHIFInstanceMetadata` object + * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects. */ -export default function isDisplaySetReconstructable(series, instances) { - // Can't reconstruct if we only have one image. - - const modality = series._data.modality; // TODO -> Is there a better way to get this? - const isMultiframe = instances[0].getRawValue('x00280008') > 1; - - if (!constructableModalities.includes(modality)) { +export default function isDisplaySetReconstructable(instances) { + if (!instances.length) { return { value: false }; } + const firstInstance = instances[0].getData().metadata; + + const Modality = firstInstance.Modality; + const isMultiframe = firstInstance.NumberOfFrames > 1; + + if (!constructableModalities.includes(Modality)) { + return { value: false }; + } + + // Can't reconstruct if we only have one image. if (!isMultiframe && instances.length === 1) { - return { values: false }; + return { value: false }; } if (isMultiframe) { @@ -26,34 +30,36 @@ export default function isDisplaySetReconstructable(series, instances) { } function processMultiframe(instance) { - //TODO: deal with multriframe checks! return true for now. - return { value: true }; + //TODO: deal with multriframe checks! return false for now as can't reconstruct. + return { value: false }; } function processSingleframe(instances) { - const firstImage = instances[0]; - const firstImageRows = firstImage.getTagValue('x00280010'); - const firstImageColumns = firstImage.getTagValue('x00280011'); - const firstImageSamplesPerPixel = firstImage.getTagValue('x00280002'); - // Note: No need to unpack iop, can compare string form. - const firstImageOrientationPatient = firstImage.getTagValue('x00200037'); + const firstImage = instances[0].getData().metadata; + const firstImageRows = firstImage.Rows; + const firstImageColumns = firstImage.Columns; + const firstImageSamplesPerPixel = firstImage.SamplesPerPixel; + const firstImageOrientationPatient = firstImage.ImageOrientationPatient; + const firstImagePositionPatient = firstImage.ImagePositionPatient; // Can't reconstruct if we: // -- Have a different dimensions within a displaySet. // -- Have a different number of components within a displaySet. // -- Have different orientations within a displaySet. for (let i = 1; i < instances.length; i++) { - const instance = instances[i]; - const rows = instance.getTagValue('x00280010'); - const columns = instance.getTagValue('x00280011'); - const samplesPerPixel = instance.getTagValue('x00280002'); - const imageOrientationPatient = instance.getTagValue('x00200037'); + const instance = instances[i].getData().metadata; + const { + Rows, + Columns, + SamplesPerPixel, + ImageOrientationPatient, + } = instance; if ( - rows !== firstImageRows || - columns !== firstImageColumns || - samplesPerPixel !== firstImageSamplesPerPixel || - imageOrientationPatient !== firstImageOrientationPatient + Rows !== firstImageRows || + Columns !== firstImageColumns || + SamplesPerPixel !== firstImageSamplesPerPixel || + !_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient) ) { return { value: false }; } @@ -61,28 +67,32 @@ function processSingleframe(instances) { let missingFrames = 0; - // Check if frame spacing is approximately equal within a tolerance. + // Check if frame spacing is approximately equal within a spacingTolerance. // If spacing is on a uniform grid but we are missing frames, // Allow reconstruction, but pass back the number of missing frames. if (instances.length > 2) { - const firstIpp = _getImagePositionPatient(firstImage); - const lastIpp = _getImagePositionPatient(instances[instances.length - 1]); + const lastIpp = instances[instances.length - 1].getData().metadata + .ImagePositionPatient; - // We can't reconstruct if we are missing imagePositionPatient values - if (!firstIpp || !lastIpp) { + // We can't reconstruct if we are missing ImagePositionPatient values + if (!firstImagePositionPatient || !lastIpp) { return { value: false }; } const averageSpacingBetweenFrames = - _getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1); + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / + (instances.length - 1); - let previousIpp = firstIpp; + let previousImagePositionPatient = firstImagePositionPatient; for (let i = 1; i < instances.length; i++) { - const instance = instances[i]; - const ipp = _getImagePositionPatient(instance); + const instance = instances[i].getData().metadata; + const { ImagePositionPatient } = instance; - const spacingBetweenFrames = _getPerpendicularDistance(ipp, previousIpp); + const spacingBetweenFrames = _getPerpendicularDistance( + ImagePositionPatient, + previousImagePositionPatient + ); const spacingIssue = _getSpacingIssue( spacingBetweenFrames, averageSpacingBetweenFrames @@ -98,15 +108,28 @@ function processSingleframe(instances) { } } - previousIpp = ipp; + previousImagePositionPatient = ImagePositionPatient; } } return { value: true, missingFrames }; } -// TODO: Is 10% a reasonable tolerance for spacing? -const tolerance = 0.1; +function _isSameOrientation(iop1, iop2) { + if (iop1 === undefined || !iop2 === undefined) { + return; + } + + return ( + Math.abs(iop1[0] - iop2[0]) < iopTolerance && + Math.abs(iop1[1] - iop2[1]) < iopTolerance && + Math.abs(iop1[2] - iop2[2]) < iopTolerance + ); +} + +// TODO: Is 10% a reasonable spacingTolerance for spacing? +const spacingTolerance = 0.1; +const iopTolerance = 0.01; /** * Checks for spacing issues. @@ -118,7 +141,7 @@ const tolerance = 0.1; */ function _getSpacingIssue(spacing, averageSpacing) { const equalWithinTolerance = - Math.abs(spacing - averageSpacing) < averageSpacing * tolerance; + Math.abs(spacing - averageSpacing) < averageSpacing * spacingTolerance; if (equalWithinTolerance) { return; @@ -131,7 +154,7 @@ function _getSpacingIssue(spacing, averageSpacing) { const errorForEachSpacing = Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings; - if (errorForEachSpacing < tolerance * averageSpacing) { + if (errorForEachSpacing < spacingTolerance * averageSpacing) { return { issue: reconstructionIssues.MISSING_FRAMES, missingFrames: numberOfSpacings - 1, @@ -141,18 +164,6 @@ function _getSpacingIssue(spacing, averageSpacing) { return { issue: reconstructionIssues.IRREGULAR_SPACING }; } -function _getImagePositionPatient(instance) { - const tagValue = instance - .getTagValue('x00200032'); - if (!tagValue) { - return; - } - - return tagValue - .split('\\') - .map(element => Number(element)); -} - function _getPerpendicularDistance(a, b) { return Math.sqrt( Math.pow(a[0] - b[0], 2) + diff --git a/platform/core/src/utils/isImage.js b/platform/core/src/utils/isImage.js index db1f79dc6..5720f046c 100644 --- a/platform/core/src/utils/isImage.js +++ b/platform/core/src/utils/isImage.js @@ -53,10 +53,10 @@ const imagesTypes = [ /** * Checks whether dicom files with specified SOP Class UID have image data - * @param {string} sopClassUid - SOP Class UID to be checked + * @param {string} SOPClassUID - SOP Class UID to be checked * @returns {boolean} - true if it has image data */ -export const isImage = sopClassUid => { - if (!sopClassUid) return false; - return imagesTypes.indexOf(sopClassUid) !== -1; +export const isImage = SOPClassUID => { + if (!SOPClassUID) return false; + return imagesTypes.indexOf(SOPClassUID) !== -1; }; diff --git a/platform/core/src/utils/isLowPriorityModality.js b/platform/core/src/utils/isLowPriorityModality.js index 218a3c3b5..d2794a8ce 100644 --- a/platform/core/src/utils/isLowPriorityModality.js +++ b/platform/core/src/utils/isLowPriorityModality.js @@ -1,5 +1,5 @@ const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']); -export default function isLowPriorityModality(modality) { - return LOW_PRIORITY_MODALITIES.includes(modality); +export default function isLowPriorityModality(Modality) { + return LOW_PRIORITY_MODALITIES.includes(Modality); } diff --git a/platform/core/src/utils/metadataProvider/fetchOverlayData.js b/platform/core/src/utils/metadataProvider/fetchOverlayData.js new file mode 100644 index 000000000..ba9cb9ccc --- /dev/null +++ b/platform/core/src/utils/metadataProvider/fetchOverlayData.js @@ -0,0 +1,78 @@ +import { api } from 'dicomweb-client'; +import DICOMWeb from '../../DICOMWeb'; + +export default async function fetchOverlayData(instance, server) { + const OverlayDataPromises = []; + const OverlayDataTags = []; + + return new Promise((resolve, reject) => { + for (let overlayGroup = 0x00; overlayGroup <= 0x1e; overlayGroup += 0x02) { + let groupStr = `60${overlayGroup.toString(16)}`; + + if (groupStr.length === 3) { + groupStr = `600${overlayGroup.toString(16)}`; + } + + const OverlayDataTag = `${groupStr}3000`; + + if (instance[OverlayDataTag] && instance[OverlayDataTag].BulkDataURI) { + OverlayDataPromises.push( + _getOverlayData(instance[OverlayDataTag], server) + ); + OverlayDataTags.push(OverlayDataTag); + } + } + + if (OverlayDataPromises.length) { + Promise.all(OverlayDataPromises).then(results => { + for (let i = 0; i < results.length; i++) { + instance[OverlayDataTags[i]] = results[i]; + } + + resolve(); + }); + } else { + resolve(); + } + }); +} + +async function _getOverlayData(tag, server) { + const { BulkDataURI } = tag; + + let uri = BulkDataURI; + + // TODO: Workaround for dcm4chee behind SSL-terminating proxy returning + // incorrect bulk data URIs + if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) { + uri = uri.replace('http', 'https'); + } + + const config = { + url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used + headers: DICOMWeb.getAuthorizationHeader(server), + }; + const dicomWeb = new api.DICOMwebClient(config); + const options = { + BulkDataURI: uri, + }; + + return dicomWeb + .retrieveBulkData(options) + .then(result => result[0]) + .then(_unpackOverlay); +} + +function _unpackOverlay(arrayBuffer) { + const bitArray = new Uint8Array(arrayBuffer); + const byteArray = new Uint8Array(8 * bitArray.length); + + for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) { + const bitIndex = byteIndex % 8; + const bitByteIndex = Math.floor(byteIndex / 8); + byteArray[byteIndex] = + 1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex); + } + + return byteArray; +} diff --git a/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js new file mode 100644 index 000000000..65ca00608 --- /dev/null +++ b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js @@ -0,0 +1,187 @@ +import { api } from 'dicomweb-client'; +import DICOMWeb from '../../DICOMWeb'; + +export default async function fetchPaletteColorLookupTableData( + instance, + server +) { + const { + PaletteColorLookupTableUID, + RedPaletteColorLookupTableDescriptor, + GreenPaletteColorLookupTableDescriptor, + BluePaletteColorLookupTableDescriptor, + RedPaletteColorLookupTableData, + GreenPaletteColorLookupTableData, + BluePaletteColorLookupTableData, + } = instance; + + return new Promise((resolve, reject) => { + let entry; + if (_paletteColorCache.isValidUID(PaletteColorLookupTableUID)) { + entry = _paletteColorCache.get(PaletteColorLookupTableUID); + + if (entry) { + return resolve(entry); + } + } + + // no entry in cache... Fetch remote data. + const promises = [ + _getPaletteColor( + server, + RedPaletteColorLookupTableData, + RedPaletteColorLookupTableDescriptor + ), + _getPaletteColor( + server, + GreenPaletteColorLookupTableData, + GreenPaletteColorLookupTableDescriptor + ), + _getPaletteColor( + server, + BluePaletteColorLookupTableData, + BluePaletteColorLookupTableDescriptor + ), + ]; + + Promise.all(promises).then( + ([ + RedPaletteColorLookupTableData, + GreenPaletteColorLookupTableData, + BluePaletteColorLookupTableData, + ]) => { + // when PaletteColorLookupTableUID is present, the entry can be cached... + _paletteColorCache.add({ + RedPaletteColorLookupTableData, + GreenPaletteColorLookupTableData, + BluePaletteColorLookupTableData, + PaletteColorLookupTableUID, + }); + + instance.RedPaletteColorLookupTableData = RedPaletteColorLookupTableData; + instance.GreenPaletteColorLookupTableData = GreenPaletteColorLookupTableData; + instance.BluePaletteColorLookupTableData = BluePaletteColorLookupTableData; + + resolve(); + } + ); + }); +} + +/** + * Simple cache schema for retrieved color palettes. + */ +const _paletteColorCache = { + count: 0, + maxAge: 24 * 60 * 60 * 1000, // 24h cache? + entries: {}, + isValidUID: function(PaletteColorLookupTableUID) { + return ( + typeof PaletteColorLookupTableUID === 'string' && + PaletteColorLookupTableUID.length > 0 + ); + }, + get: function(PaletteColorLookupTableUID) { + let entry = null; + if (this.entries.hasOwnProperty(PaletteColorLookupTableUID)) { + entry = this.entries[PaletteColorLookupTableUID]; + // check how the entry is... + if (Date.now() - entry.time > this.maxAge) { + // entry is too old... remove entry. + delete this.entries[PaletteColorLookupTableUID]; + this.count--; + entry = null; + } + } + return entry; + }, + add: function(entry) { + if (this.isValidUID(entry.uid)) { + let PaletteColorLookupTableUID = entry.uid; + if (this.entries.hasOwnProperty(PaletteColorLookupTableUID) !== true) { + this.count++; // increment cache entry count... + } + entry.time = Date.now(); + this.entries[PaletteColorLookupTableUID] = entry; + // @TODO: Add logic to get rid of old entries and reduce memory usage... + } + }, +}; + +function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) { + const numLutEntries = lutDescriptor[0]; + const bits = lutDescriptor[2]; + + const readUInt16 = (byteArray, position) => { + return byteArray[position] + byteArray[position + 1] * 256; + }; + + const arrayBufferToPaletteColorLUT = arraybuffer => { + const byteArray = new Uint8Array(arraybuffer); + const lut = []; + + if (bits === 16) { + for (let i = 0; i < numLutEntries; i++) { + lut[i] = readUInt16(byteArray, i * 2); + } + } else { + for (let i = 0; i < numLutEntries; i++) { + lut[i] = byteArray[i]; + } + } + + return lut; + }; + + if (paletteColorLookupTableData.BulkDataURI) { + let uri = paletteColorLookupTableData.BulkDataURI; + + // TODO: Workaround for dcm4chee behind SSL-terminating proxy returning + // incorrect bulk data URIs + if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) { + uri = uri.replace('http', 'https'); + } + + const config = { + url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used + headers: DICOMWeb.getAuthorizationHeader(server), + }; + const dicomWeb = new api.DICOMwebClient(config); + const options = { + BulkDataURI: uri, + }; + + return dicomWeb + .retrieveBulkData(options) + .then(result => result[0]) + .then(arrayBufferToPaletteColorLUT); + } else if (paletteColorLookupTableData.InlineBinary) { + const inlineBinaryData = atob(paletteColorLookupTableData.InlineBinary); + const arraybuffer = _str2ab(inlineBinaryData); + + return new Promise(resolve => { + resolve(arrayBufferToPaletteColorLUT(arraybuffer)); + }); + } + + throw new Error( + 'Palette Color LUT was not provided as InlineBinary or BulkDataURI' + ); +} + +/** + * Convert String to ArrayBuffer + * + * @param {String} str Input String + * @return {ArrayBuffer} Output converted ArrayBuffer + */ +function _str2ab(str) { + const strLen = str.length; + const bytes = new Uint8Array(strLen); + + for (let i = 0; i < strLen; i++) { + bytes[i] = str.charCodeAt(i); + } + + return bytes.buffer; +} diff --git a/platform/core/src/utils/metadataProvider/metadataProviderFallbackTags.js b/platform/core/src/utils/metadataProvider/metadataProviderFallbackTags.js new file mode 100644 index 000000000..f75faf3f3 --- /dev/null +++ b/platform/core/src/utils/metadataProvider/metadataProviderFallbackTags.js @@ -0,0 +1,27 @@ +import log from '../../log'; + +function getFallbackTagFromInstance(tag, instance) { + if (instance[tag]) { + return instance[tag]; + } + + const fallbackTags = fallbackTagsMap[tag]; + + if (fallbackTags) { + for (let i = 0; i < fallbackTags.length; i++) { + const fallbackTag = fallbackTags[i]; + + if (instance[fallbackTag]) { + log.info(`metadata provider fallback tag ${tag} to ${fallbackTag}`); + + return instance[fallbackTag]; + } + } + } +} + +const fallbackTagsMap = { + PixelSpacing: ['ImagerPixelSpacing'], +}; + +export { fallbackTagsMap, getFallbackTagFromInstance }; diff --git a/platform/core/src/utils/updateMetaDataManager.js b/platform/core/src/utils/updateMetaDataManager.js deleted file mode 100644 index cd6256025..000000000 --- a/platform/core/src/utils/updateMetaDataManager.js +++ /dev/null @@ -1,109 +0,0 @@ -import { WadoRsMetaDataBuilder } from '../classes/metadata/WadoRsMetaDataBuilder'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; -import getWADORSImageId from './getWADORSImageId'; - -function getRadiopharmaceuticalInfoMetaData(instance) { - const radiopharmaceuticalInfo = instance.radiopharmaceuticalInfo; - - if (instance.modality !== 'PT' || !radiopharmaceuticalInfo) { - return; - } - - return new WadoRsMetaDataBuilder() - .addTag('00181072', radiopharmaceuticalInfo.radiopharmaceuticalStartTime) - .addTag('00181074', radiopharmaceuticalInfo.radionuclideTotalDose) - .addTag('00181075', radiopharmaceuticalInfo.radionuclideHalfLife) - .toJSON(); -} - -const getWadoRsInstanceMetaData = (study, series, instance) => { - return new WadoRsMetaDataBuilder() - .addTag('00080016', instance.sopClassUid) - .addTag('00080018', instance.sopInstanceUid) - .addTag('00080021', series.seriesDate) - .addTag('00080031', series.seriesTime) - .addTag('0008103e', series.seriesDescription) - .addTag('00080060', series.modality) - .addTag('00101010', study.patientAge) - .addTag('00101020', study.patientSize) - .addTag('00101030', study.patientWeight) - .addTag('0020000d', study.studyInstanceUid) - .addTag('00081030', study.studyDescription) - .addTag('00100010', study.patientName) - .addTag('00100020', study.patientId) - .addTag('00080020', study.studyDate) - .addTag('00080030', study.studyTime) - .addTag('00080050', study.accessionNumber) - .addTag('00200013', instance.instanceNumber) - .addTag('00180050', instance.sliceThickness) - .addTag('0020000e', series.seriesInstanceUid) - .addTag('00200011', series.seriesNumber) - .addTag('00200032', instance.imagePositionPatient, true) - .addTag('00200037', instance.imageOrientationPatient, true) - .addTag('00200052', instance.frameOfReferenceUID) - .addTag('00201041', instance.sliceLocation) - .addTag('00280002', instance.samplesPerPixel) - .addTag('00280004', instance.photometricInterpretation) - .addTag('00280006', instance.planarConfiguration) - .addTag('00280010', instance.rows) - .addTag('00280011', instance.columns) - .addTag('00280030', instance.pixelSpacing, true) - .addTag('00280034', instance.pixelAspectRatio, true) - .addTag('00280100', instance.bitsAllocated) - .addTag('00280101', instance.bitsStored) - .addTag('00280102', instance.highBit) - .addTag('00280103', instance.pixelRepresentation) - .addTag('00280106', instance.smallestPixelValue) - .addTag('00280107', instance.largestPixelValue) - .addTag('00281050', instance.windowCenter, true) - .addTag('00281051', instance.windowWidth, true) - .addTag('00281052', instance.rescaleIntercept) - .addTag('00281053', instance.rescaleSlope) - .addTag('00281054', instance.rescaleType) - .addTag('00281101', instance.redPaletteColorLookupTableDescriptor) - .addTag('00281102', instance.greenPaletteColorLookupTableDescriptor) - .addTag('00281103', instance.bluePaletteColorLookupTableDescriptor) - .addTag('00281201', instance.redPaletteColorLookupTableData) - .addTag('00281202', instance.greenPaletteColorLookupTableData) - .addTag('00281203', instance.bluePaletteColorLookupTableData) - .addTag('00540016', getRadiopharmaceuticalInfoMetaData(instance)) - .toJSON(); -}; - -/** - * Update metadata manager with instances of a specifc series - * @param {Object} study A plain study descriptor object - * @param {Object} series A Series descriptor object contaning the instances to be added to the manager - */ -function updateMetaDataManagerForSeries(study, series) { - series.instances.forEach(instance => { - const metaData = getWadoRsInstanceMetaData(study, series, instance); - const numberOfFrames = instance.numberOfFrames || 1; - - // We can share the same metaData with all frames because it doesn't have - // any frame specific data, such as frameNumber, pixelData, offset, etc. - // WADO-RS frame number is 1-based - for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) { - const imageId = getWADORSImageId(instance, frameNumber); - - // TODO Make a metadata manager which uses study/series/instance UIDS instead of imageIds - cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData); - } - }); -} - -/** - * Update metadata manager - * @param {Object} study A plain study descriptor object - * @param {string} [seriesInstanceUid] The Series Instance UID of the series to be added (Optional) - */ -export default function updateMetaDataManager(study, seriesInstanceUid) { - if (seriesInstanceUid) { - const series = study.seriesMap[seriesInstanceUid]; - updateMetaDataManagerForSeries(study, series); - } else { - study.seriesList.forEach(series => { - updateMetaDataManagerForSeries(study, series); - }); - } -} diff --git a/platform/i18n/src/locales/en-US/Common.json b/platform/i18n/src/locales/en-US/Common.json index 64c587150..29b11dd69 100644 --- a/platform/i18n/src/locales/en-US/Common.json +++ b/platform/i18n/src/locales/en-US/Common.json @@ -13,4 +13,4 @@ "Show": "Show", "Stop": "Stop", "StudyDate": "Study Date" -} \ No newline at end of file +} diff --git a/platform/ui/src/components/layoutButton/LayoutChooser.js b/platform/ui/src/components/layoutButton/LayoutChooser.js index a1ff45412..ec993149b 100644 --- a/platform/ui/src/components/layoutButton/LayoutChooser.js +++ b/platform/ui/src/components/layoutButton/LayoutChooser.js @@ -10,8 +10,8 @@ import './LayoutChooser.styl'; class LayoutChooser extends PureComponent { static propTypes = { - rows: PropTypes.number.isRequired, - columns: PropTypes.number.isRequired, + Rows: PropTypes.number.isRequired, + Columns: PropTypes.number.isRequired, visible: PropTypes.bool.isRequired, selectedCell: PropTypes.object, boxSize: PropTypes.number.isRequired, @@ -21,8 +21,8 @@ class LayoutChooser extends PureComponent { }; static defaultProps = { - rows: 3, - columns: 3, + Rows: 3, + Columns: 3, visible: true, boxSize: 20, cellBorder: 1, @@ -63,9 +63,9 @@ class LayoutChooser extends PureComponent { }; highlightCells = currentCell => { let table = []; - for (let row = 0; row < this.props.rows; row++) { + for (let row = 0; row < this.props.Rows; row++) { let newRow = []; - for (let col = 0; col < this.props.columns; col++) { + for (let col = 0; col < this.props.Columns; col++) { let cell = { row: row, col: col }; if (this.isRange(cell, currentCell)) { cell.className = 'hover'; @@ -83,11 +83,11 @@ class LayoutChooser extends PureComponent { }; render() { - let columns = this.props.columns; + let Columns = this.props.Columns; const style = { display: this.props.visible ? 'block' : 'none', minWidth: - columns * this.props.boxSize + (columns + 5) * this.props.cellBorder, + Columns * this.props.boxSize + (Columns + 5) * this.props.cellBorder, }; return (
@@ -68,8 +66,8 @@ export class QuickSwitch extends Component { @@ -82,7 +80,7 @@ export class QuickSwitch extends Component {
@@ -92,29 +90,29 @@ export class QuickSwitch extends Component { } getSeriesItems = () => { - let seriesListData; + let seriesData; - if (this.props.seriesListData) { - seriesListData = this.props.seriesListData; - } else if (this.state.activeStudyInstanceUid) { + if (this.props.seriesData) { + seriesData = this.props.seriesData; + } else if (this.state.activeStudyInstanceUID) { const study = this.props.studyListData.find( - study => study.studyInstanceUid === this.state.activeStudyInstanceUid + study => study.StudyInstanceUID === this.state.activeStudyInstanceUID ); - seriesListData = study.thumbnails; + seriesData = study.thumbnails; } else { - seriesListData = this.props.studyListData[0].thumbnails; + seriesData = this.props.studyListData[0].thumbnails; } - return seriesListData || []; + return seriesData || []; }; getSmallListItems = () => { const seriesItems = this.getSeriesItems() || []; return seriesItems.map((seriesData, index) => { const active = - seriesData.displaySetInstanceUid === - this.state.activeDisplaySetInstanceUid; + seriesData.displaySetInstanceUID === + this.state.activeDisplaySetInstanceUID; return (
); @@ -126,14 +124,14 @@ export class QuickSwitch extends Component { this.props.onStudySelected(studyDataSelected); } this.setState({ - activeStudyInstanceUid: studyDataSelected.studyInstanceUid, + activeStudyInstanceUID: studyDataSelected.StudyInstanceUID, seriesQuickSwitchOpen: true, }); }; onSeriesClick = seriesDataSelected => { this.setState({ - activeDisplaySetInstanceUid: seriesDataSelected.displaySetInstanceUid, + activeDisplaySetInstanceUID: seriesDataSelected.displaySetInstanceUID, }); this.props.onSeriesSelected(seriesDataSelected); diff --git a/platform/ui/src/components/quickSwitch/SeriesList.js b/platform/ui/src/components/quickSwitch/SeriesList.js index 2108c25d5..ad0c62c76 100644 --- a/platform/ui/src/components/quickSwitch/SeriesList.js +++ b/platform/ui/src/components/quickSwitch/SeriesList.js @@ -8,7 +8,7 @@ export class SeriesList extends Component { static propTypes = { seriesItems: PropTypes.array.isRequired, onClick: PropTypes.func.isRequired, - activeDisplaySetInstanceUid: PropTypes.string, + activeDisplaySetInstanceUID: PropTypes.string, }; render() { @@ -25,12 +25,12 @@ export class SeriesList extends Component { return this.props.seriesItems.map((seriesData, index) => { return ( this.props.onClick(seriesData)} /> diff --git a/platform/ui/src/components/quickSwitch/StudiesItem.js b/platform/ui/src/components/quickSwitch/StudiesItem.js index c43fe281c..ff3d8d2bb 100644 --- a/platform/ui/src/components/quickSwitch/StudiesItem.js +++ b/platform/ui/src/components/quickSwitch/StudiesItem.js @@ -12,13 +12,13 @@ export class StudiesItem extends Component { render() { const { - studyDate, - studyDescription, + StudyDate, + StudyDescription, modalities, studyAvailable, } = this.props.studyData; const activeClass = this.props.active ? ' active' : ''; - const hasDescriptionAndDate = studyDate && studyDescription; + const hasDescriptionAndDate = StudyDate && StudyDescription; return (
{hasDescriptionAndDate ? ( -
{studyDate}
-
{studyDescription}
+
{StudyDate}
+
{StudyDescription}
) : (
diff --git a/platform/ui/src/components/quickSwitch/StudiesItem.styl b/platform/ui/src/components/quickSwitch/StudiesItem.styl index 98c494844..4302ce1f1 100644 --- a/platform/ui/src/components/quickSwitch/StudiesItem.styl +++ b/platform/ui/src/components/quickSwitch/StudiesItem.styl @@ -8,7 +8,7 @@ $nestingMargin = 6px $spacerX = 7px $spacerY = 12px -.studyBrowseItem +.studyBrowseItem margin-bottom: 10px; position: relative // required transformation to make inner fixed elements relative to this one @@ -58,7 +58,7 @@ $spacerY = 12px &.additional color: var(--text-secondary-color) - + .studyModality color: var(--primary-background-color) & diff --git a/platform/ui/src/components/quickSwitch/StudiesList.js b/platform/ui/src/components/quickSwitch/StudiesList.js index 414b46944..15053557b 100644 --- a/platform/ui/src/components/quickSwitch/StudiesList.js +++ b/platform/ui/src/components/quickSwitch/StudiesList.js @@ -9,7 +9,7 @@ export class StudiesList extends Component { class: PropTypes.string, studyListData: PropTypes.array.isRequired, onClick: PropTypes.func.isRequired, - activeStudyInstanceUid: PropTypes.string, + activeStudyInstanceUID: PropTypes.string, }; render() { @@ -27,7 +27,7 @@ export class StudiesList extends Component { key={index} studyData={studyData} active={ - studyData.studyInstanceUid === this.props.activeStudyInstanceUid + studyData.StudyInstanceUID === this.props.activeStudyInstanceUID } onClick={event => this.props.onClick(studyData)} /> diff --git a/platform/ui/src/components/quickSwitch/__docs__/quickSwitch.mdx b/platform/ui/src/components/quickSwitch/__docs__/quickSwitch.mdx index c14670d9d..75b228448 100644 --- a/platform/ui/src/components/quickSwitch/__docs__/quickSwitch.mdx +++ b/platform/ui/src/components/quickSwitch/__docs__/quickSwitch.mdx @@ -23,17 +23,17 @@ import studies from './studies.js' studyListData={state.studyListData} // If this is always activeStudy's thumbnails, // Why do we need to specify this? - seriesListData={ + seriesData={ state.studyListData.find((study) => { return !!study.active; }).thumbnails} onSeriesSelected={(selectedSeries) => { const { studyListData } = state; - const selectedId = selectedSeries.displaySetInstanceUid; + const selectedId = selectedSeries.displaySetInstanceUID; studyListData.forEach(study => { study.thumbnails.forEach(series => { - series.active = series.displaySetInstanceUid === selectedId; + series.active = series.displaySetInstanceUID === selectedId; }) }); @@ -41,10 +41,10 @@ import studies from './studies.js' }} onStudySelected={(selectedStudy) => { const { studyListData } = state; - const selectedId = selectedStudy.studyInstanceUid; + const selectedId = selectedStudy.StudyInstanceUID; studyListData.forEach(study => { - study.active = study.studyInstanceUid === selectedId; + study.active = study.StudyInstanceUID === selectedId; }); setState({ studyListData }); diff --git a/platform/ui/src/components/quickSwitch/__docs__/studies.js b/platform/ui/src/components/quickSwitch/__docs__/studies.js index c4c5250d9..2580fda90 100644 --- a/platform/ui/src/components/quickSwitch/__docs__/studies.js +++ b/platform/ui/src/components/quickSwitch/__docs__/studies.js @@ -1,74 +1,74 @@ export default [ { - studyInstanceUid: '10001', - studyDescription: 'Anti-PD-1', + StudyInstanceUID: '10001', + StudyDescription: 'Anti-PD-1', modalities: 'CT', - studyDate: '18-nov-2018', + StudyDate: '18-nov-2018', active: false, thumbnails: [ { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg', - seriesDescription: 'Anti-PD-1_Lung', - seriesNumber: '2', + SeriesDescription: 'Anti-PD-1_Lung', + SeriesNumber: '2', numImageFrames: 512, - displaySetInstanceUid: '10001-1', + displaySetInstanceUID: '10001-1', stackPercentComplete: 30, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg', - seriesDescription: 'Anti-PD-1_MELANOMA', - seriesNumber: '2', - instanceNumber: '1', + SeriesDescription: 'Anti-PD-1_MELANOMA', + SeriesNumber: '2', + InstanceNumber: '1', numImageFrames: 256, - displaySetInstanceUid: '10001-2', + displaySetInstanceUID: '10001-2', stackPercentComplete: 70, }, ], }, { - studyInstanceUid: '10002', - studyDescription: 'CPTAC', + StudyInstanceUID: '10002', + StudyDescription: 'CPTAC', modalities: 'CT', - studyDate: '16-aug-2017', + StudyDate: '16-aug-2017', active: true, thumbnails: [ { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg', - seriesDescription: 'CPTAC-GBM', + SeriesDescription: 'CPTAC-GBM', active: true, - seriesNumber: '2', + SeriesNumber: '2', numImageFrames: 512, - displaySetInstanceUid: '10002-1', + displaySetInstanceUID: '10002-1', stackPercentComplete: 100, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg', - seriesDescription: 'CPTAC-CM', - seriesNumber: '2', - instanceNumber: '1', - displaySetInstanceUid: '10002-2', + SeriesDescription: 'CPTAC-CM', + SeriesNumber: '2', + InstanceNumber: '1', + displaySetInstanceUID: '10002-2', numImageFrames: 256, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg', - seriesDescription: 'CPTAC-HNSCC', - seriesNumber: '2', - instanceNumber: '1', - displaySetInstanceUid: '10002-3', + SeriesDescription: 'CPTAC-HNSCC', + SeriesNumber: '2', + InstanceNumber: '1', + displaySetInstanceUID: '10002-3', numImageFrames: 256, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg', - seriesDescription: 'CPTAC-LSCC', - seriesNumber: '2', - instanceNumber: '1', - displaySetInstanceUid: '10002-4', + SeriesDescription: 'CPTAC-LSCC', + SeriesNumber: '2', + InstanceNumber: '1', + displaySetInstanceUID: '10002-4', numImageFrames: 256, }, ], diff --git a/platform/ui/src/components/studyBrowser/StudyBrowser.js b/platform/ui/src/components/studyBrowser/StudyBrowser.js index fdacf3ec4..921888807 100644 --- a/platform/ui/src/components/studyBrowser/StudyBrowser.js +++ b/platform/ui/src/components/studyBrowser/StudyBrowser.js @@ -16,23 +16,23 @@ function StudyBrowser(props) {
{studies .map((study, studyIndex) => { - const { studyInstanceUid } = study; + const { StudyInstanceUID } = study; return study.thumbnails.map((thumb, thumbIndex) => { // TODO: Thumb has more props than we care about? const { altImageText, - displaySetInstanceUid, + displaySetInstanceUID, imageId, - instanceNumber, + InstanceNumber, numImageFrames, - seriesDescription, - seriesNumber, + SeriesDescription, + SeriesNumber, stackPercentComplete, } = thumb; return (
@@ -41,20 +41,20 @@ function StudyBrowser(props) { key={`${studyIndex}_${thumbIndex}`} id={`${studyIndex}_${thumbIndex}`} // Unused? // Study - studyInstanceUid={studyInstanceUid} // used by drop + StudyInstanceUID={StudyInstanceUID} // used by drop // Thumb altImageText={altImageText} imageId={imageId} - instanceNumber={instanceNumber} - displaySetInstanceUid={displaySetInstanceUid} // used by drop + InstanceNumber={InstanceNumber} + displaySetInstanceUID={displaySetInstanceUID} // used by drop numImageFrames={numImageFrames} - seriesDescription={seriesDescription} - seriesNumber={seriesNumber} + SeriesDescription={SeriesDescription} + SeriesNumber={SeriesNumber} stackPercentComplete={stackPercentComplete} // Events onClick={onThumbnailClick.bind( undefined, - displaySetInstanceUid + displaySetInstanceUID )} onDoubleClick={onThumbnailDoubleClick} /> @@ -73,16 +73,16 @@ const noop = () => {}; StudyBrowser.propTypes = { studies: PropTypes.arrayOf( PropTypes.shape({ - studyInstanceUid: PropTypes.string.isRequired, + StudyInstanceUID: PropTypes.string.isRequired, thumbnails: PropTypes.arrayOf( PropTypes.shape({ altImageText: PropTypes.string, - displaySetInstanceUid: PropTypes.string.isRequired, + displaySetInstanceUID: PropTypes.string.isRequired, imageId: PropTypes.string, - instanceNumber: PropTypes.number, + InstanceNumber: PropTypes.number, numImageFrames: PropTypes.number, - seriesDescription: PropTypes.string, - seriesNumber: PropTypes.number, + SeriesDescription: PropTypes.string, + SeriesNumber: PropTypes.number, stackPercentComplete: PropTypes.number, }) ), diff --git a/platform/ui/src/components/studyBrowser/Thumbnail.js b/platform/ui/src/components/studyBrowser/Thumbnail.js index 2f93a56cf..66b102560 100644 --- a/platform/ui/src/components/studyBrowser/Thumbnail.js +++ b/platform/ui/src/components/studyBrowser/Thumbnail.js @@ -7,12 +7,12 @@ import classNames from 'classnames'; import './Thumbnail.styl'; function ThumbnailFooter({ - seriesDescription, - seriesNumber, - instanceNumber, + SeriesDescription, + SeriesNumber, + InstanceNumber, numImageFrames, }) { - const infoOnly = !seriesDescription; + const infoOnly = !SeriesDescription; const getInfo = (value, icon, className = '') => { return ( @@ -23,18 +23,18 @@ function ThumbnailFooter({ ); }; const getSeriesInformation = ( - seriesNumber, - instanceNumber, + SeriesNumber, + InstanceNumber, numImageFrames ) => { - if (!seriesNumber && !instanceNumber && !numImageFrames) { + if (!SeriesNumber && !InstanceNumber && !numImageFrames) { return; } return (
- {getInfo(seriesNumber, 'S:')} - {getInfo(instanceNumber, 'I:')} + {getInfo(SeriesNumber, 'S:')} + {getInfo(InstanceNumber, 'I:')} {getInfo(numImageFrames, '', 'image-frames')}
); @@ -42,8 +42,8 @@ function ThumbnailFooter({ return (
-
{seriesDescription}
- {getSeriesInformation(seriesNumber, instanceNumber, numImageFrames)} +
{SeriesDescription}
+ {getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames)}
); } @@ -53,15 +53,15 @@ function Thumbnail(props) { active, altImageText, error, - displaySetInstanceUid, + displaySetInstanceUID, imageId, imageSrc, - instanceNumber, + InstanceNumber, numImageFrames, - seriesDescription, - seriesNumber, + SeriesDescription, + SeriesNumber, stackPercentComplete, - studyInstanceUid, + StudyInstanceUID, onClick, onDoubleClick, onMouseDown, @@ -72,8 +72,8 @@ function Thumbnail(props) { // `droppedItem` in `dropTarget` // The only data it will have access to item: { - studyInstanceUid, - displaySetInstanceUid, + StudyInstanceUID, + displaySetInstanceUID, type: 'thumbnail', // Has to match `dropTarget`'s type }, canDrag: function(monitor) { @@ -117,8 +117,8 @@ const noop = () => {}; Thumbnail.propTypes = { supportsDrag: PropTypes.bool, id: PropTypes.string.isRequired, - displaySetInstanceUid: PropTypes.string.isRequired, - studyInstanceUid: PropTypes.string.isRequired, + displaySetInstanceUID: PropTypes.string.isRequired, + StudyInstanceUID: PropTypes.string.isRequired, imageSrc: PropTypes.string, imageId: PropTypes.string, error: PropTypes.bool, @@ -130,9 +130,9 @@ It will be displayed inside the
. This is useful when it is difficult to make a preview for a type of DICOM series (e.g. DICOM-SR) */ altImageText: PropTypes.string, - seriesDescription: PropTypes.string, - seriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - instanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + SeriesDescription: PropTypes.string, + SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), numImageFrames: PropTypes.number, onDoubleClick: PropTypes.func, onClick: PropTypes.func, diff --git a/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js b/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js index a8ce6327f..e505eac31 100644 --- a/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js +++ b/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js @@ -18,7 +18,7 @@ const divTarget = { component.setState({ item: { id: item.id, - seriesDescription: item.seriesDescription, + SeriesDescription: item.SeriesDescription, }, }); return { id: 'ExampleDropTarget' }; diff --git a/platform/ui/src/components/studyBrowser/__docs__/exampleStudies.js b/platform/ui/src/components/studyBrowser/__docs__/exampleStudies.js index ee6a6d321..a9ca38c67 100644 --- a/platform/ui/src/components/studyBrowser/__docs__/exampleStudies.js +++ b/platform/ui/src/components/studyBrowser/__docs__/exampleStudies.js @@ -4,25 +4,25 @@ export const studies = [ { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg', - seriesDescription: 'Anti-PD-1_Lung', + SeriesDescription: 'Anti-PD-1_Lung', active: true, - seriesNumber: '2', + SeriesNumber: '2', numImageFrames: 512, stackPercentComplete: 30, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg', - seriesDescription: 'Anti-PD-1_MELANOMA', - seriesNumber: '2', - instanceNumber: '1', + SeriesDescription: 'Anti-PD-1_MELANOMA', + SeriesNumber: '2', + InstanceNumber: '1', numImageFrames: 256, stackPercentComplete: 70, }, { altImageText: 'SR', - seriesDescription: 'Imaging Measurement Report', - seriesNumber: '3', + SeriesDescription: 'Imaging Measurement Report', + SeriesNumber: '3', stackPercentComplete: 100, }, ], @@ -32,34 +32,34 @@ export const studies = [ { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg', - seriesDescription: 'CPTAC-GBM', + SeriesDescription: 'CPTAC-GBM', active: true, - seriesNumber: '2', + SeriesNumber: '2', numImageFrames: 512, stackPercentComplete: 100, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg', - seriesDescription: 'CPTAC-CM', - seriesNumber: '2', - instanceNumber: '1', + SeriesDescription: 'CPTAC-CM', + SeriesNumber: '2', + InstanceNumber: '1', numImageFrames: 256, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg', - seriesDescription: 'CPTAC-HNSCC', - seriesNumber: '2', - instanceNumber: '1', + SeriesDescription: 'CPTAC-HNSCC', + SeriesNumber: '2', + InstanceNumber: '1', numImageFrames: 256, }, { imageSrc: 'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg', - seriesDescription: 'CPTAC-LSCC', - seriesNumber: '2', - instanceNumber: '1', + SeriesDescription: 'CPTAC-LSCC', + SeriesNumber: '2', + InstanceNumber: '1', numImageFrames: 256, }, ], diff --git a/platform/ui/src/components/studyList/StudyList.js b/platform/ui/src/components/studyList/StudyList.js index 9e6c56fad..c8fb7415d 100644 --- a/platform/ui/src/components/studyList/StudyList.js +++ b/platform/ui/src/components/studyList/StudyList.js @@ -43,25 +43,25 @@ function StudyList(props) { const largeTableMeta = [ { displayText: t('PatientName'), - fieldName: 'patientName', + fieldName: 'PatientName', inputType: 'text', size: 330, }, { displayText: t('MRN'), - fieldName: 'patientId', + fieldName: 'PatientID', inputType: 'text', size: 378, }, { displayText: t('AccessionNumber'), - fieldName: 'accessionNumber', + fieldName: 'AccessionNumber', inputType: 'text', size: 180, }, { displayText: t('StudyDate'), - fieldName: 'studyDate', + fieldName: 'StudyDate', inputType: 'date-range', size: 300, }, @@ -73,7 +73,7 @@ function StudyList(props) { }, { displayText: t('StudyDescription'), - fieldName: 'studyDescription', + fieldName: 'StudyDescription', inputType: 'text', size: 335, }, @@ -94,7 +94,7 @@ function StudyList(props) { }, { displayText: t('StudyDate'), - fieldName: 'studyDate', + fieldName: 'StudyDate', inputType: 'date-range', size: 300, }, @@ -178,15 +178,15 @@ function StudyList(props) { {!isLoading && studies.map((study, index) => ( handleSelectItem(studyInstanceUid)} - accessionNumber={study.accessionNumber || ''} + key={`${study.StudyInstanceUID}-${index}`} + onClick={StudyInstanceUID => handleSelectItem(StudyInstanceUID)} + AccessionNumber={study.AccessionNumber || ''} modalities={study.modalities} - patientId={study.patientId || ''} - patientName={study.patientName || ''} - studyDate={study.studyDate} - studyDescription={study.studyDescription || ''} - studyInstanceUid={study.studyInstanceUid} + PatientID={study.PatientID || ''} + PatientName={study.PatientName || ''} + StudyDate={study.StudyDate} + StudyDescription={study.StudyDescription || ''} + StudyInstanceUID={study.StudyInstanceUID} displaySize={displaySize} /> ))} @@ -208,12 +208,12 @@ StudyList.propTypes = { onSort: PropTypes.func.isRequired, // ~~ FILTERS filterValues: PropTypes.shape({ - patientName: PropTypes.string.isRequired, - patientId: PropTypes.string.isRequired, - accessionNumber: PropTypes.string.isRequired, - studyDate: PropTypes.string.isRequired, + PatientName: PropTypes.string.isRequired, + PatientID: PropTypes.string.isRequired, + AccessionNumber: PropTypes.string.isRequired, + StudyDate: PropTypes.string.isRequired, modalities: PropTypes.string.isRequired, - studyDescription: PropTypes.string.isRequired, + StudyDescription: PropTypes.string.isRequired, patientNameOrId: PropTypes.string.isRequired, accessionOrModalityOrDescription: PropTypes.string.isRequired, allFields: PropTypes.string.isRequired, @@ -229,14 +229,14 @@ StudyList.defaultProps = {}; function TableRow(props) { const { - accessionNumber, + AccessionNumber, isHighlighted, modalities, - patientId, - patientName, - studyDate, - studyDescription, - studyInstanceUid, + PatientID, + PatientName, + StudyDate, + StudyDescription, + StudyInstanceUID, onClick: handleClick, displaySize, } = props; @@ -245,30 +245,30 @@ function TableRow(props) { const largeRowTemplate = ( handleClick(studyInstanceUid)} + onClick={() => handleClick(StudyInstanceUID)} className={classNames({ active: isHighlighted })} > - - {patientName || `(${t('Empty')})`} + + {PatientName || `(${t('Empty')})`} - {patientId} - {accessionNumber} - {studyDate} + {PatientID} + {AccessionNumber} + {StudyDate} {modalities || `(${t('Empty')})`} - {studyDescription} + {StudyDescription} ); const mediumRowTemplate = ( handleClick(studyInstanceUid)} + onClick={() => handleClick(StudyInstanceUID)} className={classNames({ active: isHighlighted })} > - - {patientName || `(${t('Empty')})`} -
{patientId}
+ + {PatientName || `(${t('Empty')})`} +
{PatientID}
@@ -280,7 +280,7 @@ function TableRow(props) { flexGrow: 1, }} > - {studyDescription} + {StudyDescription}
{/* MODALITY & ACCESSION */} @@ -308,35 +308,35 @@ function TableRow(props) { whiteSpace: 'nowrap', overflow: 'hidden', }} - aria-label={accessionNumber} - title={accessionNumber} + aria-label={AccessionNumber} + title={AccessionNumber} > - {accessionNumber} + {AccessionNumber}
{/* DATE */} - {studyDate} + {StudyDate} ); const smallRowTemplate = ( handleClick(studyInstanceUid)} + onClick={() => handleClick(StudyInstanceUID)} className={classNames({ active: isHighlighted })} >
{/* NAME AND ID */}
- {patientName || `(${t('Empty')})`} + {PatientName || `(${t('Empty')})`}
-
{patientId}
+
{PatientID}
{/* DESCRIPTION */} @@ -348,7 +348,7 @@ function TableRow(props) { paddingLeft: '35px', }} > - {studyDescription} + {StudyDescription}
{/* MODALITY & DATE */} @@ -370,7 +370,7 @@ function TableRow(props) { > {modalities || `(${t('Empty')})`}
-
{studyDate}
+
{StudyDate}
@@ -391,14 +391,14 @@ function TableRow(props) { } TableRow.propTypes = { - accessionNumber: PropTypes.string.isRequired, + AccessionNumber: PropTypes.string.isRequired, isHighlighted: PropTypes.bool, modalities: PropTypes.string, - patientId: PropTypes.string.isRequired, - patientName: PropTypes.string.isRequired, - studyDate: PropTypes.string.isRequired, - studyDescription: PropTypes.string.isRequired, - studyInstanceUid: PropTypes.string.isRequired, + PatientID: PropTypes.string.isRequired, + PatientName: PropTypes.string.isRequired, + StudyDate: PropTypes.string.isRequired, + StudyDescription: PropTypes.string.isRequired, + StudyInstanceUID: PropTypes.string.isRequired, displaySize: PropTypes.string, }; diff --git a/platform/ui/src/components/studyList/StudyList.styl b/platform/ui/src/components/studyList/StudyList.styl index 564a00289..4d350f117 100644 --- a/platform/ui/src/components/studyList/StudyList.styl +++ b/platform/ui/src/components/studyList/StudyList.styl @@ -156,7 +156,7 @@ table.table text-align: left; border-top: 0; -.study-list-container > table.table > thead > tr > th.studyDate +.study-list-container > table.table > thead > tr > th.StudyDate min-width: 230px .study-list-container > table.table .modalities font-weight: 500; diff --git a/platform/ui/src/components/studyList/TablePagination.js b/platform/ui/src/components/studyList/TablePagination.js index 1a55b9481..ea6ed280f 100644 --- a/platform/ui/src/components/studyList/TablePagination.js +++ b/platform/ui/src/components/studyList/TablePagination.js @@ -11,7 +11,7 @@ class TablePagination extends PureComponent { }; static propTypes = { - /* Values to show in "rows per page" select dropdown */ + /* Values to show in "Rows per page" select dropdown */ pageOptions: PropTypes.array, rowsPerPage: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired, diff --git a/platform/ui/src/components/studyList/__docs__/onSearch.js b/platform/ui/src/components/studyList/__docs__/onSearch.js index 1101e76c9..17c7deb91 100644 --- a/platform/ui/src/components/studyList/__docs__/onSearch.js +++ b/platform/ui/src/components/studyList/__docs__/onSearch.js @@ -4,9 +4,9 @@ export default function(searchData) { this.setState({ searchData }); const filter = (key, searchData, study) => { - if (key === 'studyDateFrom' && searchData[key] && study['studyDate']) { - const studyDate = moment(study['studyDate'], 'YYYYMMDD'); - return studyDate.isBetween( + if (key === 'studyDateFrom' && searchData[key] && study['StudyDate']) { + const StudyDate = moment(study['StudyDate'], 'YYYYMMDD'); + return StudyDate.isBetween( searchData['studyDateFrom'], searchData['studyDateTo'], 'days', @@ -25,11 +25,11 @@ export default function(searchData) { let filteredStudies = this.defaultStudies .filter(function(study) { const all = [ - 'patientName', - 'patientId', - 'accessionNumber', + 'PatientName', + 'PatientID', + 'AccessionNumber', 'modalities', - 'studyDescription', + 'StudyDescription', 'studyDateFrom', ].every(key => { return filter(key, searchData, study); diff --git a/platform/ui/src/components/studyList/__docs__/studies.js b/platform/ui/src/components/studyList/__docs__/studies.js index 5538d634b..86de8c647 100644 --- a/platform/ui/src/components/studyList/__docs__/studies.js +++ b/platform/ui/src/components/studyList/__docs__/studies.js @@ -2,83 +2,83 @@ import moment from 'moment'; const studies = [ { - studyInstanceUid: '11111.111111.111111.11111', - patientName: 'John Doe', - patientId: '1', - accessionNumber: '1234567', - studyDate: '19930201', + StudyInstanceUID: '11111.111111.111111.11111', + PatientName: 'John Doe', + PatientID: '1', + AccessionNumber: '1234567', + StudyDate: '19930201', modalities: 'MR', - studyDescription: 'BRAIN', + StudyDescription: 'BRAIN', }, { - studyInstanceUid: '2222.222222.22222.22222', - patientName: 'José Silva', - patientId: '2', - accessionNumber: '7654321', - studyDate: moment().format('YYYYMMDD'), + StudyInstanceUID: '2222.222222.22222.22222', + PatientName: 'José Silva', + PatientID: '2', + AccessionNumber: '7654321', + StudyDate: moment().format('YYYYMMDD'), modalities: 'CT', - studyDescription: 'PET CT STANDARD', + StudyDescription: 'PET CT STANDARD', }, { - studyInstanceUid: '3333.333333.33333.33333', - patientName: 'Antônio Jefferson', - patientId: '3', - accessionNumber: '732311', - studyDate: moment() + StudyInstanceUID: '3333.333333.33333.33333', + PatientName: 'Antônio Jefferson', + PatientID: '3', + AccessionNumber: '732311', + StudyDate: moment() .subtract(14, 'days') .format('YYYYMMDD'), modalities: 'US', - studyDescription: '0', + StudyDescription: '0', }, { - studyInstanceUid: '444444.44444.44444.4444', - patientName: 'Antonio da Silva', - patientId: '4', - accessionNumber: '732311', - studyDate: moment() + StudyInstanceUID: '444444.44444.44444.4444', + PatientName: 'Antonio da Silva', + PatientID: '4', + AccessionNumber: '732311', + StudyDate: moment() .subtract(1, 'months') .format('YYYYMMDD'), modalities: 'US', - studyDescription: '0', + StudyDescription: '0', }, { - studyInstanceUid: '55555.55555.55555.55555', - patientName: 'Bezerra Souza', - patientId: '5', - accessionNumber: '5134543', - studyDate: moment() + StudyInstanceUID: '55555.55555.55555.55555', + PatientName: 'Bezerra Souza', + PatientID: '5', + AccessionNumber: '5134543', + StudyDate: moment() .subtract(6, 'days') .format('YYYYMMDD'), modalities: 'US', - studyDescription: '0', + StudyDescription: '0', }, { - studyInstanceUid: '66666.66666.66666.6666', - patientName: 'Geraldo Roger', - patientId: '6', - accessionNumber: '5315135', - studyDate: moment() + StudyInstanceUID: '66666.66666.66666.6666', + PatientName: 'Geraldo Roger', + PatientID: '6', + AccessionNumber: '5315135', + StudyDate: moment() .subtract(7, 'days') .format('YYYYMMDD'), modalities: 'US', - studyDescription: 'US', + StudyDescription: 'US', }, { - studyInstanceUid: '77777.77777.77777.77777', - patientName: '', - patientId: '7', - accessionNumber: '5315136', - studyDate: moment() + StudyInstanceUID: '77777.77777.77777.77777', + PatientName: '', + PatientID: '7', + AccessionNumber: '5315136', + StudyDate: moment() .subtract(5, 'days') .format('YYYYMMDD'), modalities: 'US', - studyDescription: 'US', + StudyDescription: 'US', }, ].sort(function(a, b) { - if (a.patientName < b.patientName) { + if (a.PatientName < b.PatientName) { return -1; } - if (a.patientName > b.patientName) { + if (a.PatientName > b.PatientName) { return 1; } return 0; diff --git a/platform/ui/src/components/studyList/__docs__/studyList.mdx b/platform/ui/src/components/studyList/__docs__/studyList.mdx index 86c33f686..a782aa9d1 100644 --- a/platform/ui/src/components/studyList/__docs__/studyList.mdx +++ b/platform/ui/src/components/studyList/__docs__/studyList.mdx @@ -20,11 +20,11 @@ import moment from 'moment' { - const studyDate = moment(study['studyDate'], 'YYYYMMDD'); + const StudyDate = moment(study['StudyDate'], 'YYYYMMDD'); const startDate = moment().subtract(5, 'days'); const endDate = moment(); - return studyDate.isBetween(startDate, endDate, 'days', '[]'); + return StudyDate.isBetween(startDate, endDate, 'days', '[]'); }).slice(0, 5) }}> {({ state, setState }) => ( @@ -35,9 +35,9 @@ import moment from 'moment' pageOptions={[1, 2, 3, 5, 10, 15, 20, 25, 50, 100]} studyListFunctionsEnabled={true} onImport={(e) => alert('Import study mock ' + e)} - onSelectItem={(studyInstanceUid) => { alert(studyInstanceUid + ' has selected! Now you can open your study.'); }} + onSelectItem={(StudyInstanceUID) => { alert(StudyInstanceUID + ' has selected! Now you can open your study.'); }} rowsPerPage={5} - defaultSort={{ field: 'patientName', order: 'desc', }} + defaultSort={{ field: 'PatientName', order: 'desc', }} studyListDateFilterNumDays={7} onSearch={onSearch.bind({ setState, defaultStudies })} /> diff --git a/platform/ui/src/elements/form/TextArea.js b/platform/ui/src/elements/form/TextArea.js index 7f132f267..6c53e3daf 100644 --- a/platform/ui/src/elements/form/TextArea.js +++ b/platform/ui/src/elements/form/TextArea.js @@ -19,7 +19,7 @@ class TextArea extends React.Component {