From 64cf3b324da2383a927af1df2d46db2fca5318aa Mon Sep 17 00:00:00 2001 From: Danny Brown Date: Thu, 31 Oct 2019 14:41:08 -0400 Subject: [PATCH] fix: application crash if patientName is an object (#1138) * fix: get adapter store picker to show * fix: error caused by DICOMWeb "empty object" for patientName * formatting * refactor: kill the pills * Indicate modalities is missing if not provided * Consistent modality display * Make sure modalities styles are applied * revert config --- platform/core/src/DICOMWeb/getModalities.js | 14 +- .../core/src/studies/services/qido/studies.js | 2 +- .../ui/src/components/studyList/StudyList.js | 53 +++---- .../src/components/studyList/StudyList.styl | 26 ++-- .../studyList/internal/bkdr-hash.js | 24 --- .../studyList/internal/color-hash.js | 145 ------------------ .../viewer/src/studylist/StudyListRoute.js | 69 +++++---- 7 files changed, 87 insertions(+), 246 deletions(-) delete mode 100644 platform/ui/src/components/studyList/internal/bkdr-hash.js delete mode 100644 platform/ui/src/components/studyList/internal/color-hash.js diff --git a/platform/core/src/DICOMWeb/getModalities.js b/platform/core/src/DICOMWeb/getModalities.js index 0b52bb578..642c7316f 100644 --- a/platform/core/src/DICOMWeb/getModalities.js +++ b/platform/core/src/DICOMWeb/getModalities.js @@ -1,11 +1,14 @@ export default function getModalities(modality, modalitiesInStudy) { - let modalities = {}; - if (modality) { - modalities = modality; + if (!modality && !modalitiesInStudy) { + return {}; } + const modalities = modality || { + vr: 'CS', + Value: [], + }; + if (modalitiesInStudy) { - // Find vr in modalities if (modalities.vr && modalities.vr === modalitiesInStudy.vr) { for (let i = 0; i < modalitiesInStudy.Value.length; i++) { const value = modalitiesInStudy.Value[i]; @@ -14,8 +17,9 @@ export default function getModalities(modality, modalitiesInStudy) { } } } else { - modalities = modalitiesInStudy; + return modalitiesInStudy; } } + return modalities; } diff --git a/platform/core/src/studies/services/qido/studies.js b/platform/core/src/studies/services/qido/studies.js index aab412f63..01d325390 100644 --- a/platform/core/src/studies/services/qido/studies.js +++ b/platform/core/src/studies/services/qido/studies.js @@ -30,7 +30,7 @@ function dateToString(date) { function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) { const commaSeparatedFields = [ '00081030', // Study Description - '00080060', //Modality + '00080060', // Modality // Add more fields here if you want them in the result ].join(','); diff --git a/platform/ui/src/components/studyList/StudyList.js b/platform/ui/src/components/studyList/StudyList.js index 3ce1bbd12..84e0d31f5 100644 --- a/platform/ui/src/components/studyList/StudyList.js +++ b/platform/ui/src/components/studyList/StudyList.js @@ -5,12 +5,9 @@ import classNames from 'classnames'; import TableSearchFilter from './TableSearchFilter.js'; import useMedia from '../../hooks/useMedia.js'; import PropTypes from 'prop-types'; -import ColorHash from './internal/color-hash.js'; import { StudyListLoadingText } from './StudyListLoadingText.js'; import { withTranslation } from '../../utils/LanguageProvider'; -const colorHash = new ColorHash(); - /** * * @@ -236,13 +233,15 @@ function TableRow(props) { onClick={() => handleClick(studyInstanceUid)} className={classNames({ active: isHighlighted })} > - + {patientName || `(${t('Empty')})`} {patientId} {accessionNumber} {studyDate} - {modalities} + + {modalities || `(${t('Empty')})`} + {studyDescription} ); @@ -252,7 +251,7 @@ function TableRow(props) { onClick={() => handleClick(studyInstanceUid)} className={classNames({ active: isHighlighted })} > - + {patientName || `(${t('Empty')})`}
{patientId}
@@ -274,25 +273,19 @@ function TableRow(props) { style={{ display: 'flex', flexDirection: 'column', - minWidth: '80px', - maxWidth: '100px', + maxWidth: '80px', + width: '80px', }} >
- {modalities} + {modalities || `(${t('Empty')})`}
{/* NAME AND ID */}
@@ -348,19 +341,19 @@ function TableRow(props) { style={{ display: 'flex', flexDirection: 'column', - minWidth: '80px', + maxWidth: '80px', + width: '80px', }} >
- {modalities} + {modalities || `(${t('Empty')})`}
{studyDate}
@@ -381,7 +374,7 @@ function TableRow(props) { TableRow.propTypes = { accessionNumber: PropTypes.string.isRequired, isHighlighted: PropTypes.bool, - modalities: PropTypes.string.isRequired, + modalities: PropTypes.string, patientId: PropTypes.string.isRequired, patientName: PropTypes.string.isRequired, studyDate: PropTypes.string.isRequired, diff --git a/platform/ui/src/components/studyList/StudyList.styl b/platform/ui/src/components/studyList/StudyList.styl index 0f72e91a0..807304bdf 100644 --- a/platform/ui/src/components/studyList/StudyList.styl +++ b/platform/ui/src/components/studyList/StudyList.styl @@ -113,11 +113,18 @@ table.table border-spacing: 0; border-collapse: collapse; table-layout: fixed; + color: var(--table-text-primary-color) + font-weight: 300 > tbody tr padding: 5px background-color: black + > tbody td + padding: $body-cell-top-bottom-padding 8px; + height: $body-cell-height + word-wrap: break-word; + /* Striped Variant */ &.table--striped > tbody tr:nth-child(even) background-color: var(--ui-gray-darker) @@ -130,6 +137,9 @@ table.table &:hover, &:active, &.active background-color: var(--ui-gray-darker) + .empty-value + color: var(--ui-gray-light) + .study-list-container > table.table > tr height: 20px @@ -146,18 +156,14 @@ table.table text-align: left; border-top: 0; -.study-list-container > table.table > tbody > tr > td - padding: $body-cell-top-bottom-padding 8px; - height: $body-cell-height - color: var(--table-text-primary-color) - font-weight: 300 - word-wrap: break-word; - - &.emptyCell - color: var(--ui-gray-light) - .study-list-container > table.table > thead > tr > th.studyDate min-width: 230px +.study-list-container > table.table .modalities + font-weight: 500; + min-height: 20px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; .study-list-container .filters diff --git a/platform/ui/src/components/studyList/internal/bkdr-hash.js b/platform/ui/src/components/studyList/internal/bkdr-hash.js deleted file mode 100644 index 888f7aeab..000000000 --- a/platform/ui/src/components/studyList/internal/bkdr-hash.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * BKDR Hash (modified version) - * - * @param {String} str string to hash - * @returns {Number} - */ -function BKDRHash(str) { - const seed = 131; - const seed2 = 137; - let hash = 0; - // make hash more sensitive for short string like 'a', 'b', 'c' - str += 'x'; - // Note: Number.MAX_SAFE_INTEGER equals 9007199254740991 - var MAX_SAFE_INTEGER = parseInt(9007199254740991 / seed2); - for (var i = 0; i < str.length; i++) { - if (hash > MAX_SAFE_INTEGER) { - hash = parseInt(hash / seed2); - } - hash = hash * seed + str.charCodeAt(i); - } - return hash; -} - -export default BKDRHash; diff --git a/platform/ui/src/components/studyList/internal/color-hash.js b/platform/ui/src/components/studyList/internal/color-hash.js deleted file mode 100644 index fc2eea7dd..000000000 --- a/platform/ui/src/components/studyList/internal/color-hash.js +++ /dev/null @@ -1,145 +0,0 @@ -import BKDRHash from './bkdr-hash'; - -/** - * Convert RGB Array to HEX - * - * @param {Array} RGBArray - [R, G, B] - * @returns {String} 6 digits hex starting with # - */ -function RGB2HEX(RGBArray) { - let hex = '#'; - RGBArray.forEach(function(value) { - if (value < 16) { - hex += 0; - } - hex += value.toString(16); - }); - return hex; -} - -/** - * Convert HSL to RGB - * - * @see {@link http://zh.wikipedia.org/wiki/HSL和HSV色彩空间} for further information. - * @param {Number} H Hue ∈ [0, 360) - * @param {Number} S Saturation ∈ [0, 1] - * @param {Number} L Lightness ∈ [0, 1] - * @returns {Array} R, G, B ∈ [0, 255] - */ -function HSL2RGB(H, S, L) { - H /= 360; - - const q = L < 0.5 ? L * (1 + S) : L + S - L * S; - const p = 2 * L - q; - - return [H + 1 / 3, H, H - 1 / 3].map(function(color) { - if (color < 0) { - color++; - } - if (color > 1) { - color--; - } - if (color < 1 / 6) { - color = p + (q - p) * 6 * color; - } else if (color < 0.5) { - color = q; - } else if (color < 2 / 3) { - color = p + (q - p) * 6 * (2 / 3 - color); - } else { - color = p; - } - return Math.round(color * 255); - }); -} - -function isArray(o) { - return Object.prototype.toString.call(o) === '[object Array]'; -} - -/** - * Color Hash Class - * - * @class - */ -const ColorHash = function(options = {}) { - const LS = [options.lightness, options.saturation].map(function(param) { - param = param || [0.35, 0.5, 0.65]; // note that 3 is a prime - return isArray(param) ? param.concat() : [param]; - }); - - this.L = LS[0]; - this.S = LS[1]; - - if (typeof options.hue === 'number') { - options.hue = { min: options.hue, max: options.hue }; - } - if (typeof options.hue === 'object' && !isArray(options.hue)) { - options.hue = [options.hue]; - } - if (typeof options.hue === 'undefined') { - options.hue = []; - } - this.hueRanges = options.hue.map(function(range) { - return { - min: typeof range.min === 'undefined' ? 0 : range.min, - max: typeof range.max === 'undefined' ? 360 : range.max, - }; - }); - - this.hash = options.hash || BKDRHash; -}; - -/** - * Returns the hash in [h, s, l]. - * Note that H ∈ [0, 360); S ∈ [0, 1]; L ∈ [0, 1]; - * - * @param {String} str string to hash - * @returns {Array} [h, s, l] - */ -ColorHash.prototype.hsl = function(str) { - var H, S, L; - var hash = this.hash(str); - - if (this.hueRanges.length) { - var range = this.hueRanges[hash % this.hueRanges.length]; - var hueResolution = 727; // note that 727 is a prime - H = - (((hash / this.hueRanges.length) % hueResolution) * - (range.max - range.min)) / - hueResolution + - range.min; - } else { - H = hash % 359; // note that 359 is a prime - } - hash = parseInt(hash / 360); - S = this.S[hash % this.S.length]; - hash = parseInt(hash / this.S.length); - L = this.L[hash % this.L.length]; - - return [H, S, L]; -}; - -/** - * Returns the hash in [r, g, b]. - * Note that R, G, B ∈ [0, 255] - * - * @param {String} str string to hash - * @returns {Array} [r, g, b] - */ -ColorHash.prototype.rgb = function(str) { - var hsl = this.hsl(str); - return HSL2RGB.apply(this, hsl); -}; - -/** - * Returns the hash in hex - * - * @param {String} str string to hash - * @returns {String} hex with # - */ -ColorHash.prototype.hex = function(str) { - var rgb = this.rgb(str); - return RGB2HEX(rgb); -}; - -export default ColorHash; diff --git a/platform/viewer/src/studylist/StudyListRoute.js b/platform/viewer/src/studylist/StudyListRoute.js index 8e2c4eb2d..3218e7a26 100644 --- a/platform/viewer/src/studylist/StudyListRoute.js +++ b/platform/viewer/src/studylist/StudyListRoute.js @@ -77,39 +77,43 @@ function StudyListRoute(props) { // Called when relevant state/props are updated // Watches filters and sort, debounced - useEffect(() => { - const fetchStudies = async () => { - try { - setSearchStatus({ error: null, isSearchingForStudies: true }); + useEffect( + () => { + const fetchStudies = async () => { + try { + setSearchStatus({ error: null, isSearchingForStudies: true }); - const response = await getStudyList( - server, - debouncedFilters, - debouncedSort, - rowsPerPage, - pageNumber, - displaySize - ); + const response = await getStudyList( + server, + debouncedFilters, + debouncedSort, + rowsPerPage, + pageNumber, + displaySize + ); - setStudies(response); - setSearchStatus({ error: null, isSearchingForStudies: false }); - } catch (error) { - console.warn(error); - setSearchStatus({ error: true, isFetching: false }); + setStudies(response); + setSearchStatus({ error: null, isSearchingForStudies: false }); + } catch (error) { + console.warn(error); + setSearchStatus({ error: true, isFetching: false }); + } + }; + + if (server) { + fetchStudies(); } - }; - - if (server) { - fetchStudies(); - } - }, [ - debouncedFilters, - debouncedSort, - rowsPerPage, - pageNumber, - displaySize, - server, - ]); + }, + // TODO: Can we update studies directly? + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + debouncedFilters, + debouncedSort, + rowsPerPage, + pageNumber, + displaySize, + server, + ]); // TODO: Update Server // if (this.props.server !== prevProps.server) { @@ -352,6 +356,9 @@ async function getStudyList( // Only the fields we use const mappedStudies = studies.map(study => { + const patientName = + typeof study.patientName === 'string' ? study.patientName : undefined; + return { accessionNumber: study.accessionNumber, // "1" modalities: study.modalities, // "SEG\\MR" ​​ @@ -359,7 +366,7 @@ async function getStudyList( // numberOfStudyRelatedSeries: "3" // patientBirthdate: undefined patientId: study.patientId, // "NOID" - patientName: study.patientName, // "NAME^NONE" + patientName, // "NAME^NONE" // patientSex: "M" // referringPhysicianName: undefined studyDate: study.studyDate, // "Jun 28, 2002"