comparison Sphinx/source/plugins/python.rst @ 703:a589668768d7

moving python samples in separate files (2)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 11 Jun 2021 10:07:12 +0200
parents 6e02cd89eb6a
children ba2403ebd4b7
comparison
equal deleted inserted replaced
702:6e02cd89eb6a 703:a589668768d7
213 ------- 213 -------
214 214
215 Extending the REST API 215 Extending the REST API
216 ...................... 216 ......................
217 217
218 .. highlight:: python
219
220 Here is a basic Python script that registers two new routes in the 218 Here is a basic Python script that registers two new routes in the
221 REST API:: 219 REST API:
222 220
223 import orthanc 221 .. literalinclude:: python/extending-rest-api.py
224 import pprint 222 :language: python
225
226 def OnRest(output, uri, **request):
227 pprint.pprint(request)
228 print('Accessing uri: %s' % uri)
229 output.AnswerBuffer('ok\n', 'text/plain')
230
231 orthanc.RegisterRestCallback('/(to)(t)o', OnRest)
232 orthanc.RegisterRestCallback('/tata', OnRest)
233 223
234 .. highlight:: json 224 .. highlight:: json
235 225
236 Here is the associated minimal configuration file for Orthanc 226 Here is the associated minimal configuration file for Orthanc
237 (provided the Python script is saved as ``rest.py``):: 227 (provided the Python script is saved as ``rest.py``)::
253 .. _python-changes: 243 .. _python-changes:
254 244
255 Listening to changes 245 Listening to changes
256 .................... 246 ....................
257 247
258 .. highlight:: python 248 This sample uploads a DICOM file as soon as Orthanc is started:
259 249
260 This sample uploads a DICOM file as soon as Orthanc is started:: 250 .. literalinclude:: python/listening-changes.py
261 251 :language: python
262 import orthanc
263
264 def OnChange(changeType, level, resource):
265 if changeType == orthanc.ChangeType.ORTHANC_STARTED:
266 print('Started')
267
268 with open('/tmp/sample.dcm', 'rb') as f:
269 orthanc.RestApiPost('/instances', f.read())
270
271 elif changeType == orthanc.ChangeType.ORTHANC_STOPPED:
272 print('Stopped')
273
274 elif changeType == orthanc.ChangeType.NEW_INSTANCE:
275 print('A new instance was uploaded: %s' % resource)
276
277 orthanc.RegisterOnChangeCallback(OnChange)
278
279 252
280 253
281 .. warning:: 254 .. warning::
282 In releases <= 3.0 of the Python plugin, deadlocks might emerge if 255 In releases <= 3.0 of the Python plugin, deadlocks might emerge if
283 you call other core primitives of Orthanc (such as the REST API) in 256 you call other core primitives of Orthanc (such as the REST API) in
287 As a **temporary workaround** against such deadlocks in releases <= 260 As a **temporary workaround** against such deadlocks in releases <=
288 3.0, if you have to call other primitives of Orthanc, you should make 261 3.0, if you have to call other primitives of Orthanc, you should make
289 these calls in a separate thread, passing the pending events to be 262 these calls in a separate thread, passing the pending events to be
290 processed through a message queue. Here is the template of a possible 263 processed through a message queue. Here is the template of a possible
291 solution to postpone such deadlocks as much as possible by relying on 264 solution to postpone such deadlocks as much as possible by relying on
292 the multithreading primitives of Python:: 265 the multithreading primitives of Python:
293 266
294 import orthanc 267 .. literalinclude:: python/changes-deadlock-3.0.py
295 import threading 268 :language: python
296
297 def OnChange(changeType, level, resource):
298 # One can safely invoke the "orthanc" module in this function
299 orthanc.LogWarning("Hello world")
300
301 def _OnChange(changeType, level, resource):
302 # Invoke the actual "OnChange()" function in a separate thread
303 t = threading.Timer(0, function = OnChange, args = (changeType, level, resource))
304 t.start()
305
306 orthanc.RegisterOnChangeCallback(_OnChange)
307
308 269
309 Beware that **this workaround is imperfect** and deadlocks have been 270 Beware that **this workaround is imperfect** and deadlocks have been
310 observed even if using it! Make sure to upgrade your plugin to solve 271 observed even if using it! Make sure to upgrade your plugin to solve
311 this issue for good. Note that this temporary workaround is not needed 272 this issue for good. Note that this temporary workaround is not needed
312 in releases >= 3.1 of the plugin. 273 in releases >= 3.1 of the plugin.
314 275
315 276
316 Accessing the content of a new instance 277 Accessing the content of a new instance
317 ....................................... 278 .......................................
318 279
319 .. highlight:: python 280 .. literalinclude:: python/accessing-new-instance.py
320 281 :language: python
321 ::
322
323 import orthanc
324 import json
325 import pprint
326
327 def OnStoredInstance(dicom, instanceId):
328 print('Received instance %s of size %d (transfer syntax %s, SOP class UID %s)' % (
329 instanceId, dicom.GetInstanceSize(),
330 dicom.GetInstanceMetadata('TransferSyntax'),
331 dicom.GetInstanceMetadata('SopClassUid')))
332
333 # Print the origin information
334 if dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.DICOM_PROTOCOL:
335 print('This instance was received through the DICOM protocol')
336 elif dicom.GetInstanceOrigin() == orthanc.InstanceOrigin.REST_API:
337 print('This instance was received through the REST API')
338
339 # Print the DICOM tags
340 pprint.pprint(json.loads(dicom.GetInstanceSimplifiedJson()))
341
342 orthanc.RegisterOnStoredInstanceCallback(OnStoredInstance)
343
344 282
345 .. warning:: 283 .. warning::
346 Your callback function will be called synchronously with 284 Your callback function will be called synchronously with
347 the core of Orthanc. This implies that deadlocks might emerge if 285 the core of Orthanc. This implies that deadlocks might emerge if
348 you call other core primitives of Orthanc in your callback (such 286 you call other core primitives of Orthanc in your callback (such
355 293
356 294
357 Calling pydicom 295 Calling pydicom
358 ............... 296 ...............
359 297
360 .. highlight:: python
361
362 Here is a sample Python plugin that registers a REST callback to dump 298 Here is a sample Python plugin that registers a REST callback to dump
363 the content of the dataset of one given DICOM instance stored in 299 the content of the dataset of one given DICOM instance stored in
364 Orthanc, using `pydicom <https://pydicom.github.io/>`__:: 300 Orthanc, using `pydicom <https://pydicom.github.io/>`__:
365 301
366 import io 302 .. literalinclude:: python/pydicom.py
367 import orthanc 303 :language: python
368 import pydicom
369
370 def DecodeInstance(output, uri, **request):
371 if request['method'] == 'GET':
372 # Retrieve the instance ID from the regular expression (*)
373 instanceId = request['groups'][0]
374 # Get the content of the DICOM file
375 f = orthanc.GetDicomForInstance(instanceId)
376 # Parse it using pydicom
377 dicom = pydicom.dcmread(io.BytesIO(f))
378 # Return a string representation the dataset to the caller
379 output.AnswerBuffer(str(dicom), 'text/plain')
380 else:
381 output.SendMethodNotAllowed('GET')
382
383 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
384 304
385 .. highlight:: bash 305 .. highlight:: bash
386 306
387 This can be called as follows:: 307 This callback can be called as follows::
388 308
389 $ curl http://localhost:8042/pydicom/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5 309 $ curl http://localhost:8042/pydicom/19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5
390 310
391 311
392 Auto-routing studies 312 Auto-routing studies
393 .................... 313 ....................
394 314
395 .. highlight:: python
396
397 Here is a sample Python plugin that routes any :ref:`stable study 315 Here is a sample Python plugin that routes any :ref:`stable study
398 <stable-resources>` to a modality named ``samples`` (as declared in the 316 <stable-resources>` to a modality named ``samples`` (as declared in the
399 ``DicomModalities`` configuration option):: 317 ``DicomModalities`` configuration option):
400 318
401 import orthanc 319 .. literalinclude:: python/autorouting-1.py
402 320 :language: python
403 def OnChange(changeType, level, resourceId):
404 if changeType == orthanc.ChangeType.STABLE_STUDY:
405 print('Stable study: %s' % resourceId)
406 orthanc.RestApiPost('/modalities/sample/store', resourceId)
407
408 orthanc.RegisterOnChangeCallback(OnChange)
409
410 321
411 Note that, if you want to use an orthanc plugin to transfer the study, 322 Note that, if you want to use an orthanc plugin to transfer the study,
412 you should use the ``RestApiPostAfterPlugins()`` method:: 323 you should use the ``RestApiPostAfterPlugins()`` method:
413 324
414 import orthanc 325 .. literalinclude:: python/autorouting-2.py
415 326 :language: python
416 def OnChange(changeType, level, resourceId): 327
417 if changeType == orthanc.ChangeType.STABLE_STUDY:
418 print('Stable study: %s' % resourceId)
419 orthanc.RestApiPostAfterPlugins('/dicom-web/servers/sample/store', resourceId)
420
421 orthanc.RegisterOnChangeCallback(OnChange)
422
423 328
424 Rendering a thumbnail using PIL/Pillow 329 Rendering a thumbnail using PIL/Pillow
425 ...................................... 330 ......................................
426 331
427 .. highlight:: python 332 .. literalinclude:: python/pil.py
428 333 :language: python
429 ::
430
431 from PIL import Image
432 import io
433 import orthanc
434
435 def DecodeInstance(output, uri, **request):
436 if request['method'] == 'GET':
437 # Retrieve the instance ID from the regular expression (*)
438 instanceId = request['groups'][0]
439
440 # Render the instance, then open it in Python using PIL/Pillow
441 png = orthanc.RestApiGet('/instances/%s/rendered' % instanceId)
442 image = Image.open(io.BytesIO(png))
443
444 # Downsize the image as a 64x64 thumbnail
445 image.thumbnail((64, 64), Image.ANTIALIAS)
446
447 # Save the thumbnail as JPEG, then send the buffer to the caller
448 jpeg = io.BytesIO()
449 image.save(jpeg, format = "JPEG", quality = 80)
450 jpeg.seek(0)
451 output.AnswerBuffer(jpeg.read(), 'text/plain')
452
453 else:
454 output.SendMethodNotAllowed('GET')
455
456 orthanc.RegisterRestCallback('/pydicom/(.*)', DecodeInstance) # (*)
457 334
458 335
459 .. _python-introspection: 336 .. _python-introspection:
460 337
461 Inspecting the available API 338 Inspecting the available API
462 ............................ 339 ............................
463 340
464 .. highlight:: python
465
466 Thanks to Python's introspection primitives, it is possible to inspect 341 Thanks to Python's introspection primitives, it is possible to inspect
467 the API of the ``orthanc`` module in order to dump all the available 342 the API of the ``orthanc`` module in order to dump all the available
468 features:: 343 features:
469 344
470 import inspect 345 .. literalinclude:: python/inspect-api.py
471 import numbers 346 :language: python
472 import orthanc 347
473 348
474 # Loop over the members of the "orthanc" module
475 for (name, obj) in inspect.getmembers(orthanc):
476 if inspect.isroutine(obj):
477 print('Function %s():\n Documentation: %s\n' % (name, inspect.getdoc(obj)))
478
479 elif inspect.isclass(obj):
480 print('Class %s:\n Documentation: %s' % (name, inspect.getdoc(obj)))
481
482 # Loop over the members of the class
483 for (subname, subobj) in inspect.getmembers(obj):
484 if isinstance(subobj, numbers.Number):
485 print(' - Enumeration value %s: %s' % (subname, subobj))
486 elif (not subname.startswith('_') and
487 inspect.ismethoddescriptor(subobj)):
488 print(' - Method %s(): %s' % (subname, inspect.getdoc(subobj)))
489 print('')
490
491
492 .. _python-scheduler: 349 .. _python-scheduler:
493 350
494 Scheduling a task for periodic execution 351 Scheduling a task for periodic execution
495 ........................................ 352 ........................................
496 353