PMA.UI Examples 2.43.3by Pathomation

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 html>
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});