Skip to content

Commit 2336601

Browse files
fix(about modal): Address hash + disable select dialog for bulk ann + auto load (#360)
* Address hash (about modal) * Improve styls * Update dockerfile * Update selected roi * Update selected roi * Update selected roi * Update selected roi * Address auto load * Address auto load * Address runtime error * Fix wrong slice * Fix wrong slice * Fix wrong slice * Fix wrong slice * Fix wrong slide * Fix wrong slide * Fix selection
1 parent 3fc4517 commit 2336601

7 files changed

Lines changed: 273 additions & 145 deletions

File tree

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ COPY craco.config.js .
3737
COPY tsconfig.json .
3838
COPY types ./types
3939
COPY public ./public
40+
COPY scripts ./scripts
4041
COPY src ./src
4142

43+
RUN chmod +x scripts/*.sh
44+
4245

4346
FROM lib AS app
4447

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
"private": true,
55
"author": "ImagingDataCommons",
66
"scripts": {
7-
"start": "rm -rf ./node_modules/.cache/default-development && craco start",
8-
"build": "craco build",
9-
"build:firebase": "REACT_APP_CONFIG=gcp PUBLIC_URL=/ craco build",
7+
"start": "rm -rf ./node_modules/.cache/default-development && ./scripts/set-git-env.sh craco start",
8+
"dev": "npm run start",
9+
"build": "./scripts/set-git-env.sh craco build",
10+
"build:firebase": "REACT_APP_CONFIG=gcp PUBLIC_URL=/ ./scripts/set-git-env.sh craco build",
1011
"lint": "ts-standard --env jest 'src/**/*.{tsx,ts}'",
1112
"fmt": "ts-standard --env jest 'src/**/*.{tsx,ts}' --fix",
1213
"test": "ts-standard --env jest 'src/**/*.{tsx,ts}' && craco test --watchAll=false",
13-
"predeploy": "REACT_APP_CONFIG=demo PUBLIC_URL='https://imagingdatacommons.github.io/slim/' craco build",
14+
"predeploy": "REACT_APP_CONFIG=demo PUBLIC_URL='https://imagingdatacommons.github.io/slim/' ./scripts/set-git-env.sh craco build",
1415
"deploy": "gh-pages -d build",
1516
"clean": "rm -rf ./build ./node_modules"
1617
},

scripts/set-git-env.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# Script to set git SHA environment variables for build
3+
4+
# Get current repository git SHA
5+
if [ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1; then
6+
export REACT_APP_GIT_SHA=$(git rev-parse HEAD 2>/dev/null || echo '')
7+
else
8+
export REACT_APP_GIT_SHA=''
9+
fi
10+
11+
# Get dicom-microscopy-viewer git SHA
12+
if [ -d ../dicom-microscopy-viewer/.git ] || (cd ../dicom-microscopy-viewer && git rev-parse --git-dir > /dev/null 2>&1); then
13+
export REACT_APP_DMV_GIT_SHA=$(cd ../dicom-microscopy-viewer && git rev-parse HEAD 2>/dev/null || echo '')
14+
else
15+
export REACT_APP_DMV_GIT_SHA=''
16+
fi
17+
18+
# Execute the command passed as arguments
19+
exec "$@"
20+

src/components/CaseViewer.tsx

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary
2323
interface NaturalizedInstance {
2424
SeriesInstanceUID: string
2525
SOPInstanceUID: string
26+
FrameOfReferenceUID?: string
27+
ContainerIdentifier?: string
2628
ReferencedSeriesSequence?: Array<{
2729
SeriesInstanceUID: string
2830
}>
@@ -40,11 +42,6 @@ interface NaturalizedInstance {
4042
}>
4143
}
4244

43-
interface ReferencedSlideResult {
44-
slide: Slide | undefined
45-
metadata: NaturalizedInstance
46-
}
47-
4845
const findSeriesSlide = (slides: Slide[], seriesInstanceUID: string): Slide | undefined => {
4946
return slides.find((slide: Slide) => {
5047
return slide.seriesInstanceUIDs.find((uid: string) => {
@@ -82,65 +79,57 @@ function ParametrizedSlideViewer ({
8279
const [derivedDataset, setDerivedDataset] = useState<NaturalizedInstance | null>(null)
8380

8481
useEffect(() => {
85-
const seriesSlide = findSeriesSlide(slides, seriesInstanceUID)
86-
if (seriesSlide !== null) {
87-
setSelectedSlide(seriesSlide)
88-
}
89-
}, [seriesInstanceUID, slides])
82+
const currentSlideMatchesSeries = selectedSlide?.seriesInstanceUIDs.some((uid: string) => uid === seriesInstanceUID) ?? false
9083

91-
useEffect(() => {
92-
const findReferencedSlide = async ({ clients, studyInstanceUID, seriesInstanceUID }: {
93-
clients: { [key: string]: DicomWebManager }
94-
studyInstanceUID: string
95-
seriesInstanceUID: string
96-
}): Promise<ReferencedSlideResult | null> => await new Promise<ReferencedSlideResult | null>((resolve, reject) => {
97-
try {
98-
const allClients = Object.values(StorageClasses).map((storageClass) => clients[storageClass])
99-
Promise.all(allClients.map(async (client) => {
100-
const seriesMetadata = await client.retrieveSeriesMetadata({
101-
studyInstanceUID: studyInstanceUID,
102-
seriesInstanceUID: seriesInstanceUID
103-
})
104-
const [naturalizedSeriesMetadata] = seriesMetadata.map((metadata) => naturalizeDataset(metadata)) as NaturalizedInstance[]
84+
if (selectedSlide === null || selectedSlide === undefined || !currentSlideMatchesSeries) {
85+
const imageSlide = findSeriesSlide(slides, seriesInstanceUID)
86+
if (imageSlide !== null && imageSlide !== undefined) {
87+
setSelectedSlide(imageSlide)
88+
setDerivedDataset(null)
89+
return
90+
}
10591

106-
if (naturalizedSeriesMetadata.ReferencedSeriesSequence != null) {
107-
const referencedSeriesInstanceUID = naturalizedSeriesMetadata.ReferencedSeriesSequence[0].SeriesInstanceUID
92+
const findReferencedSlide = async (): Promise<void> => {
93+
const client = clients[StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE]
94+
const derivedSeriesMetadata = await client.retrieveSeriesMetadata({
95+
studyInstanceUID: studyInstanceUID,
96+
seriesInstanceUID: seriesInstanceUID
97+
})
98+
const naturalizedDerivedMetadata = naturalizeDataset(derivedSeriesMetadata[0]) as NaturalizedInstance
99+
if (
100+
naturalizedDerivedMetadata.ReferencedSeriesSequence != null &&
101+
naturalizedDerivedMetadata.ReferencedSeriesSequence.length > 0
102+
) {
103+
for (const referencedSeries of naturalizedDerivedMetadata.ReferencedSeriesSequence) {
104+
const referencedImageSeriesUID = referencedSeries.SeriesInstanceUID
108105
const referencedSlide = slides.find((slide: Slide) => {
109-
return slide.seriesInstanceUIDs.find((uid: string) => {
110-
return uid === referencedSeriesInstanceUID
111-
})
106+
return slide.seriesInstanceUIDs.some((uid: string) => uid === referencedImageSeriesUID)
112107
})
113-
resolve({ slide: referencedSlide, metadata: naturalizedSeriesMetadata })
108+
if (referencedSlide !== null && referencedSlide !== undefined) {
109+
setSelectedSlide(referencedSlide)
110+
setDerivedDataset(naturalizedDerivedMetadata)
111+
console.log('naturalizedDerivedMetadata', naturalizedDerivedMetadata)
112+
return
113+
}
114114
}
115-
116-
const IMAGE_LIBRARY_CONCEPT_NAME_CODE = '111028'
117-
const imageLibrary = naturalizedSeriesMetadata.ContentSequence?.find(
118-
contentItem => contentItem.ConceptNameCodeSequence[0].CodeValue === IMAGE_LIBRARY_CONCEPT_NAME_CODE
119-
)
120-
if ((imageLibrary?.ContentSequence?.[0]?.ContentSequence?.[0]?.ReferencedSOPSequence?.[0]) != null) {
121-
const referencedSOPInstanceUID = imageLibrary.ContentSequence[0].ContentSequence[0].ReferencedSOPSequence[0].ReferencedSOPInstanceUID
122-
const referencedSlide = slides.find((slide: Slide) => {
123-
return slide.volumeImages.find((image: { SOPInstanceUID: string }) => {
124-
return image.SOPInstanceUID === referencedSOPInstanceUID
125-
})
115+
}
116+
const IMAGE_LIBRARY_CONCEPT_NAME_CODE = '111028'
117+
const imageLibrary = naturalizedDerivedMetadata.ContentSequence?.find(
118+
contentItem => contentItem.ConceptNameCodeSequence[0].CodeValue === IMAGE_LIBRARY_CONCEPT_NAME_CODE
119+
)
120+
if ((imageLibrary?.ContentSequence?.[0]?.ContentSequence?.[0]?.ReferencedSOPSequence?.[0]) != null) {
121+
const referencedSOPInstanceUID = imageLibrary.ContentSequence[0].ContentSequence[0].ReferencedSOPSequence[0].ReferencedSOPInstanceUID
122+
const referencedSlide = slides.find((slide: Slide) => {
123+
return slide.volumeImages.find((image: { SOPInstanceUID: string }) => {
124+
return image.SOPInstanceUID === referencedSOPInstanceUID
126125
})
127-
resolve({ slide: referencedSlide, metadata: naturalizedSeriesMetadata })
128-
}
129-
})).catch(reject)
130-
} catch (error) {
131-
reject(error)
126+
})
127+
setSelectedSlide(referencedSlide)
128+
setDerivedDataset(naturalizedDerivedMetadata)
129+
}
132130
}
133-
})
134131

135-
if (selectedSlide === null || selectedSlide === undefined) {
136-
void findReferencedSlide({ clients, studyInstanceUID, seriesInstanceUID }).then((result: ReferencedSlideResult | null) => {
137-
if (result !== null && result !== undefined) {
138-
setSelectedSlide(result.slide)
139-
setDerivedDataset(result.metadata)
140-
}
141-
}).catch(error => {
142-
console.error('Error finding referenced slide:', error)
143-
})
132+
void findReferencedSlide()
144133
}
145134
}, [slides, clients, studyInstanceUID, seriesInstanceUID, selectedSlide])
146135

src/components/Header.tsx

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
Badge,
1212
Collapse,
1313
Radio,
14-
Tooltip
14+
Tooltip,
15+
Typography
1516
} from 'antd'
1617
import {
1718
ApiOutlined,
@@ -34,6 +35,47 @@ import { v4 as uuidv4 } from 'uuid'
3435
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser'
3536
import DicomWebManager from '../DicomWebManager'
3637

38+
const aboutModalCopyTooltips: [React.ReactNode, React.ReactNode] = ['Copy hash', 'Copied!']
39+
40+
const aboutModalStyles: Record<string, React.CSSProperties> = {
41+
container: {
42+
textAlign: 'center',
43+
lineHeight: 1.6,
44+
paddingRight: 25,
45+
fontSize: '1rem'
46+
},
47+
title: {
48+
fontSize: '1.4rem',
49+
fontWeight: 700,
50+
marginBottom: 4
51+
},
52+
subtitle: {
53+
fontSize: '1.15rem',
54+
fontWeight: 600,
55+
marginBottom: 12
56+
},
57+
link: {
58+
display: 'inline-block',
59+
marginBottom: 16
60+
},
61+
section: {
62+
marginBottom: 12
63+
},
64+
label: {
65+
fontWeight: 600,
66+
display: 'block',
67+
marginBottom: 4
68+
},
69+
bodyText: {
70+
marginBottom: 4
71+
},
72+
code: {
73+
display: 'inline-block',
74+
wordBreak: 'break-all',
75+
fontSize: '0.85rem'
76+
}
77+
}
78+
3779
interface HeaderProps extends RouteComponentProps {
3880
app: {
3981
name: string
@@ -178,45 +220,72 @@ class Header extends React.Component<HeaderProps, HeaderState> {
178220
'unknown'
179221
const viewerVersion = viewerVersionRaw.replace(/^[^0-9]*/, '')
180222

223+
const renderHashText = (hash: string | null): JSX.Element => {
224+
if (hash == null) {
225+
return (
226+
<Typography.Text code style={aboutModalStyles.code}>
227+
unknown
228+
</Typography.Text>
229+
)
230+
}
231+
return (
232+
<Typography.Text
233+
code
234+
style={aboutModalStyles.code}
235+
copyable={{
236+
text: hash,
237+
tooltips: aboutModalCopyTooltips
238+
}}
239+
>
240+
{hash}
241+
</Typography.Text>
242+
)
243+
}
244+
181245
Modal.info({
182246
width: 480,
183247
title: null,
184248
centered: true,
185249
content: (
186-
<div style={{ textAlign: 'center', lineHeight: 1.6, paddingRight: 25 }}>
187-
<div style={{ fontSize: '2.2rem', fontWeight: 700, marginBottom: 4 }}>{this.props.app.name}</div>
188-
<div style={{ fontSize: '2rem', fontWeight: 600 }}>{this.props.app.version}</div>
189-
<div style={{ fontSize: '1rem', marginBottom: 16 }}>
190-
<a href={this.props.app.homepage} target='_blank' rel='noreferrer'>
191-
{this.props.app.homepage}
192-
</a>
193-
</div>
194-
195-
<div style={{ marginBottom: 12 }}>
196-
<div style={{ fontWeight: 600 }}>Commit Hash</div>
197-
<code>{slimCommit ?? 'unknown'}</code>
250+
<div style={aboutModalStyles.container}>
251+
<Typography.Title level={3} style={aboutModalStyles.title}>
252+
<Typography.Link href={this.props.app.homepage} target='_blank' rel='noreferrer'>
253+
{this.props.app.name}
254+
</Typography.Link>
255+
</Typography.Title>
256+
<Typography.Text style={aboutModalStyles.subtitle}>
257+
{this.props.app.version}
258+
</Typography.Text>
259+
260+
<div style={aboutModalStyles.section}>
261+
<Typography.Text style={aboutModalStyles.label}>Commit Hash</Typography.Text>
262+
{renderHashText(slimCommit)}
198263
</div>
199264

200-
<div style={{ marginBottom: 12 }}>
201-
<div style={{ fontWeight: 600 }}>
265+
<div style={aboutModalStyles.section}>
266+
<Typography.Text style={aboutModalStyles.label}>
202267
<a
203268
href='https://github.com/MGHComputationalPathology/dicom-microscopy-viewer'
204269
target='_blank'
205270
rel='noreferrer'
206271
>
207272
DICOM Microscopy Viewer
208273
</a>
209-
</div>
210-
<div>Version {viewerVersion}</div>
211-
<code>{viewerCommit ?? 'unknown'}</code>
274+
</Typography.Text>
275+
<Typography.Text style={aboutModalStyles.bodyText}>
276+
Version {viewerVersion}
277+
</Typography.Text>
278+
{renderHashText(viewerCommit)}
212279
</div>
213280

214-
<div style={{ marginBottom: 12 }}>
215-
<div style={{ fontWeight: 600 }}>Current Browser &amp; OS</div>
216-
<div>
281+
<div style={aboutModalStyles.section}>
282+
<Typography.Text style={aboutModalStyles.label}>Current Browser &amp; OS</Typography.Text>
283+
<Typography.Text style={aboutModalStyles.bodyText}>
217284
{environment.browser.name ?? 'Unknown'} {environment.browser.version ?? ''}
218-
</div>
219-
<div>{environment.os.name ?? 'Unknown OS'}</div>
285+
</Typography.Text>
286+
<Typography.Text style={aboutModalStyles.bodyText}>
287+
{environment.os.name ?? 'Unknown OS'}
288+
</Typography.Text>
220289
</div>
221290
</div>
222291
),

0 commit comments

Comments
 (0)