fix: ui for dicom tag browswer (#3022)

* fix: ui for dicom tag browswer

* fix build
This commit is contained in:
Alireza 2022-11-16 22:35:23 -05:00 committed by GitHub
parent ccf010c0fe
commit 6db4d7a917
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 313 deletions

View File

@ -1,48 +1,37 @@
import React, { useState, useEffect, useRef } from 'react';
import { classes } from '@ohif/core';
import Range from './Range';
import DicomTagTable from './DicomTagTable';
import dcmjs from 'dcmjs';
import moment from 'moment';
import React, { useState, useMemo } from 'react';
import { classes } from '@ohif/core';
import DicomTagTable from './DicomTagTable';
import './DicomTagBrowser.css';
import { Select, Typography } from '@ohif/ui';
import { InputRange, Select, Typography } from '@ohif/ui';
const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary;
let instanceSelectList = null;
let instanceSelectTitle = null;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const [
activeDisplaySetInstanceUID,
setActiveDisplaySetInstanceUID,
selectedDisplaySetInstanceUID,
setSelectedDisplaySetInstanceUID,
] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(1);
const [tags, setTags] = useState([]);
const [meta, setMeta] = useState('');
const [instanceList, setInstanceList] = useState([]);
const [displaySetList, setDisplaySetList] = useState([]);
const [isImageStack, setIsImageStack] = useState(false);
const [selectedDisplaySetValue, setSelectedDisplaySetValue] = useState({}
);
const onSeriesSelect = value => {
console.log(value);
setActiveDisplaySetInstanceUID(value.value);
setActiveInstance(1);
setSelectedDisplaySetValue(value);
const [instanceNumber, setInstanceNumber] = useState(1);
const onSelectChange = value => {
setSelectedDisplaySetInstanceUID(value.value);
setInstanceNumber(1);
};
useEffect(() => {
var activeDisplaySet = displaySets.find(
ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID
);
const activeDisplaySet = displaySets.find(
ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID
);
const isImageStack = _isImageStack(activeDisplaySet);
const showInstanceList = isImageStack && activeDisplaySet.images.length > 1;
const displaySetList = useMemo(() => {
displaySets.sort((a, b) => a.SeriesNumber - b.SeriesNumber);
const newDisplaySetList = displaySets.map(displaySet => {
return displaySets.map(displaySet => {
const {
displaySetInstanceUID,
SeriesDate,
@ -56,123 +45,72 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const displayDate = date.format('ddd, MMM Do YYYY');
return {
value: displaySetInstanceUID,
label: `${SeriesNumber} (${Modality}): ${SeriesDescription}`,
description: displayDate,
onClick: () => {
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
setActiveInstance(1);
//instanceSelectList.props.children.props.value = 1;
},
};
});
let metadata;
if (!activeDisplaySet) {
if (!displaySets || displaySets.length == 0) {
return;
} else {
activeDisplaySet = displaySets[0];
setActiveDisplaySetInstanceUID(displaySets[0].displaySetInstanceUID);
setSelectedDisplaySetValue(newDisplaySetList[0]);
}
} else {
setSelectedDisplaySetValue(newDisplaySetList.find(
ds => ds.value === activeDisplaySetInstanceUID
));
}
const isImageStack =
activeDisplaySet instanceof ImageSet; /*&&
activeDisplaySet.isSOPClassUIDSupported === true*/;
let instanceList;
if (isImageStack) {
const { images } = activeDisplaySet;
const image = images[activeInstance - 1];
instanceList = images.map((image, index) => {
// const metadata = image.getData().metadata;
const { InstanceNumber } = image;
return {
value: index,
title: `Instance Number: ${InstanceNumber}`,
description: '',
onClick: () => {
setActiveInstance(index);
},
};
});
metadata = image;
} else {
metadata = activeDisplaySet;
}
if (isImageStack) {
instanceSelectTitle = (
<Typography variant="subtitle" className="mr-5 text-right h-full">
Instance Number
</Typography>
)
instanceSelectList = (
<div className="dicom-tag-browser-instance-range ml-auto">
<Range
showValue
step={1}
min={1}
max={instanceList.length}
value={activeInstance}
valueRenderer={value => <p>{value}</p>}
onChange={({ target }) => {
const instanceIndex = parseInt(target.value);
setActiveInstance(instanceIndex);
}}
/>
</div>
);
} else {
instanceSelectList = null;
}
setTags(getSortedTags(metadata));
setMeta(metadata);
setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack);
}, [activeDisplaySetInstanceUID, activeInstance, displaySets]);
}, [displaySets]);
return (
<div className="dicom-tag-browser-content">
<div className="flex flex-row items-center justify-between">
<Typography variant="subtitle" className="mr-5 text-right h-full">
<div className="flex flex-row items-center mb-2">
<Typography variant="subtitle" className="w-1/2 mr-8">
Series
</Typography>
{instanceSelectTitle}</div>
<div className="flex flex-row items-center">
<div className="w-72">
{showInstanceList && (
<Typography variant="subtitle" className="w-1/2">
Instance Number
</Typography>
)}
</div>
<div className="flex flex-row items-center mb-6">
<div className="w-1/2 mr-8">
<Select
isClearable={false}
onChange={onSeriesSelect}
onChange={onSelectChange}
options={displaySetList}
value={selectedDisplaySetValue}
/></div>
{instanceSelectList}
</div>
<div className="dicom-tag-browser-table-wrapper">
<DicomTagTable rows={getFormattedRowsFromTags(tags, meta)}></DicomTagTable>
value={displaySetList.find(
ds => ds.value === selectedDisplaySetInstanceUID
)}
className="text-white"
/>
</div>
{showInstanceList ? (
<div className="w-1/2">
<InputRange
value={instanceNumber}
onChange={value => {
setInstanceNumber(parseInt(value));
}}
minValue={1}
maxValue={activeDisplaySet.images.length}
step={1}
/>
</div>
) : null}
</div>
<DicomTagTable
rows={getFormattedRowsFromTags(activeDisplaySet, instanceNumber)}
></DicomTagTable>
</div>
);
};
function getFormattedRowsFromTags(displaySet, instanceNumber) {
const isImageStack = _isImageStack(displaySet);
function getFormattedRowsFromTags(tags, meta) {
let metadata;
if (isImageStack) {
metadata = displaySet.images[instanceNumber - 1];
} else {
metadata = displaySet;
}
const tags = getSortedTags(metadata);
const rows = [];
tags.forEach(tagInfo => {
@ -205,7 +143,6 @@ function getFormattedRowsFromTags(tags, meta) {
meta.StudyInstanceUID
);*/
// console.log(dataset);
// const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
// const originalTagInfo = dataset[tag];
// tagInfo.vr = originalTagInfo.vr;
@ -220,10 +157,9 @@ function getFormattedRowsFromTags(tags, meta) {
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
tagInfo.keyword,
tagInfo.value
tagInfo.value,
]);
} else {
rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
@ -353,7 +289,7 @@ function getRows(metadata, depth = 0) {
});
} else {
// skip properties without hex tag numbers
var regex = /[0-9A-Fa-f]{6}/g;
const regex = /[0-9A-Fa-f]{6}/g;
if (keyword.match(regex)) {
const tag = `(${keyword.substring(0, 4)},${keyword.substring(4, 8)})`;
rows.push({
@ -370,6 +306,10 @@ function getRows(metadata, depth = 0) {
return rows;
}
function _isImageStack(displaySet) {
return displaySet instanceof ImageSet;
}
function toArray(objectOrArray) {
return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
}

View File

@ -1,55 +1,99 @@
import React from 'react';
function ColumnHeaders() {
return (<div className="border-b-4 border-black"> <div className="pt-3 pb-3 bg-primary-dark ">
<div className="container m-auto relative flex flex-col"><div className="flex flex-row w-full">
<div className="px-3 w-5/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Tag</span></label></div> <div className="px-3 w-4/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Value Representation</span></label></div> <div className="px-3 w-6/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Keyword</span></label></div> <div className="px-3 w-5/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Value</span></label></div></div></div>
</div></div>)
return (
<div className="m-auto flex flex-col py-2 bg-secondary-light">
<div className="flex flex-row w-full">
<div className="px-3 w-5/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Tag
</span>
</label>
</div>
<div className="px-3 w-4/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
VR
</span>
</label>
</div>
<div className="px-3 w-6/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Keyword
</span>
</label>
</div>
<div className="px-3 w-5/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none">
<span className="flex flex-row items-center focus:outline-none">
Value
</span>
</label>
</div>
</div>
</div>
);
}
function DicomTagTable({ rows }) {
return (
<div>
{ColumnHeaders()}
<div
className="m-auto relative border-2 border-secondary-light overflow-hidden ohif-scrollbar"
style={{ height: '32rem' }}
>
<table className="w-full text-white">
<tbody>
{rows.map((row, index) => {
const className = row.className ? row.className : null;
return (<div>{ColumnHeaders()}
<div className="container m-auto relative">
<table className="w-full text-white">
<tbody>
{rows.map((row, index) => {
const className = row.className ? row.className : null;
return (
<tr className="hover:bg-secondary-main transition duration-300 bg-primary-dark" key={`DICOMTagRow-${index}`}>
<td style={{ maxWidth: "0px" }} className="px-4 py-2 text-base break-all border-b border-secondary-light w-5/24">
<div className="flex"><div className="inline-flex max-w-full">{row[0]}</div></div>
</td>
<td style={{ maxWidth: "0px" }} className="px-4 py-2 text-base break-all border-b border-secondary-light w-4/24">
<div className="flex"><div className="inline-flex max-w-full">{row[1]}</div></div>
</td>
<td style={{ maxWidth: "0px" }} className="px-4 py-2 text-base break-all border-b border-secondary-light w-6/24">
<div className="flex"><div className="inline-flex max-w-full">{row[2]}</div></div>
</td>
<td style={{ maxWidth: "0px" }} className="px-4 py-2 text-base break-all border-b border-secondary-light w-14/24">
<div className="flex"><div className="inline-flex max-w-full">{row[3]}</div></div>
</td>
</tr>
);
})}
</tbody>
</table></div></div >
return (
<tr
className="hover:bg-secondary-main transition duration-300 bg-primary-dark"
key={`DICOMTagRow-${index}`}
>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-5/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[0]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-4/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[1]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-6/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[2]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-14/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[3]}</div>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}

View File

@ -1,65 +0,0 @@
.range {
margin: 0;
width: 100%;
-webkit-appearance: none;
background-color: transparent;
}
.range:focus {
outline: none;
}
.range::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
box-shadow: none;
background-color: #3C5D80;
border-radius: 0px;
border: 0px solid #3C5D80;
}
.range::-moz-range-track {
width: 100%;
height: 2px;
cursor: pointer;
box-shadow: none;
background-color: #3C5D80;
border-radius: 0px;
border: 0px solid #3C5D80;
}
.range::-webkit-slider-thumb {
box-shadow: none;
border: 0px solid #20A5D6;
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.25);
height: 21px;
width: 11px;
border-radius: 11px;
background: #20A5D6;
cursor: pointer;
-webkit-appearance: none;
margin-top: -10px;
}
.range::-moz-range-thumb {
box-shadow: none;
border: 0px solid #20A5D6;
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.25);
height: 21px;
width: 11px;
border-radius: 11px;
background: #20A5D6;
cursor: pointer;
-webkit-appearance: none;
margin-top: -10px;
}
.range::-moz-focus-outer {
border: 0;
}
.range-example {
background: black;
height: 2em;
}

View File

@ -1,57 +0,0 @@
import './Range.css';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
function Range(props) {
const [value, setValue] = useState(props.value);
const handleChange = event => {
setValue(event.target.value);
if (props.onChange) props.onChange(event);
};
return (
<>
<input
ref={props.ref}
type="range"
value={props.value}
min={props.min}
max={props.max}
step={props.step || 1}
onChange={handleChange}
id={props.id}
className="range"
/>
{props.showPercentage && <span>{`${value}%`}</span>}
{props.showValue && (
<span>
{props.valueRenderer
? props.valueRenderer(props.value)
: props.value}
</span>
)}
</>
);
}
Range.propTypes = {
value: PropTypes.number,
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
step: PropTypes.number,
id: PropTypes.string,
valueRenderer: PropTypes.func,
onChange: PropTypes.func,
showPercentage: PropTypes.bool,
showValue: PropTypes.bool,
ref: PropTypes.any,
};
Range.defaultProps = {
showPercentage: false,
showValue: false,
};
export default Range;

View File

@ -377,19 +377,6 @@ const toolbarButtons = [
],
'Reset'
),
_createActionButton(
'TagBrowser',
'list-bullets',
'Dicom Tag Browser',
[
{
commandName: 'openDICOMTagViewer',
commandOptions: {},
context: 'DEFAULT',
},
],
'Dicom Tag Browser'
),
_createActionButton(
'rotate-right',
'tool-rotate-right',
@ -535,6 +522,19 @@ const toolbarButtons = [
],
'Rectangle'
),
_createActionButton(
'TagBrowser',
'list-bullets',
'Dicom Tag Browser',
[
{
commandName: 'openDICOMTagViewer',
commandOptions: {},
context: 'DEFAULT',
},
],
'Dicom Tag Browser'
),
],
},
},

View File

@ -8,7 +8,7 @@ import Dialog from './Dialog';
import Dropdown from './Dropdown';
import EmptyStudies from './EmptyStudies';
import ErrorBoundary from './ErrorBoundary';
import Icon, { addIcon } from './Icon';
import Icon from './Icon';
import IconButton from './IconButton';
import Input from './Input';
import InputDateRange from './InputDateRange';
@ -89,7 +89,6 @@ export {
ExpandableToolbarButton,
ListMenu,
Icon,
addIcon,
IconButton,
Input,
InputDateRange,

View File

@ -54,6 +54,7 @@ export {
Icon,
IconButton,
Input,
InputRange,
InputDateRange,
InputGroup,
InputLabelWrapper,