Runtime controlling fluo channels
advancedimage adjustmentsslide loader
Console
PMA.UI version: 2.43.3
Viewer example showing runtime controlling fluo channels, such as channel color, clipping & gamma.
runtime_controls_fluo.html
1<!doctype >
2<html lang="en">
3
4<head>
5 <meta charset="utf-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=10">
7 <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
8
9 <!-- Include PMA.UI required libraries downloaded or from CDN -->
10 <script src="./pma.ui/jquery-3.1.0.js"></script>
11 <link href="./pma.ui/font-awesome.min.css" type="text/css" rel="stylesheet">
12
13 <!-- Include optional libraries downloaded or from CDN -->
14 <link rel="stylesheet" href="./pma.ui/bootstrap.min.css">
15 <script src="./pma.ui/bootstrap.bundle.min.js"></script>
16 <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/11.0.2/bootstrap-slider.min.js"
17 integrity="sha512-f0VlzJbcEB6KiW8ZVtL+5HWPDyW1+nJEjguZ5IVnSQkvZbwBt2RfCBY0CBO1PsMAqxxrG4Di6TfsCPP3ZRwKpA=="
18 crossorigin="anonymous" referrerpolicy="no-referrer"></script>
19 <link rel="stylesheet"
20 href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/11.0.2/css/bootstrap-slider.min.css"
21 integrity="sha512-3q8fi8M0VS+X/3n64Ndpp6Bit7oXSiyCnzmlx6IDBLGlY5euFySyJ46RUlqIVs0DPCGOypqP8IRk/EyPvU28mQ=="
22 crossorigin="anonymous" referrerpolicy="no-referrer" />
23
24 <!-- Include PMA.UI script & css -->
25 <script src="./pma.ui/pma.ui.js"></script>
26 <link href="./pma.ui/pma.ui.css" type="text/css" rel="stylesheet">
27
28 <!-- Include custom script & css -->
29 <script src="./js/runtime_controls_fluo.js"></script>
30 <link href="./css/runtime_controls_fluo.css" type="text/css" rel="stylesheet">
31
32 <title>Runtime controlling fluo channels</title>
33</head>
34
35<body>
36 <div class="container-fluid">
37 <div class="row">
38 <div class="col-4 py-1 border-right" style="overflow-y: auto; overflow-x: hidden; max-height: 100vh;">
39 <form>
40 <fieldset id="channel0" disabled>
41 <div class="form-group row">
42 <label for="channel0"
43 class="col-2 col-form-label col-form-label-sm align-self-center"></label>
44 <div class="col-10 pt-2">
45 <div class="form-group row">
46 <label for="inputActive0"
47 class="col-2 col-form-label col-form-label-sm">Active</label>
48 <div class="col-7 pt-2">
49 <input class="form-check-input" type="checkbox" id="inputActive0">
50 </div>
51 </div>
52 <div class="form-group row">
53 <label for="inputColor" class="col-2 col-form-label col-form-label-sm">Color</label>
54 <div class="col-7 pt-2">
55 <input type="color" class="custom-range" id="inputColor0" value="">
56 </div>
57 <label for="inputColor0" id="inputColor0-label"
58 class="col-3 col-form-label col-form-label-sm"></label>
59 </div>
60 <div class="form-group row">
61 <label for="inputClipping"
62 class="col-2 col-form-label col-form-label-sm">Clipping</label>
63 <div class="col-7 pt-2">
64 <!-- <input type="range" class="custom-range" min="0" max="255" step="1"
65 id="inputClipping0" value=""> -->
66 <input id="inputClipping0" type="text" class="span2" value=""
67 data-slider-min="0" data-slider-max="100" data-slider-step="1"
68 data-slider-value="[0,100]" data-slider-enabled="false" />
69 </div>
70 <label for="inputClipping0" id="inputClipping0-label"
71 class="col-3 col-form-label col-form-label-sm"></label>
72 </div>
73 <div class="form-group row">
74 <label for="inputGamma" class="col-2 col-form-label col-form-label-sm">Gamma</label>
75 <div class="col-7 pt-2">
76 <input type="range" class="custom-range" min="0" max="20" step="0.1"
77 id="inputGamma0" value="">
78 </div>
79 <label for="inputGamma0" id="inputGamma0-label"
80 class="col-3 col-form-label col-form-label-sm"></label>
81 </div>
82 </div>
83 </div>
84 </fieldset>
85 <hr />
86 <fieldset id="channel1" disabled>
87 <div class="form-group row">
88 <label for="channel1"
89 class="col-2 col-form-label col-form-label-sm align-self-center"></label>
90 <div class="col-10 pt-2">
91 <div class="form-group row">
92 <label for="inputActive1"
93 class="col-2 col-form-label col-form-label-sm">Active</label>
94 <div class="col-7 pt-2">
95 <input class="form-check-input" type="checkbox" id="inputActive1">
96 </div>
97 </div>
98 <div class="form-group row">
99 <label for="inputColor" class="col-2 col-form-label col-form-label-sm">Color</label>
100 <div class="col-7 pt-2">
101 <input type="color" class="custom-range" id="inputColor1" value="">
102 </div>
103 <label for="inputColor1" id="inputColor1-label"
104 class="col-3 col-form-label col-form-label-sm"></label>
105 </div>
106 <div class="form-group row">
107 <label for="inputClipping"
108 class="col-2 col-form-label col-form-label-sm">Clipping</label>
109 <div class="col-7 pt-2">
110 <!-- <input type="range" class="custom-range" min="0" max="255" step="1"
111 id="inputClipping1" value=""> -->
112 <input id="inputClipping1" type="text" class="span2" value=""
113 data-slider-min="0" data-slider-max="100" data-slider-step="1"
114 data-slider-value="[0,100]" data-slider-enabled="false" />
115 </div>
116 <label for="inputClipping1" id="inputClipping1-label"
117 class="col-3 col-form-label col-form-label-sm"></label>
118 </div>
119 <div class="form-group row">
120 <label for="inputGamma" class="col-2 col-form-label col-form-label-sm">Gamma</label>
121 <div class="col-7 pt-2">
122 <input type="range" class="custom-range" min="0" max="20" step="0.1"
123 id="inputGamma1" value="">
124 </div>
125 <label for="inputGamma1" id="inputGamma1-label"
126 class="col-3 col-form-label col-form-label-sm"></label>
127 </div>
128 </div>
129 </div>
130 </fieldset>
131 <hr />
132 <fieldset id="channel2" disabled>
133 <div class="form-group row">
134 <label for="channel2"
135 class="col-2 col-form-label col-form-label-sm align-self-center"></label>
136 <div class="col-10 pt-2">
137 <div class="form-group row">
138 <label for="inputActive2"
139 class="col-2 col-form-label col-form-label-sm">Active</label>
140 <div class="col-7 pt-2">
141 <input class="form-check-input" type="checkbox" id="inputActive2">
142 </div>
143 </div>
144 <div class="form-group row">
145 <label for="inputColor" class="col-2 col-form-label col-form-label-sm">Color</label>
146 <div class="col-7 pt-2">
147 <input type="color" class="custom-range" id="inputColor2" value="">
148 </div>
149 <label for="inputColor2" id="inputColor2-label"
150 class="col-3 col-form-label col-form-label-sm"></label>
151 </div>
152 <div class="form-group row">
153 <label for="inputClipping"
154 class="col-2 col-form-label col-form-label-sm">Clipping</label>
155 <div class="col-7 pt-2">
156 <!-- <input type="range" class="custom-range" min="0" max="255" step="1"
157 id="inputClipping2" value=""> -->
158 <input id="inputClipping2" type="text" class="span2" value=""
159 data-slider-min="0" data-slider-max="100" data-slider-step="1"
160 data-slider-value="[0,100]" data-slider-enabled="false" />
161 </div>
162 <label for="inputClipping2" id="inputClipping2-label"
163 class="col-3 col-form-label col-form-label-sm"></label>
164 </div>
165 <div class="form-group row">
166 <label for="inputGamma" class="col-2 col-form-label col-form-label-sm">Gamma</label>
167 <div class="col-7 pt-2">
168 <input type="range" class="custom-range" min="0" max="20" step="0.1"
169 id="inputGamma2" value="">
170 </div>
171 <label for="inputGamma2" id="inputGamma2-label"
172 class="col-3 col-form-label col-form-label-sm"></label>
173 </div>
174 </div>
175 </div>
176 </fieldset>
177 <hr />
178 <div class="form-group row">
179 <div class="col">
180 <button id="reset-btn" type="button" class="btn btn-light btn-sm"><i
181 class="fas fa-history"></i> Reset values</button>
182 </div>
183 </div>
184 </form>
185 </div>
186 <div class="col-8">
187 <!-- The element that will host the viewport -->
188 <div id="viewer"></div>
189 </div>
190 </div>
191 </div>
192</body>
193
194</html>
runtime_controls_fluo.css
1html,
2body {
3 height: 100%;
4 padding: 0px;
5 margin: 0px;
6}
7
8#viewer {
9 height: 100vh;
10}
11
12#inputActive0,
13#inputActive1,
14#inputActive2 {
15 margin-top: unset;
16 margin-left: unset;
17}
18
19.slider.slider-horizontal {
20 width: 90%;
21 margin-left: 16px;
22}
runtime_controls_fluo.js
1// Initial declarations
2var serverUrl = "https://host.pathomation.com/pma.core.3/";
3var serverUsername = "PMA_UI_demo";
4var serverPassword = "PMA_UI_demo";
5var caller = "DemoPortal";
6var slideLoaderElementSelector = "#viewer";
7var imagePath = "wsiformats/fluo/Olympus/155 _02.vsi";
8var slideLoader = null;
9var initialChannelRenderingOptions = [];
10var initialActiveChannels = [];
11
12jQuery(function () {
13 console.log(`PMA.UI version: ${PMA.UI.getVersion()}`);
14
15 // Create a context
16 var context = new PMA.UI.Components.Context({ caller: caller });
17
18 // Add an autologin authentication provider
19 new PMA.UI.Authentication.AutoLogin(context, [{ serverUrl: serverUrl, username: serverUsername, password: serverPassword }]);
20
21 // Create an image loader that will allow us to load images easily
22 slideLoader = new PMA.UI.Components.SlideLoader(context, {
23 element: slideLoaderElementSelector,
24 dimensions: false,
25 });
26
27 // Listen for the slide loaded event by the slide loader
28 slideLoader.listen(PMA.UI.Components.Events.SlideLoaded, function (args) {
29 console.log("Slide loaded");
30 console.log(args);
31 $("#reset-btn").attr("disabled", false);
32
33 // Get initial values from viewport
34 var bgColor = slideLoader.getLoadedImageInfo().BackgroundColor;
35 if (bgColor) {
36 $("#viewer").parent().css("background-color", "#" + bgColor);
37 }
38
39 const channelRenderingOptions = slideLoader.mainViewport.getChannelRenderingOptions();
40 const activeChannels = slideLoader.mainViewport.getActiveChannels();
41 initialChannelRenderingOptions = [...channelRenderingOptions];
42 initialActiveChannels = activeChannels;
43 console.log(channelRenderingOptions);
44
45 // Assign initial values to inputs
46 channelRenderingOptions.forEach(ch => {
47 $("#channel" + ch.index + " label").first().text(ch.name);
48 $("#inputColor" + ch.index).val(ch.color.replace("ff", "#"));
49 $("#inputClipping" + ch.index).bootstrapSlider({ id: "#inputClipping" + ch.index, value: ch.clipping ?? [0, 100], enabled: true });
50 $("#inputGamma" + ch.index).val(ch.gamma);
51 $("#inputColor" + ch.index + "-label").text(ch.color.replace("ff", "#"));
52 $("#inputClipping" + ch.index + "-label").text(JSON.stringify(ch.clipping ?? [0, 100]));
53 $("#inputGamma" + ch.index + "-label").text(ch.gamma.toFixed(2));
54 $("#channel" + ch.index).attr("disabled", false);
55
56 if (activeChannels.includes(ch.index)) {
57 $("#inputActive" + ch.index).prop('checked', true);
58 } else {
59 $("#inputColor" + ch.index).attr("disabled", true);
60 $("#inputGamma" + ch.index).attr("disabled", true);
61 $("#inputClipping" + ch.index).bootstrapSlider("disable");
62 }
63
64 $("#inputActive" + ch.index).on("change", function () {
65 // Channel active or inactive
66 var currentActiveChannels = slideLoader.mainViewport.getActiveChannels();
67 if ($("#inputActive" + ch.index).is(':checked')) {
68 currentActiveChannels.indexOf(ch.index) === -1 && currentActiveChannels.push(ch.index);
69 slideLoader.mainViewport.setActiveChannels(currentActiveChannels);
70 $("#inputColor" + ch.index).attr("disabled", false);
71 $("#inputGamma" + ch.index).attr("disabled", false);
72 $("#inputClipping" + ch.index).bootstrapSlider("enable");
73 console.log(`Channel ${ch.index} active: True`);
74 } else {
75 slideLoader.mainViewport.setActiveChannels(currentActiveChannels.filter(x => x !== ch.index));
76 $("#inputColor" + ch.index).attr("disabled", true);
77 $("#inputGamma" + ch.index).attr("disabled", true);
78 $("#inputClipping" + ch.index).bootstrapSlider("disable");
79 console.log(`Channel ${ch.index} active: False`);
80 }
81 });
82
83 $("#inputColor" + ch.index).on("input", function (e) {
84 $("#inputColor" + ch.index + "-label").text(e.target.value);
85 });
86
87 $("#inputColor" + ch.index).on("change", function (e) {
88 // Assign input's value to color
89 var channelROptions = channelRenderingOptions[ch.index];
90 channelROptions.color = e.target.value.replace("#", "ff");
91 slideLoader.mainViewport.setChannelRenderingOptions(channelROptions);
92 $("#inputColor" + ch.index + "-label").text(e.target.value);
93 console.log(`Color for channel ${ch.index}: ${e.target.value}`);
94 });
95
96 $("#inputClipping" + ch.index).on("change", function (e) {
97 $("#inputClipping" + ch.index + "-label").text(JSON.stringify(e.value.newValue));
98 });
99
100 $("#inputClipping" + ch.index).on("slideStop", function (e) {
101 // Assign input's value to clipping
102 var channelROptions = channelRenderingOptions[ch.index];
103 var value = e.value;
104 channelROptions.clipping = value;
105 slideLoader.mainViewport.setChannelRenderingOptions(channelROptions);
106 $("#inputClipping" + ch.index + "-label").text(JSON.stringify(value));
107 console.log(`Clipping for channel ${ch.index}: ${JSON.stringify(value)}`);
108 });
109
110 $("#inputGamma" + ch.index).on("input", function (e) {
111 $("#inputGamma" + ch.index + "-label").text(parseFloat(e.target.value).toFixed(2));
112 });
113
114 $("#inputGamma" + ch.index).on("change", function (e) {
115 // Assign input's value to gamma
116 var channelROptions = channelRenderingOptions[ch.index];
117 channelROptions.gamma = parseFloat(e.target.value);
118 slideLoader.mainViewport.setChannelRenderingOptions(channelROptions);
119 $("#inputGamma" + ch.index + "-label").text(channelROptions.gamma.toFixed(2));
120 console.log(`Gamma for channel ${ch.index}: ${channelROptions.gamma.toFixed(2)}`);
121 });
122 });
123 });
124
125 // Listen for the slide info error event by slide loader
126 slideLoader.listen(PMA.UI.Components.Events.SlideInfoError, function (args) {
127 console.log("Slide info error");
128 console.log(args);
129 });
130
131 // Load the image with the context
132 slideLoader.load(serverUrl, imagePath);
133
134 $("#reset-btn").on("click", function () {
135 initialChannelRenderingOptions.forEach(ch => {
136 // Assign initial values to viewport
137 ch.color = ch.defaultColor;
138 ch.gamma = 1.0;
139 ch.clipping = [0, 100];
140 slideLoader.mainViewport.setChannelRenderingOptions(ch);
141
142 // Assign initial values to inputs
143 $("#inputColor" + ch.index).val(ch.defaultColor.replace("ff", "#"));
144 $("#inputClipping" + ch.index).bootstrapSlider("setValue", [0, 100]);
145 $("#inputGamma" + ch.index).val(ch.gamma.toFixed(2));
146 $("#inputColor" + ch.index + "-label").text(ch.defaultColor.replace("ff", "#"));
147 $("#inputClipping" + ch.index + "-label").text("[" + ch.clipping + "]");
148 $("#inputGamma" + ch.index + "-label").text(ch.gamma.toFixed(2));
149
150 if (initialActiveChannels.includes(ch.index)) {
151 $("#inputActive" + ch.index).prop('checked', true);
152 $("#inputColor" + ch.index).attr("disabled", false);
153 $("#inputGamma" + ch.index).attr("disabled", false);
154 $("#inputClipping" + ch.index).bootstrapSlider("enable");
155 } else {
156 $("#inputColor" + ch.index).attr("disabled", true);
157 $("#inputGamma" + ch.index).attr("disabled", true);
158 $("#inputClipping" + ch.index).bootstrapSlider("disable");
159 }
160 });
161
162 slideLoader.mainViewport.setActiveChannels(initialActiveChannels);
163
164 console.log("Values reverted to initial");
165 })
166});