From ccdfcd4a85f1c3122c775d77a9c2e6401dc187cf Mon Sep 17 00:00:00 2001 From: James Petts Date: Thu, 24 Sep 2020 11:34:22 +0100 Subject: [PATCH] [IDC-2049] Sort Tags in Tag browser split items in sequences, add indent after space. (#2053) * Sort tag browser, add items, add indent. * Remove debugger. --- .../src/components/DicomBrowserSelect.js | 1 + .../src/components/DicomBrowserSelectItem.css | 1 + .../src/components/DicomTagBrowser.css | 2 +- .../src/components/DicomTagBrowser.js | 259 +++++++++++++----- platform/viewer/src/index.js | 2 +- 5 files changed, 187 insertions(+), 78 deletions(-) diff --git a/extensions/dicom-tag-browser/src/components/DicomBrowserSelect.js b/extensions/dicom-tag-browser/src/components/DicomBrowserSelect.js index f8d62f878..35e19bd55 100644 --- a/extensions/dicom-tag-browser/src/components/DicomBrowserSelect.js +++ b/extensions/dicom-tag-browser/src/components/DicomBrowserSelect.js @@ -39,6 +39,7 @@ const dicomBrowserSelectStyles = { }), option: (base, state) => ({ ...base, + width: '100%', cursor: 'pointer', '&:first-of-type': { borderTopLeftRadius: 5, diff --git a/extensions/dicom-tag-browser/src/components/DicomBrowserSelectItem.css b/extensions/dicom-tag-browser/src/components/DicomBrowserSelectItem.css index bc65eee35..b2977ac54 100644 --- a/extensions/dicom-tag-browser/src/components/DicomBrowserSelectItem.css +++ b/extensions/dicom-tag-browser/src/components/DicomBrowserSelectItem.css @@ -2,6 +2,7 @@ display: flex; justify-content: start; margin: 0; + width: 100%; } .dcmseg-segmentation-item .segmentation-meta { diff --git a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.css b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.css index 3b336a1dc..ea2341b0a 100644 --- a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.css +++ b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.css @@ -3,7 +3,7 @@ margin-left: auto; } -.dicom-tag-browser-table td { +.dicom-tag-browser-table tr { padding-left: 10px; padding-right: 10px; color: var(--table-text-primary-color); diff --git a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js index dae111451..19455c984 100644 --- a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js +++ b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { classes } from '@ohif/core'; import dcmjs from 'dcmjs'; import DicomBrowserSelect from './DicomBrowserSelect'; @@ -16,73 +16,96 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { setActiveDisplaySetInstanceUID, ] = useState(displaySetInstanceUID); const [activeInstance, setActiveInstance] = useState(0); + const [tags, setTags] = useState([]); + const [instanceList, setInstanceList] = useState([]); + const [displaySetList, setDisplaySetList] = useState([]); + const [isImageStack, setIsImageStack] = useState(false); - const activeDisplaySet = displaySets.find( - ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID - ); + useEffect(() => { + const activeDisplaySet = displaySets.find( + ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID + ); - const displaySetList = displaySets.map(displaySet => { - const { - displaySetInstanceUID, - SeriesDate, - SeriesTime, - SeriesNumber, - SeriesDescription, - Modality, - } = displaySet; + const newDisplaySetList = displaySets.map(displaySet => { + const { + displaySetInstanceUID, + SeriesDate, + SeriesTime, + SeriesNumber, + SeriesDescription, + Modality, + } = displaySet; - /* Map to display representation */ - const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; - const date = moment(dateStr, 'YYYYMMDD:HHmmss'); - const displayDate = date.format('ddd, MMM Do YYYY'); - - return { - value: displaySetInstanceUID, - title: `${SeriesNumber} (${Modality}): ${SeriesDescription}`, - description: displayDate, - onClick: () => { - setActiveDisplaySetInstanceUID(displaySetInstanceUID); - setActiveInstance(0); - }, - }; - }); - - let metadata; - const isImageStack = activeDisplaySet instanceof ImageSet; - - let selectedInstanceValue; - let instanceList; - - if (isImageStack) { - const { images } = activeDisplaySet; - const image = images[activeInstance]; - - instanceList = images.map((image, index) => { - const metadata = image.getData().metadata; - - const { InstanceNumber } = metadata; + /* Map to display representation */ + const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; + const date = moment(dateStr, 'YYYYMMDD:HHmmss'); + const displayDate = date.format('ddd, MMM Do YYYY'); return { - value: index, - title: `Instance Number: ${InstanceNumber}`, - description: '', + value: displaySetInstanceUID, + title: `${SeriesNumber} (${Modality}): ${SeriesDescription}`, + description: displayDate, onClick: () => { - setActiveInstance(index); + setActiveDisplaySetInstanceUID(displaySetInstanceUID); + setActiveInstance(0); }, }; }); - selectedInstanceValue = instanceList[activeInstance]; + let metadata; + const isImageStack = activeDisplaySet instanceof ImageSet; - metadata = image.getData().metadata; - } else { - metadata = activeDisplaySet.metadata; - } + let selectedInstanceValue; + let instanceList; + + if (isImageStack) { + const { images } = activeDisplaySet; + const image = images[activeInstance]; + + instanceList = images.map((image, index) => { + const metadata = image.getData().metadata; + + const { InstanceNumber } = metadata; + + return { + value: index, + title: `Instance Number: ${InstanceNumber}`, + description: '', + onClick: () => { + setActiveInstance(index); + }, + }; + }); + + metadata = image.getData().metadata; + } else { + metadata = activeDisplaySet.metadata; + } + + setTags(getSortedTags(metadata)); + setInstanceList(instanceList); + setDisplaySetList(newDisplaySetList); + setIsImageStack(isImageStack); + }, [activeDisplaySetInstanceUID, activeInstance]); const selectedDisplaySetValue = displaySetList.find( ds => ds.value === activeDisplaySetInstanceUID ); + let instanceSelectList = null; + + if (isImageStack) { + const selectedInstanceValue = instanceList[activeInstance]; + + instanceSelectList = ( + + ); + } + return (
{ formatOptionLabel={DicomBrowserSelectItem} options={displaySetList} /> - {isImageStack ? ( - - ) : null} - + {instanceSelectList} +
); }; -function DicomTagTable({ instanceMetadata }) { - const rows = getRows(instanceMetadata); +function DicomTagTable({ tags }) { + const rows = getFormattedRowsFromTags(tags); return (
@@ -114,19 +131,71 @@ function DicomTagTable({ instanceMetadata }) { Keyword Value - {rows.map(row => ( - - {row[0]} - {row[1]} - {row[2]} - {row[3]} - - ))} + {rows.map(row => { + const className = row.className ? row.className : null; + + return ( + + {row[0]} + {row[1]} + {row[2]} + {row[3]} + + ); + })}
); } +function getFormattedRowsFromTags(tags) { + const rows = []; + + tags.forEach(tagInfo => { + if (tagInfo.vr === 'SQ') { + rows.push([ + `${tagInfo.tagIndent}${tagInfo.tag}`, + tagInfo.vr, + tagInfo.keyword, + '', + ]); + + const { values } = tagInfo; + + values.forEach((item, index) => { + const formatedRowsFromTags = getFormattedRowsFromTags(item); + + rows.push([ + `${item[0].tagIndent}(FFFE,E000)`, + '', + `Item #${index}`, + '', + ]); + + rows.push(...formatedRowsFromTags); + }); + } else { + rows.push([ + `${tagInfo.tagIndent}${tagInfo.tag}`, + tagInfo.vr, + tagInfo.keyword, + tagInfo.value, + ]); + } + }); + + return rows; +} + +function getSortedTags(metadata) { + const tagList = getRows(metadata); + + // Sort top level tags, sequence groups are sorted when created. + _sortTagList(tagList); + + return tagList; +} + function getRows(metadata, depth = 0) { // Tag, Type, Value, Keyword @@ -138,6 +207,10 @@ function getRows(metadata, depth = 0) { tagIndent += '>'; } + if (depth > 0) { + tagIndent += ' '; // If indented, add a space after the indents. + } + const rows = []; for (let i = 0; i < keywords.length; i++) { @@ -155,7 +228,16 @@ function getRows(metadata, depth = 0) { const sequenceAsArray = toArray(value); // Push line defining the sequence - rows.push([`${tagIndent}${tagInfo.tag}`, tagInfo.vr, keyword, '']); + + const sequence = { + tag: tagInfo.tag, + tagIndent, + vr: tagInfo.vr, + keyword, + values: [], + }; + + rows.push(sequence); if (value === null) { // Type 2 Sequence @@ -165,9 +247,10 @@ function getRows(metadata, depth = 0) { sequenceAsArray.forEach(item => { const sequenceRows = getRows(item, depth + 1); - sequenceRows.forEach(row => { - rows.push(row); - }); + // Sort the sequence group. + _sortTagList(sequenceRows); + + sequence.values.push(sequenceRows); }); continue; @@ -205,16 +288,30 @@ function getRows(metadata, depth = 0) { } } + // tag / vr/ keyword/ value + // Remove retired tags keyword = keyword.replace('RETIRED_', ''); if (tagInfo) { - rows.push([`${tagIndent}${tagInfo.tag}`, tagInfo.vr, keyword, value]); + rows.push({ + tag: tagInfo.tag, + tagIndent, + vr: tagInfo.vr, + keyword, + value, + }); } else { // Private tag const tag = `(${keyword.substring(0, 4)},${keyword.substring(4, 8)})`; - rows.push([`${tagIndent}${tag}`, '', 'Private Tag', value]); + rows.push({ + tag, + tagIndent, + vr: '', + keyword: 'Private Tag', + value, + }); } } @@ -225,4 +322,14 @@ function toArray(objectOrArray) { return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray]; } +function _sortTagList(tagList) { + tagList.sort((a, b) => { + if (a.tag < b.tag) { + return -1; + } + + return 1; + }); +} + export default DicomTagBrowser; diff --git a/platform/viewer/src/index.js b/platform/viewer/src/index.js index bc38c4d07..0bd3441ea 100644 --- a/platform/viewer/src/index.js +++ b/platform/viewer/src/index.js @@ -55,7 +55,7 @@ const appProps = { OHIFDicomSegmentationExtension, OHIFDicomRtExtension, //[OHIFDebuggingExtension, { mailTo: 'support@canceridc.dev' }], - //OHIFDicomTagBrowserExtension, + OHIFDicomTagBrowserExtension, ], };