comparison OrthancServer/Plugins/Samples/Basic/Plugin.c @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Plugins/Samples/Basic/Plugin.c@8aeb0c6e4628
children d9473bd5ed43
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include <orthanc/OrthancCPlugin.h>
23
24 #include <string.h>
25 #include <stdio.h>
26
27 static OrthancPluginContext* context = NULL;
28
29 static OrthancPluginErrorCode customError;
30
31
32 OrthancPluginErrorCode Callback1(OrthancPluginRestOutput* output,
33 const char* url,
34 const OrthancPluginHttpRequest* request)
35 {
36 char buffer[1024];
37 uint32_t i;
38
39 if (request->method != OrthancPluginHttpMethod_Get)
40 {
41 /**
42 * NB: Calling "OrthancPluginSendMethodNotAllowed(context, output,
43 * "GET");" is preferable. This is a sample to demonstrate
44 * "OrthancPluginSetHttpErrorDetails()".
45 **/
46 OrthancPluginSetHttpErrorDetails(context, output, "This Callback1() can only be used by a GET call", 1 /* log */);
47 return OrthancPluginErrorCode_ParameterOutOfRange;
48 }
49
50 sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, (const char*) request->body);
51 OrthancPluginLogWarning(context, buffer);
52
53 OrthancPluginSetCookie(context, output, "hello", "world");
54 OrthancPluginAnswerBuffer(context, output, buffer, strlen(buffer), "text/plain");
55
56 OrthancPluginLogWarning(context, "");
57
58 for (i = 0; i < request->groupsCount; i++)
59 {
60 sprintf(buffer, " REGEX GROUP %d = [%s]", i, request->groups[i]);
61 OrthancPluginLogWarning(context, buffer);
62 }
63
64 OrthancPluginLogWarning(context, "");
65
66 for (i = 0; i < request->getCount; i++)
67 {
68 sprintf(buffer, " GET [%s] = [%s]", request->getKeys[i], request->getValues[i]);
69 OrthancPluginLogWarning(context, buffer);
70 }
71
72 OrthancPluginLogWarning(context, "");
73
74 for (i = 0; i < request->headersCount; i++)
75 {
76 sprintf(buffer, " HEADERS [%s] = [%s]", request->headersKeys[i], request->headersValues[i]);
77 OrthancPluginLogWarning(context, buffer);
78 }
79
80 OrthancPluginLogWarning(context, "");
81
82 return OrthancPluginErrorCode_Success;
83 }
84
85
86 OrthancPluginErrorCode Callback2(OrthancPluginRestOutput* output,
87 const char* url,
88 const OrthancPluginHttpRequest* request)
89 {
90 /* Answer with a sample 16bpp image. */
91
92 uint16_t buffer[256 * 256];
93 uint32_t x, y, value;
94
95 if (request->method != OrthancPluginHttpMethod_Get)
96 {
97 OrthancPluginSendMethodNotAllowed(context, output, "GET");
98 }
99 else
100 {
101 value = 0;
102 for (y = 0; y < 256; y++)
103 {
104 for (x = 0; x < 256; x++, value++)
105 {
106 buffer[value] = value;
107 }
108 }
109
110 OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale16,
111 256, 256, sizeof(uint16_t) * 256, buffer);
112 }
113
114 return OrthancPluginErrorCode_Success;
115 }
116
117
118 OrthancPluginErrorCode Callback3(OrthancPluginRestOutput* output,
119 const char* url,
120 const OrthancPluginHttpRequest* request)
121 {
122 if (request->method != OrthancPluginHttpMethod_Get)
123 {
124 OrthancPluginSendMethodNotAllowed(context, output, "GET");
125 }
126 else
127 {
128 OrthancPluginMemoryBuffer dicom;
129 if (!OrthancPluginGetDicomForInstance(context, &dicom, request->groups[0]))
130 {
131 /* No error, forward the DICOM file */
132 OrthancPluginAnswerBuffer(context, output, dicom.data, dicom.size, "application/dicom");
133
134 /* Free memory */
135 OrthancPluginFreeMemoryBuffer(context, &dicom);
136 }
137 }
138
139 return OrthancPluginErrorCode_Success;
140 }
141
142
143 OrthancPluginErrorCode Callback4(OrthancPluginRestOutput* output,
144 const char* url,
145 const OrthancPluginHttpRequest* request)
146 {
147 /* Answer with a sample 8bpp image. */
148
149 uint8_t buffer[256 * 256];
150 uint32_t x, y, value;
151
152 if (request->method != OrthancPluginHttpMethod_Get)
153 {
154 OrthancPluginSendMethodNotAllowed(context, output, "GET");
155 }
156 else
157 {
158 value = 0;
159 for (y = 0; y < 256; y++)
160 {
161 for (x = 0; x < 256; x++, value++)
162 {
163 buffer[value] = x;
164 }
165 }
166
167 OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale8,
168 256, 256, 256, buffer);
169 }
170
171 return OrthancPluginErrorCode_Success;
172 }
173
174
175 OrthancPluginErrorCode Callback5(OrthancPluginRestOutput* output,
176 const char* url,
177 const OrthancPluginHttpRequest* request)
178 {
179 /**
180 * Demonstration the difference between the
181 * "OrthancPluginRestApiXXX()" and the
182 * "OrthancPluginRestApiXXXAfterPlugins()" mechanisms to forward
183 * REST calls.
184 *
185 * # curl http://localhost:8042/forward/built-in/system
186 * # curl http://localhost:8042/forward/plugins/system
187 * # curl http://localhost:8042/forward/built-in/plugin/image
188 * => FAILURE (because the "/plugin/image" URI is implemented by this plugin)
189 * # curl http://localhost:8042/forward/plugins/plugin/image => SUCCESS
190 **/
191
192 OrthancPluginMemoryBuffer tmp;
193 int isBuiltIn, error;
194
195 if (request->method != OrthancPluginHttpMethod_Get)
196 {
197 OrthancPluginSendMethodNotAllowed(context, output, "GET");
198 return OrthancPluginErrorCode_Success;
199 }
200
201 isBuiltIn = strcmp("plugins", request->groups[0]);
202
203 if (isBuiltIn)
204 {
205 error = OrthancPluginRestApiGet(context, &tmp, request->groups[1]);
206 }
207 else
208 {
209 error = OrthancPluginRestApiGetAfterPlugins(context, &tmp, request->groups[1]);
210 }
211
212 if (error)
213 {
214 return OrthancPluginErrorCode_InternalError;
215 }
216 else
217 {
218 OrthancPluginAnswerBuffer(context, output, tmp.data, tmp.size, "application/octet-stream");
219 OrthancPluginFreeMemoryBuffer(context, &tmp);
220 return OrthancPluginErrorCode_Success;
221 }
222 }
223
224
225 OrthancPluginErrorCode CallbackCreateDicom(OrthancPluginRestOutput* output,
226 const char* url,
227 const OrthancPluginHttpRequest* request)
228 {
229 const char* pathLocator = "\"Path\" : \"";
230 char info[1024];
231 char *id, *eos;
232 OrthancPluginMemoryBuffer tmp;
233
234 if (request->method != OrthancPluginHttpMethod_Post)
235 {
236 OrthancPluginSendMethodNotAllowed(context, output, "POST");
237 }
238 else
239 {
240 /* Make POST request to create a new DICOM instance */
241 sprintf(info, "{\"PatientName\":\"Test\"}");
242 OrthancPluginRestApiPost(context, &tmp, "/tools/create-dicom", info, strlen(info));
243
244 /**
245 * Recover the ID of the created instance is constructed by a
246 * quick-and-dirty parsing of a JSON string.
247 **/
248 id = strstr((char*) tmp.data, pathLocator) + strlen(pathLocator);
249 eos = strchr(id, '\"');
250 eos[0] = '\0';
251
252 /* Delete the newly created DICOM instance. */
253 OrthancPluginRestApiDelete(context, id);
254 OrthancPluginFreeMemoryBuffer(context, &tmp);
255
256 /* Set some cookie */
257 OrthancPluginSetCookie(context, output, "hello", "world");
258
259 /* Set some HTTP header */
260 OrthancPluginSetHttpHeader(context, output, "Cache-Control", "max-age=0, no-cache");
261
262 OrthancPluginAnswerBuffer(context, output, "OK\n", 3, "text/plain");
263 }
264
265 return OrthancPluginErrorCode_Success;
266 }
267
268
269 void DicomWebBinaryCallback(
270 OrthancPluginDicomWebNode* node,
271 OrthancPluginDicomWebSetBinaryNode setter,
272 uint32_t levelDepth,
273 const uint16_t* levelTagGroup,
274 const uint16_t* levelTagElement,
275 const uint32_t* levelIndex,
276 uint16_t tagGroup,
277 uint16_t tagElement,
278 OrthancPluginValueRepresentation vr)
279 {
280 setter(node, OrthancPluginDicomWebBinaryMode_BulkDataUri, "HelloURI");
281 }
282
283
284 OrthancPluginErrorCode OnStoredCallback(const OrthancPluginDicomInstance* instance,
285 const char* instanceId)
286 {
287 char buffer[256];
288 FILE* fp;
289 char* json;
290 static int first = 1;
291
292 sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from origin %d (AET %s)",
293 (int) OrthancPluginGetInstanceSize(context, instance), instanceId,
294 OrthancPluginGetInstanceOrigin(context, instance),
295 OrthancPluginGetInstanceRemoteAet(context, instance));
296
297 OrthancPluginLogWarning(context, buffer);
298
299 fp = fopen("PluginReceivedInstance.dcm", "wb");
300 fwrite(OrthancPluginGetInstanceData(context, instance),
301 OrthancPluginGetInstanceSize(context, instance), 1, fp);
302 fclose(fp);
303
304 json = OrthancPluginGetInstanceSimplifiedJson(context, instance);
305 if (first)
306 {
307 printf("[%s]\n", json);
308 }
309 OrthancPluginFreeString(context, json);
310
311 if (OrthancPluginHasInstanceMetadata(context, instance, "ReceptionDate"))
312 {
313 printf("Received on [%s]\n", OrthancPluginGetInstanceMetadata(context, instance, "ReceptionDate"));
314 }
315 else
316 {
317 OrthancPluginLogError(context, "Instance has no reception date, should never happen!");
318 }
319
320 json = OrthancPluginEncodeDicomWebXml(context,
321 OrthancPluginGetInstanceData(context, instance),
322 OrthancPluginGetInstanceSize(context, instance),
323 DicomWebBinaryCallback);
324 if (first)
325 {
326 printf("[%s]\n", json);
327 first = 0; /* Only print the first DICOM instance */
328 }
329 OrthancPluginFreeString(context, json);
330
331
332 return OrthancPluginErrorCode_Success;
333 }
334
335
336 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
337 OrthancPluginResourceType resourceType,
338 const char* resourceId)
339 {
340 char info[1024];
341
342 OrthancPluginMemoryBuffer tmp;
343 memset(&tmp, 0, sizeof(tmp));
344
345 sprintf(info, "Change %d on resource %s of type %d", changeType,
346 (resourceId == NULL ? "<none>" : resourceId), resourceType);
347 OrthancPluginLogWarning(context, info);
348
349 switch (changeType)
350 {
351 case OrthancPluginChangeType_NewInstance:
352 {
353 sprintf(info, "/instances/%s/metadata/AnonymizedFrom", resourceId);
354 if (OrthancPluginRestApiGet(context, &tmp, info) == 0)
355 {
356 sprintf(info, " Instance %s comes from the anonymization of instance", resourceId);
357 strncat(info, (const char*) tmp.data, tmp.size);
358 OrthancPluginLogWarning(context, info);
359 OrthancPluginFreeMemoryBuffer(context, &tmp);
360 }
361
362 break;
363 }
364
365 case OrthancPluginChangeType_OrthancStarted:
366 {
367 OrthancPluginSetMetricsValue(context, "sample_started", 1, OrthancPluginMetricsType_Default);
368
369 /* Make REST requests to the built-in Orthanc API */
370 OrthancPluginRestApiGet(context, &tmp, "/changes");
371 OrthancPluginFreeMemoryBuffer(context, &tmp);
372 OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1");
373 OrthancPluginFreeMemoryBuffer(context, &tmp);
374
375 /* Play with PUT by defining a new target modality. */
376 sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
377 OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
378
379 break;
380 }
381
382 case OrthancPluginChangeType_OrthancStopped:
383 OrthancPluginLogWarning(context, "Orthanc has stopped");
384 break;
385
386 default:
387 break;
388 }
389
390 return OrthancPluginErrorCode_Success;
391 }
392
393
394 int32_t FilterIncomingHttpRequest(OrthancPluginHttpMethod method,
395 const char* uri,
396 const char* ip,
397 uint32_t headersCount,
398 const char* const* headersKeys,
399 const char* const* headersValues)
400 {
401 uint32_t i;
402
403 if (headersCount > 0)
404 {
405 OrthancPluginLogInfo(context, "HTTP headers of an incoming REST request:");
406 for (i = 0; i < headersCount; i++)
407 {
408 char info[1024];
409 sprintf(info, " %s: %s", headersKeys[i], headersValues[i]);
410 OrthancPluginLogInfo(context, info);
411 }
412 }
413
414 if (method == OrthancPluginHttpMethod_Get ||
415 method == OrthancPluginHttpMethod_Post)
416 {
417 return 1; /* Allowed */
418 }
419 else
420 {
421 return 0; /* Only allow GET and POST requests */
422 }
423 }
424
425
426 static void RefreshMetrics()
427 {
428 static unsigned int count = 0;
429 OrthancPluginSetMetricsValue(context, "sample_counter",
430 (float) (count++), OrthancPluginMetricsType_Default);
431 }
432
433
434 static int32_t FilterIncomingDicomInstance(const OrthancPluginDicomInstance* instance)
435 {
436 char buf[1024];
437 char* s;
438 int32_t hasPixelData;
439
440 s = OrthancPluginGetInstanceTransferSyntaxUid(context, instance);
441 sprintf(buf, "Incoming transfer syntax: %s", s);
442 OrthancPluginFreeString(context, s);
443 OrthancPluginLogWarning(context, buf);
444
445 hasPixelData = OrthancPluginHasInstancePixelData(context, instance);
446 sprintf(buf, "Incoming has pixel data: %d", hasPixelData);
447 OrthancPluginLogWarning(context, buf);
448
449 /* Reject all instances without pixel data */
450 return hasPixelData;
451 }
452
453
454 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
455 {
456 char info[1024], *s;
457 int counter, i;
458 OrthancPluginDictionaryEntry entry;
459
460 context = c;
461 OrthancPluginLogWarning(context, "Sample plugin is initializing");
462
463 /* Check the version of the Orthanc core */
464 if (OrthancPluginCheckVersion(c) == 0)
465 {
466 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
467 c->orthancVersion,
468 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
469 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
470 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
471 OrthancPluginLogError(context, info);
472 return -1;
473 }
474
475 /* Print some information about Orthanc */
476 sprintf(info, "The version of Orthanc is '%s'", context->orthancVersion);
477 OrthancPluginLogWarning(context, info);
478
479 s = OrthancPluginGetOrthancPath(context);
480 sprintf(info, " Path to Orthanc: %s", s);
481 OrthancPluginLogWarning(context, info);
482 OrthancPluginFreeString(context, s);
483
484 s = OrthancPluginGetOrthancDirectory(context);
485 sprintf(info, " Directory of Orthanc: %s", s);
486 OrthancPluginLogWarning(context, info);
487 OrthancPluginFreeString(context, s);
488
489 s = OrthancPluginGetConfiguration(context);
490 sprintf(info, " Content of the configuration file:\n");
491 OrthancPluginLogWarning(context, info);
492 OrthancPluginLogWarning(context, s);
493 OrthancPluginFreeString(context, s);
494
495 /* Print the command-line arguments of Orthanc */
496 counter = OrthancPluginGetCommandLineArgumentsCount(context);
497 for (i = 0; i < counter; i++)
498 {
499 s = OrthancPluginGetCommandLineArgument(context, i);
500 sprintf(info, " Command-line argument %d: \"%s\"", i, s);
501 OrthancPluginLogWarning(context, info);
502 OrthancPluginFreeString(context, s);
503 }
504
505 /* Register the callbacks */
506 OrthancPluginRegisterRestCallback(context, "/(plu.*)/hello", Callback1);
507 OrthancPluginRegisterRestCallback(context, "/plu.*/image", Callback2);
508 OrthancPluginRegisterRestCallback(context, "/plugin/instances/([^/]+)/info", Callback3);
509 OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
510 OrthancPluginRegisterRestCallback(context, "/forward/(built-in)(/.+)", Callback5);
511 OrthancPluginRegisterRestCallback(context, "/forward/(plugins)(/.+)", Callback5);
512 OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom);
513
514 OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback);
515 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
516 OrthancPluginRegisterIncomingHttpRequestFilter(context, FilterIncomingHttpRequest);
517 OrthancPluginRegisterRefreshMetricsCallback(context, RefreshMetrics);
518 OrthancPluginRegisterIncomingDicomInstanceFilter(context, FilterIncomingDicomInstance);
519
520
521 /* Declare several properties of the plugin */
522 OrthancPluginSetRootUri(context, "/plugin/hello");
523 OrthancPluginSetDescription(context, "This is the description of the sample plugin that can be seen in Orthanc Explorer.");
524 OrthancPluginExtendOrthancExplorer(context, "alert('Hello Orthanc! From sample plugin with love.');");
525
526 customError = OrthancPluginRegisterErrorCode(context, 4, 402, "Hello world");
527
528 OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA,
529 "ValidationExpiryDate", 1, 1);
530
531 OrthancPluginLookupDictionary(context, &entry, "ValidationExpiryDate");
532 OrthancPluginLookupDictionary(context, &entry, "0010-0010");
533
534 return 0;
535 }
536
537
538 ORTHANC_PLUGINS_API void OrthancPluginFinalize()
539 {
540 OrthancPluginLogWarning(context, "Sample plugin is finalizing");
541 }
542
543
544 ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
545 {
546 return "sample";
547 }
548
549
550 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
551 {
552 return "1.0";
553 }
554