Skip to content

Commit 7d22998

Browse files
Punzoswederik
andauthored
ENH: multi channels API, coloring and blending (#47)
* Add support for interactive visualization of immunofluorescence images - Implement rendering engine for additive blending and coloring of multiple monochrome images with different optical paths (channels) using WebGL - Implement methods for selecting channels for display and setting blending information (threshold value, color) * Add ability to decode compressed images client-side for a variety of transfer syntaxes (media types), including baseline JPEG, JPEG 2000, JPEG-LS Co-authored-by: Erik Ziegler <[email protected]>
1 parent b4d0ecc commit 7d22998

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+12040
-17958
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,15 @@ client.searchForInstances(searchInstanceOptions).then((instances) => {
7373
sopInstanceUID,
7474
};
7575
const promise = client.retrieveInstanceMetadata(retrieveInstanceOptions).then(metadata => {
76-
const imageType = metadata[0]["00080008"]["Value"];
77-
if (imageType[2] === "VOLUME") {
76+
const image = DICOMMicroscopyViewer.metadata.formatMetadata(metadata[0]);
77+
if (image.ImageType[2] === "VOLUME") {
7878
return(metadata[0]);
7979
}
8080
});
8181
promises.push(promise);
8282
}
8383
return(Promise.all(promises));
8484
}).then(metadata => {
85-
metadata = metadata.filter(m => m);
8685
const viewer = new DICOMMicroscopyViewer.viewer.VolumeViewer({
8786
client,
8887
metadata

babel.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module.exports = {
2-
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
3-
};
2+
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
3+
}

examples/basic/index.html

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616
<noscript>
1717
You need to enable JavaScript to run this app.
1818
</noscript>
19-
19+
<script src="https://unpkg.com/@cornerstonejs/codec-libjpeg-turbo/dist/libjpegturbowasm.js"></script>
20+
<script src="https://unpkg.com/@cornerstonejs/codec-openjpeg/dist/openjpegwasm.js"></script>
21+
<script src="https://unpkg.com/@cornerstonejs/codec-charls/dist/charlsjs.js"></script>
22+
2023
<script src="https://unpkg.com/dicomweb-client"></script>
2124
<script src="https://unpkg.com/dicom-microscopy-viewer"></script>
25+
<!-- enable for testing locally
26+
<script src="../../node_modules/dicomweb-client/build/dicomweb-client.js"></script>
27+
<script src="../../build/dicom-microscopy-viewer.js"></script>
28+
!-->
2229
<script>
2330
const url = 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs';
2431
const client = new DICOMwebClient.api.DICOMwebClient({url});
25-
const studyInstanceUID = '1.2.392.200140.2.1.1.1.2.799008771.2448.1519719572.518';
26-
const seriesInstanceUID = '1.2.392.200140.2.1.1.1.3.799008771.2448.1519719572.519';
32+
const studyInstanceUID = '1.2.392.200140.2.1.1.1.2.799008771.2156.1519721160.421';
33+
const seriesInstanceUID = '1.2.392.200140.2.1.1.1.3.799008771.2156.1519721160.422';
2734
const searchInstanceOptions = {
2835
studyInstanceUID,
2936
seriesInstanceUID
@@ -37,24 +44,24 @@
3744
seriesInstanceUID,
3845
sopInstanceUID,
3946
};
47+
4048
const promise = client.retrieveInstanceMetadata(retrieveInstanceOptions).then(metadata => {
41-
const imageType = metadata[0]["00080008"]["Value"];
42-
if (imageType[2] === "VOLUME") {
49+
const image = DICOMMicroscopyViewer.metadata.formatMetadata(metadata[0]);
50+
if (image.ImageType[2] === "VOLUME") {
4351
return(metadata[0]);
4452
}
4553
});
54+
4655
promises.push(promise);
4756
}
57+
4858
return(Promise.all(promises));
4959
}).then(metadata => {
50-
metadata = metadata.filter(m => m);
51-
52-
// Note: We are using retrieveRendered: false because dcm4chee does not yet
53-
// support this functionality. See https://github.com/dcm4che/dcm4chee-arc-light/issues/1617
5460
const viewer = new DICOMMicroscopyViewer.api.VLWholeSlideMicroscopyImageViewer({
5561
client,
5662
metadata,
57-
retrieveRendered: false
63+
controls: ['fullscreen', 'overview', 'zoom'],
64+
retrieveRendered: true
5865
});
5966

6067
var container = document.getElementById('root');
@@ -71,7 +78,7 @@ <h1>
7178
</p>
7279
<a href="../">Go back to the Examples page</a>
7380
</div>
74-
<div id="root"></div>
81+
<div id="root" style="height:1000px; width:1000px"></div>
7582
</div>
7683
</body>
7784
</html>

examples/blend/index.html

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
8+
<title>dicom-microscopy-viewer example</title>
9+
10+
<!-- Latest compiled and minified CSS -->
11+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
12+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
13+
</head>
14+
15+
<body>
16+
<noscript>
17+
You need to enable JavaScript to run this app.
18+
</noscript>
19+
<script src="https://unpkg.com/@cornerstonejs/codec-libjpeg-turbo/dist/libjpegturbowasm.js"></script>
20+
<script src="https://unpkg.com/@cornerstonejs/codec-openjpeg/dist/openjpegwasm.js"></script>
21+
<script src="https://unpkg.com/@cornerstonejs/codec-charls/dist/charlsjs.js"></script>
22+
23+
<script src="https://unpkg.com/dicomweb-client"></script>
24+
<script src="https://unpkg.com/dicom-microscopy-viewer"></script>
25+
<!-- enable for testing locally
26+
<script src="../../node_modules/dicomweb-client/build/dicomweb-client.js"></script>
27+
<script src="../../build/dicom-microscopy-viewer.js"></script>
28+
!-->
29+
<script>
30+
var colorPicker;
31+
var channelToggle;
32+
var minRange;
33+
window.addEventListener("load", startup, false);
34+
35+
function startup() {
36+
colorPicker = document.querySelector("#colorpicker");
37+
colorPicker.addEventListener("change", updateChannelColor, false);
38+
colorPicker.select();
39+
40+
channelToggle = document.querySelector("#toggleChannel");
41+
channelToggle.addEventListener("change", updateChannelToggle, false);
42+
43+
minRange = document.querySelector("#minRange");
44+
minRange.oninput = function() {
45+
const minValue = this.value;
46+
const thresholdValues = [minValue, 255.];
47+
const blendingInformation = {
48+
thresholdValues : thresholdValues,
49+
opticalPathID : `15`,
50+
};
51+
viewer.setBlendingInformation(blendingInformation);
52+
}
53+
}
54+
55+
function updateChannelColor(event) {
56+
const hexColor = event.target.value;
57+
const color = [parseInt(hexColor.substr(1, 2), 16) / 255.,
58+
parseInt(hexColor.substr(3, 2), 16) / 255.,
59+
parseInt(hexColor.substr(5, 2), 16) / 255.];
60+
const blendingInformation = {
61+
color : color,
62+
opticalPathID : `15`,
63+
};
64+
viewer.setBlendingInformation(blendingInformation);
65+
}
66+
67+
function updateChannelToggle() {
68+
const opticalPathID = `15`;
69+
if(channelToggle.checked) {
70+
viewer.activateOpticalPath(opticalPathID);
71+
viewer.showOpticalPath(opticalPathID);
72+
} else {
73+
viewer.deactivateOpticalPath(opticalPathID);
74+
}
75+
}
76+
77+
const url = 'http://34.68.90.36/';
78+
const client = new DICOMwebClient.api.DICOMwebClient({url});
79+
const studyInstanceUID = '1.3.6.1.4.1.5962.99.1.2103930081.1286074986.1595536829665.3.0';
80+
const seriesInstanceUIDOne = '1.3.6.1.4.1.5962.1.1.0.0.0.1595262375.18986.1';
81+
const seriesInstanceUIDTwo = '1.3.6.1.4.1.5962.1.1.0.0.0.1595262375.18986.15';
82+
const seriesInstanceUIDThree = '1.3.6.1.4.1.5962.1.1.0.0.0.1595262375.18986.35';
83+
const seriesInstanceUIDs = [seriesInstanceUIDOne, seriesInstanceUIDTwo, seriesInstanceUIDThree];
84+
// NOTE: for parsing all the series of the study to the DICOMMicroscopyViewer, just setup seriesInstanceUIDs = [];
85+
86+
const searchInstanceOptions = {
87+
studyInstanceUID,
88+
};
89+
90+
client.searchForInstances(searchInstanceOptions).then((instances) => {
91+
const promises = []
92+
for (let i = 0; i < instances.length; i++) {
93+
const seriesInstanceUID = instances[i]["0020000E"]["Value"][0];
94+
if (seriesInstanceUIDs.length !== 0 && seriesInstanceUIDs.findIndex(uid => uid === seriesInstanceUID) === -1) {
95+
continue;
96+
}
97+
const sopInstanceUID = instances[i]["00080018"]["Value"][0];
98+
const retrieveInstanceOptions = {
99+
studyInstanceUID,
100+
seriesInstanceUID,
101+
sopInstanceUID,
102+
};
103+
104+
const promise = client.retrieveInstanceMetadata(retrieveInstanceOptions).then(metadata => {
105+
const image = DICOMMicroscopyViewer.metadata.formatMetadata(metadata[0]);
106+
if (image.ImageType[2] === "VOLUME") {
107+
return(metadata[0]);
108+
}
109+
});
110+
promises.push(promise);
111+
}
112+
113+
return(Promise.all(promises));
114+
}).then(metadata => {
115+
116+
// Example of custom initialization of the channel visualization parameters (this is optional)
117+
const BIOne = new DICOMMicroscopyViewer.metadata.BlendingInformation(
118+
opticalPathIdentifier = `35`,
119+
color = [0.,0.5,0.5],
120+
opacity = 1.0,
121+
thresholdValues = [125., 255.],
122+
visible = true,
123+
);
124+
const BITwo = new DICOMMicroscopyViewer.metadata.BlendingInformation(
125+
opticalPathIdentifier = `1`,
126+
color = [0.5, 0.5, 0.],
127+
opacity = 1.0,
128+
thresholdValues = [0., 255.],
129+
visible = true,
130+
);
131+
const BIThree = new DICOMMicroscopyViewer.metadata.BlendingInformation(
132+
opticalPathIdentifier = `15`,
133+
color = [1, 0., 0.],
134+
opacity = 1.0,
135+
thresholdValues = [30., 255.],
136+
visible = true,
137+
);
138+
139+
const viewer = new DICOMMicroscopyViewer.api.VLWholeSlideMicroscopyImageViewer({
140+
client,
141+
metadata,
142+
blendingInformation: [BIOne, BITwo, BIThree],
143+
controls: ['fullscreen', 'zoom'],
144+
retrieveRendered: true,
145+
});
146+
147+
const container = document.getElementById("dicomImage");
148+
viewer.render({ container });
149+
window.viewer = viewer;
150+
});
151+
152+
</script>
153+
<div class="container">
154+
<div class="page-header">
155+
<h1>
156+
DICOM Microscopy Viewer Example
157+
</h1>
158+
<p>
159+
This is a simple example blending 3 channels (DAPI 1, KERATIN and ASMA) of a Microscopy image. <br />
160+
Data credit : Rashid et al. Sci Data Manuscript 2019 (DOI: <a href="https://doi.org/10.7303/syn17865732"> https://doi.org/10.7303/syn17865732</a>).
161+
</p>
162+
<a href="../">Go back to the Examples page</a>
163+
</div>
164+
<div id="root" style="height:10px; width:10px"></div>
165+
</div>
166+
167+
<div class="bs-example" data-example-id="simple-thumbnails">
168+
<div class="row">
169+
<div class="col-xs-3 col-md-4">
170+
<div class="alert alert-warning text-center" role="alert">Toggle KERATIN Channel</div>
171+
<input type="checkbox" checked id="toggleChannel" style="margin:auto; width:100%; height:40px"/>
172+
</div>
173+
<div class="col-xs-3 col-md-4" >
174+
<div class="alert alert-warning text-center" role="alert">Select Color</div>
175+
<input type="color" id="colorpicker" value="#ff0000" style="margin:auto; width:100%; height:40px">
176+
<br/>
177+
</div>
178+
<div class="col-xs-3 col-md-4" >
179+
<div class="alert alert-warning text-center" role="alert">Clip Min Values</div>
180+
<input type="range" min="0" max="256" value="30" class="slider" id="minRange" style="margin:auto; width:100%; height:40px">
181+
<br/>
182+
</div>
183+
184+
<div class="col-xs-12 col-md-12">
185+
<div
186+
style="width:100%;height:200px;position:relative;display:inline-block;"
187+
oncontextmenu="return false"
188+
class="cornerstone-enabled-image"
189+
unselectable="on"
190+
onselectstart="return false;"
191+
onmousedown="return false;"
192+
>
193+
<div
194+
id="dicomImage"
195+
style="width:100%;height:850px;top:0px;left:0px; position:absolute; border: 1px solid black;"
196+
></div>
197+
198+
</div>
199+
</div>
200+
</div>
201+
</div>
202+
</div>
203+
<style>
204+
.scrollable {
205+
height: 600px;
206+
overflow-y: scroll;
207+
}
208+
#rois li {
209+
list-style-type: none;
210+
}
211+
</style>
212+
</body>
213+
</html>

0 commit comments

Comments
 (0)