Magic wand and brush
expertslide loaderannotationsmagic wandbrush
Console
PMA.UI version: 2.43.3
Annotations example, showing the usage of magic wand and brush to create annotations
annotations_brush_wand.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
17 <!-- Include PMA.UI script & css -->
18 <script src="./pma.ui/pma.ui.js"></script>
19 <link href="./pma.ui/pma.ui.css" type="text/css" rel="stylesheet">
20
21 <!-- Include custom script & css -->
22 <script src="./js/annotations_brush_wand.js"></script>
23 <link href="./css/annotations_brush_wand.css" type="text/css" rel="stylesheet">
24
25 <title>Annotations - Magic wand and brush</title>
26</head>
27
28<body>
29 <div class="container-xl">
30 <div class="row">
31 <div class="col-auto annotations">
32 <div class="btn-group-vertical btn-group-md">
33 <button type="button" class="btn btn-light" disabled data-action="edit" data-type="Wand"
34 title="Magic Wand">
35 <i class="fas fa-magic"></i>
36 </button>
37 <button type="button" class="btn btn-light" disabled data-action="edit" data-type="Brush"
38 title="Brush">
39 <i class="fas fa-paint-brush"></i>
40 </button>
41 <input id="color-picker-native" type='color' value='#78eb10' class="color-picker" />
42 <input id="color-picker-js" type='button' value=' ' class="btn btn-light" style="width: 50px" />
43 <button type="button" class="btn btn-light color-btn" data-color="#0000ff" style="color:#0000ff">
44 <i class='fa fa-square'></i>
45 </button>
46 <button type="button" class="btn btn-light color-btn" data-color="#ff0000" style="color:#ff0000">
47 <i class='fa fa-square'></i>
48 </button>
49 <button type="button" class="btn btn-light color-btn" data-color="#00ff00" style="color:#00ff00">
50 <i class='fa fa-square'></i>
51 </button>
52 </div>
53 <input id="color-picker-hidden" type='hidden' value='#78eb10' class="color-picker" />
54 </div>
55 <div class="col">
56 <div class="row top-row">
57 <div class="col">
58 <div class="btn-group btn-group-sm">
59 <button type="button" class="btn btn-secondary" disabled data-action="delete" data-type
60 title="Delete">
61 <i class="fas fa-times"></i>
62 </button>
63 <button type="button" class="btn btn-secondary" disabled data-action="deleteall" data-type
64 title="Delete all">
65 <i class="fas fa-times-circle"></i>
66 </button>
67 <button type="button" class="btn btn-secondary" disabled data-action="save" data-type
68 title="Save">
69 <i class="fas fa-save"></i>
70 </button>
71 <button type="button" class="btn btn-secondary" disabled id="version" title="Version">
72 <i class="fas fa-info"></i>
73 </button>
74 </div>
75
76 <div class="btn-group btn-group-sm btn-group-toggle" data-toggle="buttons">
77 <button class="btn btn-secondary active" disabled title="Toggle annotation labels">
78 <input id="chkLabels" type="checkbox" autocomplete="off" />
79 <i class="fas fa-font"></i>
80 </button>
81 <button class="btn btn-secondary active" disabled title="Toggle annotation measurements">
82 <input id="chkMeasurements" type="checkbox" autocomplete="off" />
83 <i class="fas fa-tag"></i>
84 </button>
85 <button class="btn btn-secondary" disabled title="Toggle annotations loaded by fingerprint">
86 <input id="chkFingerprint" type="checkbox" autocomplete="off" />
87 <i class="fas fa-id-card"></i>
88 </button>
89 </div>
90
91 <span class="form-group" style="margin-right: 15px;">
92 <input type="text" class="form-control form-control-sm" disabled id="annotation-text"
93 style="display:initial; width:120px" />
94 <span class="badge badge-light">
95 Length
96 <span id="txt-length"> - </span>
97 </span>
98 <span class="badge badge-light" style="margin-left: 10px">
99 Area
100 <span id="txt-area"> - </span>
101 </span>
102 </span>
103
104 <span class="annotation-helper-icon">
105 <i class="fas fa-spinner fa-spin"></i>
106 <span class="badge badge-success"><i class="fas fa-check"></i> Saved</span>
107 <span class="badge badge-error"><i class="fas fa-times"></i> Error</span>
108 </span>
109 </div>
110 </div>
111 <div class="row viewer-row">
112 <div class="col">
113 <!-- The element that will host the slide loader -->
114 <div id="viewer"></div>
115 </div>
116 </div>
117 </div>
118</body>
119
120</html>
annotations_brush_wand.css
1html,
2body
3{
4 height: 100%;
5 padding: 0px;
6 margin: 0px;
7}
8
9body .pma-ui-viewport-container {
10 height: 100%;
11 width: 100%;
12}
13
14.aligned-text {
15 display: inline-block;
16 text-align: right;
17 width: 70px;
18}
19
20.annotation-helper-icon i,
21.annotation-helper-icon span {
22 display: none;
23}
24
25.annotation-helper-icon.loading i {
26 display: initial;
27}
28
29.annotation-helper-icon.loading span {
30 display: none;
31}
32
33.annotation-helper-icon.saved i,
34.annotation-helper-icon.saved span.badge-danger {
35 display: none;
36}
37
38.annotation-helper-icon.saved span.badge-success {
39 display: initial;
40}
41
42.annotation-helper-icon.error i,
43.annotation-helper-icon.error span.badge-success {
44 display: none;
45}
46
47.annotation-helper-icon.error span.badge-danger {
48 display: block;
49}
50
51.annotations {
52 margin: 0 auto;
53 padding: 0;
54}
55.viewer-row {
56 height: calc(100vh - 33px);
57}
58.color-picker{
59 height: 35px;
60 margin-bottom: 1px;
61 border: none;
62}
annotations_brush_wand.js
1// Initial declarations
2var serverUrl = "https://host.pathomation.com/pma.core.2/";
3var serverUsername = "PMA_UI_demo";
4var serverPassword = "PMA_UI_demo";
5var caller = "DemoPortal";
6var slideLoaderElementSelector = "#viewer";
7var imagePath = "Reference/3DHistech/CMU-1.mrxs";
8var context = null;
9var slideLoader = null;
10var annotationManager = null;
11var brushSize = 1000;
12
13function supportsColorPicker() {
14 var colorInput;
15 colorInput = $('<input type="color" value="!" />')[0];
16 return colorInput.type === 'color' && colorInput.value !== '!';
17}
18
19function drawCommands(action, type) {
20 if (action) {
21 if (action === "draw") {
22 var color = $(".color-picker").val();
23
24 var f = annotationManager.getSelection();
25
26 annotationManager.startDrawing({
27 type: type,
28 color: color,
29 fillColor: "rgba(33,44,55,255)",
30 lineThickness: Math.floor(Math.random() * 15) + 1,
31 iconRelativePath: null,
32 feature: type === "MultiPoint" && f.length > 0 ? f[0] : undefined,
33 notes: $("#annotation-text").val() ? $("#annotation-text").val() : " "
34 });
35 } else if (action === "edit") {
36 var f = annotationManager.getSelection();
37
38 annotationManager.enterEditMode({
39 type: type,
40 brushType: 'circle',
41 brushSize: brushSize,
42 iconRelativePath: null,
43 drawMode: true,
44 feature: annotationManager.getSelection()[0],
45 color: $(".color-picker").val(),
46 fillColor: "rgba(33,44,55, " + Math.random() + ")",
47 lineThickness: Math.floor(Math.random() * 15) + 1,
48 notes: $("#annotation-text").val() ? $("#annotation-text").val() : " "
49 });
50 } else if (action === "measure") {
51 if (type === "clear") {
52 slideLoader.mainViewport.stopMeasuring();
53 return;
54 }
55
56 slideLoader.mainViewport.startMeasuring(type);
57 } else if (action === "save") {
58 saveAnnotations();
59 } else if (action === "delete") {
60 var ann = annotationManager.getSelection();
61
62 if (ann && ann.length > 0) {
63 annotationManager.deleteAnnotation(ann[0].getId());
64 }
65 } else if (action === "deleteall") {
66 var allAnnotations = slideLoader.mainViewport.getAnnotations();
67
68 for (var i = 0; i < allAnnotations.length; i++) {
69 annotationManager.deleteAnnotation(allAnnotations[i].getId());
70 }
71 } else if (action === "merge") {
72 annotationManager.mergeSelection();
73 } else if (action === "union") {
74 annotationManager.booleanUnion(annotationManager.getSelection());
75 } else if (action === "difference") {
76 annotationManager.booleanDifference(annotationManager.getSelection()[0], annotationManager.getSelection()[1]);
77 }
78 }
79}
80
81function saveAnnotations(e) {
82 if (e) {
83 var metadata = e.hasOwnProperty("feature") ? e.feature.metaData : (e.hasOwnProperty("length") && e.length !== 0 ? e[0].metaData : null);
84 if (metadata) {
85 metadata.Notes = $("#annotation-text").val() ? $("#annotation-text").val() : " ";
86 }
87 }
88
89 $(".annotation-helper-icon").addClass("loading");
90 annotationManager.saveAnnotations();
91}
92
93jQuery(function () {
94 var jsColorPicker = null;
95 if (supportsColorPicker()) {
96 $("#color-picker-js").remove();
97 $("#color-picker-hidden").remove();
98 } else {
99 $("#color-picker-native").remove();
100 jsColorPicker = new jscolor($("#color-picker-js")[0], {
101 valueElement: $("#color-picker-hidden")[0],
102 hash: true,
103 closable: true,
104 closeText: "Close"
105 });
106 }
107
108 $("button.color-btn").on("click", function (e) {
109 var clr = $(this).data("color");
110 $("#color-picker-native").val(clr);
111 if (jsColorPicker) {
112 jsColorPicker.fromString(clr);
113 }
114 });
115
116 $("button[data-action][data-type], a[data-action][data-type]").on("click", function (e) {
117 e.preventDefault();
118 drawCommands($(this).data("action"), $(this).data("type"));
119 });
120
121 $("#version").on("click", function (e) {
122 PMA.UI.Components.callApiMethod({
123 serverUrl: serverUrl,
124 method: PMA.UI.Components.ApiMethods.GetVersionInfo,
125 httpMethod: "GET",
126 data: {
127 username: serverUsername,
128 password: serverPassword,
129 caller: caller,
130 },
131 success: function (http) {
132 console.log("PMA.core version: " + JSON.parse(http.responseText));
133 },
134 failure: function (http) {
135 console.log(http);
136 },
137 });
138 });
139
140 console.log(`PMA.UI version: ${PMA.UI.getVersion()}`);
141
142 // Create a context
143 context = new PMA.UI.Components.Context({ caller: caller });
144
145 // Add an autologin authentication provider
146 new PMA.UI.Authentication.AutoLogin(context, [{ serverUrl: serverUrl, username: serverUsername, password: serverPassword }]);
147
148 // Create an image loader that will allow us to load images easily
149 slideLoader = new PMA.UI.Components.SlideLoader(context, {
150 element: slideLoaderElementSelector,
151 annotations: {},
152 });
153
154 // Listen for the slide loaded event by the slide loader
155 slideLoader.listen(PMA.UI.Components.Events.SlideLoaded, function (args) {
156 $("button, input").removeAttr("disabled");
157 annotationManager = new PMA.UI.Components.Annotations({
158 context: context,
159 element: null,
160 viewport: slideLoader.mainViewport,
161 serverUrl: args.serverUrl,
162 path: args.path,
163 enabled: true
164 });
165
166 annotationManager.listen(PMA.UI.Components.Events.AnnotationAdded, function (e) {
167 console.log("Annotation added", e.feature);
168 });
169
170 annotationManager.listen(PMA.UI.Components.Events.AnnotationsSelectionChanged, function (e) {
171 if (e) {
172 $("button[data-action='delete']").attr("disabled", false);
173 var metadata = e.hasOwnProperty("feature") ? e.feature.metaData : (e.hasOwnProperty("length") && e.length !== 0 ? e[0].metaData : null);
174 if (metadata) {
175 console.log("selection: ", metadata);
176 $("#annotation-text").val(metadata.Notes);
177 $("#txt-area").html(metadata.FormattedArea || " - ");
178 $("#txt-length").html(metadata.FormattedLength || " - ");
179 } else {
180 $("button[data-action='delete']").attr("disabled", true);
181 $("#annotation-text").val("");
182 $("#txt-area").html(" - ");
183 $("#txt-length").html(" - ");
184 }
185 }
186 });
187
188 annotationManager.listen(PMA.UI.Components.Events.AnnotationsSaved, function (e) {
189 if (e && e.success) {
190 $(".annotation-helper-icon").removeClass("loading").addClass("saved");
191 } else {
192 $(".annotation-helper-icon").removeClass("loading").addClass("error");
193 }
194
195 setTimeout(function () {
196 $(".annotation-helper-icon").removeClass("loading error saved");
197 }, 1000);
198
199 var labels = $("#chkLabels").parent().hasClass("active");
200 var measurements = $("#chkMeasurements").parent().hasClass("active");
201 slideLoader.mainViewport.showAnnotationsLabels(labels, measurements);
202 });
203
204 annotationManager.listen(PMA.UI.Components.Events.AnnotationDrawing, function (e) {
205 // Event called while drawing annotations
206 });
207
208 annotationManager.listen(PMA.UI.Components.Events.AnnotationEditingEnded, function (e) {
209 if (e && e.edit && e.edit.getBrushSize) {
210 brushSize = e.edit.getBrushSize() ?? 1000;
211 }
212 });
213
214 annotationManager.listen(PMA.UI.Components.Events.AnnotationDeleted, function (e) {
215 console.log("Annotation deleted", e.feature);
216 });
217 });
218
219 // Load the image with the context
220 slideLoader.load(serverUrl, imagePath);
221
222 $("#annotation-text").on("focusout", function () {
223 if (annotationManager && annotationManager.getSelection().length !== 0) {
224 var sel = annotationManager.getSelection()[0];
225 if (sel.metaData) {
226 sel.metaData.Notes = $(this).val() ? $(this).val() : " ";
227 annotationManager.saveAnnotations();
228 annotationManager.clearSelection();
229 var labels = $("#chkLabels").parent().hasClass("active");
230 var measurements = $("#chkMeasurements").parent().hasClass("active");
231 slideLoader.mainViewport.showAnnotationsLabels(labels, measurements);
232 }
233 }
234 });
235
236 $("body").on("keydown", function (event) {
237 if (!$(event.target).is('input') && (event.key === "Delete" || event.key === "Del")) {
238 drawCommands("delete", null);
239 }
240 });
241
242 $("#chkLabels").parent().on("click", function () {
243 var labels = !$("#chkLabels").parent().hasClass("active");
244 var measurements = $("#chkMeasurements").parent().hasClass("active");
245 console.log(`Show labels: ${labels} - Show measurements: ${measurements}`);
246 slideLoader.mainViewport.showAnnotationsLabels(labels, measurements);
247 });
248
249 $("#chkMeasurements").parent().on("click", function () {
250 var labels = $("#chkLabels").parent().hasClass("active");
251 var measurements = !$("#chkMeasurements").parent().hasClass("active");
252 console.log(`Show labels: ${labels} - Show measurements: ${measurements}`);
253 slideLoader.mainViewport.showAnnotationsLabels(labels, measurements);
254 });
255
256 $("#chkFingerprint").parent().on("click", function () {
257 var fingerprint = !$("#chkFingerprint").parent().hasClass("active");
258 slideLoader.mainViewport.showAnnotationsByFingerprint(fingerprint);
259 });
260});