Skip to content

Commit cc1c3bf

Browse files
committed
Add example for annotation and structured reporting
1 parent 588d82a commit cc1c3bf

File tree

2 files changed

+291
-2
lines changed

2 files changed

+291
-2
lines changed

examples/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ <h1>dicom-microscopy-viewer Examples</h1>
2020
</div>
2121

2222
<ul>
23-
<li><a href="basic/index.html">Rendering a single whole-slide image</a> - The basic usage of the library to view a whole slide image</li>
23+
<li><a href="basic/index.html">Rendering whole slide images</a> - The basic usage of the library to view a whole slide image</li>
2424
<li><a href="events/index.html">Handling viewer events</a> - Use custom viewer events to handle actions</li>
25-
<li><a href="simple_viewer/index.html">Simple whole-slide image viewer</a> - List and navigate available studies/series</li>
25+
<li><a href="structured_report/index.html">Annotating whole slide images</a> - Annotate regions of interest and save annotations as a structured report</li>
26+
<li><a href="simple_viewer/index.html">Simple whole slide image viewer</a> - List and navigate available studies/series</li>
2627
</ul>
2728
<br>
2829
</div>
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
9+
<meta name="theme-color" content="#000000" />
10+
11+
<title>dicom-microscopy-viewer example</title>
12+
13+
<!-- Latest compiled and minified CSS -->
14+
<link
15+
rel="stylesheet"
16+
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
17+
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
18+
crossorigin="anonymous"
19+
/>
20+
<link
21+
rel="stylesheet"
22+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
23+
integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0="
24+
crossorigin="anonymous"
25+
/>
26+
</head>
27+
28+
<body>
29+
<noscript>
30+
You need to enable JavaScript to run this app.
31+
</noscript>
32+
33+
<script src="https://unpkg.com/dicomweb-client@latest"
34+
type="application/javascript"
35+
charset="utf-8">
36+
</script>
37+
<script src="https://unpkg.com/dicom-microscopy-viewer@latest"
38+
type="application/javascript"
39+
charset="utf-8">
40+
</script>
41+
<script src="https://unpkg.com/dcmjs@latest"
42+
type="application/javascript"
43+
charset="utf-8">
44+
</script>
45+
<script src="../../js/FileSaver.min.js"></script>
46+
47+
<script>
48+
49+
document.addEventListener("DOMContentLoaded", () => {
50+
51+
document.getElementById("tools-list").addEventListener("click", (event) => {
52+
const geometryType = event.target.id;
53+
disableToolList();
54+
window.viewer.activateDrawInteraction({ geometryType });
55+
document.getElementById(`${geometryType}`).parentNode.setAttribute("class", "active");
56+
});
57+
58+
document.getElementById("downloadReport").addEventListener("click", (event) => {
59+
const rois = window.viewer.getAllROIs();
60+
if(rois.length === 0){
61+
alert('Please, draw an region of interest first.');
62+
return;
63+
}
64+
65+
// Metadata of image at highest resolution level
66+
const imageMetadata = window.viewer.imageMetadata[window.viewer.imageMetadata.length-1];
67+
const observationContext = new dcmjs.sr.templates.ObservationContext({
68+
observerPersonContext: new dcmjs.sr.templates.ObserverContext({
69+
observerType: new dcmjs.sr.coding.CodedConcept({
70+
value: "121006",
71+
schemeDesignator: "DCM",
72+
meaning: "Person",
73+
}),
74+
observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes({
75+
name: "Robo^Doc"
76+
})
77+
}),
78+
observerDeviceContext: new dcmjs.sr.templates.ObserverContext({
79+
observerType: new dcmjs.sr.coding.CodedConcept({
80+
value: "121007",
81+
schemeDesignator: "DCM",
82+
meaning: "Device",
83+
}),
84+
observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes({
85+
uid: dcmjs.data.DicomMetaDictionary.uid() // FIXME
86+
})
87+
}),
88+
subjectContext: new dcmjs.sr.templates.SubjectContext({
89+
subjectClass: new dcmjs.sr.coding.CodedConcept({
90+
value: "121027",
91+
schemeDesignator: "DCM",
92+
meaning: "Specimen",
93+
}),
94+
subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen({
95+
uid: imageMetadata.SpecimenDescriptionSequence[0].SpecimenUID,
96+
identifier: imageMetadata.SpecimenDescriptionSequence[0].SpecimenIdentifier,
97+
containerIdentifier: imageMetadata.ContainerIdentifier
98+
})
99+
})
100+
});
101+
102+
const imagingMeasurements = [];
103+
for (let i = 0; i < rois.length; i++) {
104+
const roi = rois[i];
105+
const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations({
106+
trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({
107+
uid: roi.uid,
108+
identifier: `Measurements of ROI #${i+1}`
109+
}),
110+
referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({
111+
graphicType: roi.scoord3d.graphicType,
112+
graphicData: roi.scoord3d.graphicData,
113+
frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID
114+
}),
115+
findingType: new dcmjs.sr.coding.CodedConcept({
116+
value: "108369006",
117+
schemeDesignator: "SCT",
118+
meaning: "Tumor",
119+
})
120+
});
121+
imagingMeasurements.push(...group);
122+
}
123+
124+
const measurementReport = new dcmjs.sr.templates.MeasurementReport({
125+
languageOfContentItemAndDescendants: new dcmjs.sr.templates.LanguageOfContentItemAndDescendants({}),
126+
observationContext: observationContext,
127+
procedureReported: new dcmjs.sr.coding.CodedConcept({
128+
value: "112703",
129+
schemeDesignator: "DCM",
130+
meaning: "Whole Slide Imaging",
131+
}),
132+
imagingMeasurements: imagingMeasurements
133+
});
134+
135+
const dataset = new dcmjs.sr.documents.Comprehensive3DSR({
136+
content: measurementReport[0],
137+
evidence: [imageMetadata],
138+
seriesInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
139+
seriesNumber: 1,
140+
seriesDescription: "Whole slide imaging structured report example",
141+
sopInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
142+
instanceNumber: 1,
143+
manufacturer: "dcmjs-org",
144+
});
145+
console.log(dataset)
146+
const fileMetaInformationVersionArray = new Uint8Array(2);
147+
fileMetaInformationVersionArray[1] = 1;
148+
dataset._meta = {
149+
FileMetaInformationVersion: {
150+
Value: [fileMetaInformationVersionArray.buffer], // TODO
151+
vr: "OB"
152+
},
153+
MediaStorageSOPClassUID: dataset.sopClassUID,
154+
MediaStorageSOPInstanceUID: dataset.sopInstanceUID,
155+
TransferSyntaxUID: {
156+
Value: ["1.2.840.10008.1.2.1"],
157+
vr: "UI"
158+
},
159+
ImplementationClassUID: {
160+
Value: [dcmjs.data.DicomMetaDictionary.uid()],
161+
vr: "UI"
162+
},
163+
ImplementationVersionName: {
164+
Value: ["dicom-microscopy-viewer-example"],
165+
vr: "SH"
166+
}
167+
};
168+
const blob = dcmjs.data.datasetToBlob(dataset);
169+
saveAs(blob, `${dataset.SOPInstanceUID}.dcm`, true);
170+
});
171+
172+
});
173+
174+
function disableToolList(){
175+
const toolList = document.getElementById("tools-list").querySelector("li.active")
176+
if(toolList !== null){
177+
toolList.classList.remove("active");
178+
}
179+
}
180+
181+
const url = "https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs";
182+
const client = new DICOMwebClient.api.DICOMwebClient({ url });
183+
const studyInstanceUID = "1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518";
184+
const seriesInstanceUID = "1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519";
185+
186+
const searchInstanceOptions = {
187+
studyInstanceUID,
188+
seriesInstanceUID
189+
};
190+
191+
client.searchForInstances(searchInstanceOptions)
192+
.then(instances => {
193+
const promises = [];
194+
195+
for (let i = 0; i < instances.length; i++) {
196+
197+
const sopInstanceUID = instances[i]["00080018"]["Value"][0];
198+
199+
const retrieveInstanceOptions = {
200+
studyInstanceUID,
201+
seriesInstanceUID,
202+
sopInstanceUID
203+
};
204+
205+
const promise = client.retrieveInstanceMetadata(retrieveInstanceOptions)
206+
.then(metadata => {
207+
const imageType = metadata[0]["00080008"]["Value"];
208+
if (imageType[2] === "VOLUME") {
209+
return metadata[0];
210+
}
211+
});
212+
promises.push(promise);
213+
}
214+
return Promise.all(promises);
215+
})
216+
.then(metadata => {
217+
metadata = metadata.filter(m => m);
218+
const viewer = new DICOMMicroscopyViewer.api.VLWholeSlideMicroscopyImageViewer({
219+
client,
220+
metadata,
221+
retrieveRendered:false
222+
});
223+
224+
const container = document.getElementById("dicomImage");
225+
viewer.render({ container });
226+
227+
viewer.activateDrawInteraction({ geometryType: "polygon" });
228+
window.viewer = viewer;
229+
230+
});
231+
232+
window.dcmjs = dcmjs;
233+
234+
</script>
235+
</body>
236+
<body>
237+
<div class="container">
238+
<div class="page-header">
239+
<h1>
240+
Example for Slide Microscopy Image Annotation
241+
</h1>
242+
<p>
243+
This example demonstrates how to annotate regions of interest (ROIs) on a digital slide represented by DICOM <a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.32.8.html" target="_blank">VL Whole Slide Microscopy Image</a> instances and to store the graphic annotations as SCOORD3D content items using the <a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.18.9.html" target="_blank">3D Spatial Coordinates Macro</a> in a
244+
DICOM <a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.35.13.html" target="_blank">Comprehensive 3D SR</a> document based on the DICOM Structured Reporting template <a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part16/chapter_A.html#sect_TID_1500" target="_blank">TID 1500 "Measurement Report"</a>.
245+
</p>
246+
<a href="../../index.html">Go back to the Examples page</a>
247+
</div>
248+
249+
<div class="bs-example" data-example-id="simple-thumbnails">
250+
<div class="row">
251+
252+
<div class="col-xs-12 col-md-9" >
253+
<div class="alert alert-warning text-center" role="alert">Select below the tool you want to use to draw</div>
254+
<ul class="nav nav-pills" id="tools-list">
255+
<li role="presentation" class="active"><a id="polygon">Polygon</a></li>
256+
<li role="presentation"><a id="point">Point</a></li>
257+
<li role="presentation"><a id="circle">Circle</a></li>
258+
<li role="presentation"><a id="box">Box</a></li>
259+
<li role="presentation"><a id="freehandpolygon">Freehand Polygon</a></li>
260+
<li role="presentation"><a id="line">Line</a></li>
261+
<li role="presentation"><a id="freehandline">Freehand Line</a></li>
262+
</ul>
263+
<br/>
264+
265+
</div>
266+
<div class="col-xs-12 col-md-3">
267+
<div class="alert alert-warning text-center" role="alert">Structured Report</div>
268+
<div class="list-group">
269+
<input
270+
type="button" class="btn btn-primary center-block" id="downloadReport"
271+
value="Download"
272+
/>
273+
</div>
274+
</div>
275+
<div class="col-xs-12 col-md-12">
276+
<div style="width:100%;height:550px;position:relative;display:inline-block;">
277+
<div id="dicomImage"
278+
style="width:100%;height:550px;top:0px;left:0px; position:absolute; border: 1px solid black;">
279+
</div>
280+
281+
</div>
282+
</div>
283+
</div>
284+
</div>
285+
</div>
286+
287+
</body>
288+
</html>

0 commit comments

Comments
 (0)