Skip to content

Commit b6da3ed

Browse files
committed
Add species and admitting parsing
1 parent c537806 commit b6da3ed

3 files changed

Lines changed: 105 additions & 8 deletions

File tree

src/components/Patient.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// skipcq: JS-C1003
22
import type * as dmv from 'dicom-microscopy-viewer'
33
import React from 'react'
4-
import { parseDate, parseName, parseSex } from '../utils/values'
4+
import {
5+
formatPatientSpeciesCodeSequence,
6+
parseDate,
7+
parseName,
8+
parseSex,
9+
} from '../utils/values'
510
import Description from './Description'
611

712
interface PatientProps {
@@ -15,6 +20,10 @@ interface PatientProps {
1520
*/
1621
class Patient extends React.Component<PatientProps, Record<string, never>> {
1722
render(): React.ReactNode {
23+
const species = formatPatientSpeciesCodeSequence(
24+
(this.props.metadata as unknown as Record<string, unknown>)
25+
.PatientSpeciesCodeSequence,
26+
)
1827
const attributes = [
1928
{
2029
name: 'ID',
@@ -24,6 +33,7 @@ class Patient extends React.Component<PatientProps, Record<string, never>> {
2433
name: 'Name',
2534
value: parseName(this.props.metadata.PatientName),
2635
},
36+
...(species !== undefined ? [{ name: 'Species', value: species }] : []),
2737
{
2838
name: 'Sex',
2939
value: parseSex(this.props.metadata.PatientSex),

src/components/SpecimenItem.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import * as dcmjs from 'dcmjs'
44
import type * as dmv from 'dicom-microscopy-viewer'
55
import React from 'react'
66
import { SpecimenPreparationStepItems } from '../data/specimens'
7+
import {
8+
codedConceptDisplayText,
9+
dedupeStringsPreserveOrder,
10+
} from '../utils/values'
711
import type { Attribute } from './Description'
812
import Item from './Item'
913

@@ -13,6 +17,35 @@ interface SpecimenItemProps {
1317
showstain: boolean
1418
}
1519

20+
/** (0008,1080) LO and (0008,1084) SQ — separate DICOM attributes; combine for display. */
21+
function formatAdmittingDiagnoses(
22+
metadata: Record<string, unknown>,
23+
): string | undefined {
24+
const descRaw = metadata.AdmittingDiagnosesDescription
25+
const desc = typeof descRaw === 'string' ? descRaw.trim() : ''
26+
27+
const seq = metadata.AdmittingDiagnosesCodeSequence
28+
const codeParts: string[] = []
29+
if (Array.isArray(seq)) {
30+
for (const item of seq) {
31+
const part = codedConceptDisplayText(item)
32+
if (part !== '') codeParts.push(part)
33+
}
34+
}
35+
const uniqueCodes = dedupeStringsPreserveOrder(codeParts)
36+
const codesJoined = uniqueCodes.join(', ')
37+
38+
if (desc !== '' && codesJoined !== '') {
39+
if (desc.toLowerCase() === codesJoined.toLowerCase()) {
40+
return desc
41+
}
42+
return `${desc}; ${codesJoined}`
43+
}
44+
if (desc !== '') return desc
45+
if (codesJoined !== '') return codesJoined
46+
return undefined
47+
}
48+
1649
/**
1750
* React component representing a DICOM Specimen Information Entity and
1851
* displays specimen-related attributes of a DICOM Slide Microscopy image.
@@ -160,14 +193,13 @@ class SpecimenItem extends React.Component<
160193
},
161194
)
162195

163-
if (
164-
(this.props.metadata as unknown as Record<string, unknown>)
165-
.AdmittingDiagnosesDescription !== undefined
166-
) {
196+
const admittingDiagnoses = formatAdmittingDiagnoses(
197+
this.props.metadata as unknown as Record<string, unknown>,
198+
)
199+
if (admittingDiagnoses !== undefined) {
167200
attributes.push({
168201
name: 'Admitting Diagnoses',
169-
value: (this.props.metadata as unknown as Record<string, unknown>)
170-
.AdmittingDiagnosesDescription as string,
202+
value: admittingDiagnoses,
171203
})
172204
}
173205

src/utils/values.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,59 @@ function parseSex(value: string | null | undefined): string {
5656
return ''
5757
}
5858

59-
export { parseDate, parseDateTime, parseName, parseSex, parseTime }
59+
/**
60+
* Human-readable text for a DICOM coded concept: prefer CodeMeaning.
61+
* Does not show SNOMED CT numeric codes (SCT) when meaning is absent.
62+
*/
63+
function codedConceptDisplayText(item: unknown): string {
64+
if (item == null || typeof item !== 'object') return ''
65+
const o = item as {
66+
CodeValue?: string
67+
CodeMeaning?: string
68+
CodingSchemeDesignator?: string
69+
}
70+
const cm = (o.CodeMeaning ?? '').trim()
71+
if (cm !== '') return cm
72+
const scheme = (o.CodingSchemeDesignator ?? '').toUpperCase()
73+
if (scheme === 'SCT') return ''
74+
return (o.CodeValue ?? '').trim()
75+
}
76+
77+
function dedupeStringsPreserveOrder(strings: string[]): string[] {
78+
const seen = new Set<string>()
79+
const out: string[] = []
80+
for (const s of strings) {
81+
const key = s.toLowerCase()
82+
if (seen.has(key)) continue
83+
seen.add(key)
84+
out.push(s)
85+
}
86+
return out
87+
}
88+
89+
/** (00102202) PatientSpeciesCodeSequence — meanings only; undefined if absent or empty. */
90+
function formatPatientSpeciesCodeSequence(
91+
sequence: unknown,
92+
): string | undefined {
93+
if (!Array.isArray(sequence) || sequence.length === 0) {
94+
return undefined
95+
}
96+
const parts: string[] = []
97+
for (const item of sequence) {
98+
const part = codedConceptDisplayText(item)
99+
if (part !== '') parts.push(part)
100+
}
101+
const unique = dedupeStringsPreserveOrder(parts)
102+
return unique.length > 0 ? unique.join(', ') : undefined
103+
}
104+
105+
export {
106+
codedConceptDisplayText,
107+
dedupeStringsPreserveOrder,
108+
formatPatientSpeciesCodeSequence,
109+
parseDate,
110+
parseDateTime,
111+
parseName,
112+
parseSex,
113+
parseTime,
114+
}

0 commit comments

Comments
 (0)