comparison Sphinx/source/plugins/python.rst @ 378:16dc3561b41e

Filtering and returning metadata using Python
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 08 Apr 2020 08:49:33 +0200
parents 766fe39fdf35
children c9fe3d0d0fa1
comparison
equal deleted inserted replaced
375:766fe39fdf35 378:16dc3561b41e
426 orthanc.LogWarning("Stopping the scheduler") 426 orthanc.LogWarning("Stopping the scheduler")
427 TIMER.cancel() 427 TIMER.cancel()
428 428
429 orthanc.RegisterOnChangeCallback(OnChange) 429 orthanc.RegisterOnChangeCallback(OnChange)
430 430
431 431
432 .. _python-metadata:
433
434 Filtering and returning metadata
435 ................................
436
437 Besides the main DICOM tags, Orthanc associates some metadata to each
438 resource it stores (this includes the date of last update, the
439 transfer syntax, the remote AET...). People are often interested in
440 getting such metadata while calling the ``/tools/find`` route in the
441 :ref:`REST API <rest-find>`, or even in filtering this metadata the
442 same way they look for DICOM tags.
443
444 This feature is not built in the core of Orthanc, as metadata is not
445 indexed in the Orthanc database, contrarily to the main DICOM
446 tags. Filtering metadata requires a linear search over all the
447 matching resources, which induces a cost in the performance.
448
449 .. highlight:: python
450
451 Nevertheless, here is a full sample Python script that overwrites the
452 ``/tools/find`` route in order to give access to metadata::
453
454 import json
455 import orthanc
456 import re
457
458 # Get the path in the REST API to the given resource that was returned
459 # by a call to "/tools/find"
460 def GetPath(resource):
461 if resource['Type'] == 'Patient':
462 return '/patients/%s' % resource['ID']
463 elif resource['Type'] == 'Study':
464 return '/studies/%s' % resource['ID']
465 elif resource['Type'] == 'Series':
466 return '/series/%s' % resource['ID']
467 elif resource['Type'] == 'Instance':
468 return '/instances/%s' % resource['ID']
469 else:
470 raise Exception('Unknown resource level')
471
472 def FindWithMetadata(output, uri, **request):
473 # The "/tools/find" route expects a POST method
474 if request['method'] != 'POST':
475 output.SendMethodNotAllowed('POST')
476 else:
477 # Parse the query provided by the user, and backup the "Expand" field
478 query = json.loads(request['body'])
479
480 if 'Expand' in query:
481 originalExpand = query['Expand']
482 else:
483 originalExpand = False
484
485 # Call the core "/tools/find" route
486 query['Expand'] = True
487 answers = orthanc.RestApiPost('/tools/find', json.dumps(query))
488
489 # Loop over the matching resources
490 filteredAnswers = []
491 for answer in json.loads(answers):
492 try:
493 # Read the metadata that is associated with the resource
494 metadata = json.loads(orthanc.RestApiGet('%s/metadata?expand' % GetPath(answer)))
495
496 # Check whether the metadata matches the regular expressions
497 # that were provided in the "Metadata" field of the user request
498 isMetadataMatch = True
499 if 'Metadata' in query:
500 for (name, pattern) in query['Metadata'].items():
501 if name in metadata:
502 value = metadata[name]
503 else:
504 value = ''
505
506 if re.match(pattern, value) == None:
507 isMetadataMatch = False
508 break
509
510 # If all the metadata matches the provided regular
511 # expressions, add the resource to the filtered answers
512 if isMetadataMatch:
513 if originalExpand:
514 answer['Metadata'] = metadata
515 filteredAnswers.append(answer)
516 else:
517 filteredAnswers.append(answer['ID'])
518 except:
519 # The resource was deleted since the call to "/tools/find"
520 pass
521
522 # Return the filtered answers in the JSON format
523 output.AnswerBuffer(json.dumps(filteredAnswers, indent = 3), 'application/json')
524
525 orthanc.RegisterRestCallback('/tools/find', FindWithMetadata)
526
527
528 **Warning:** In the sample above, the filtering of the metadata is
529 done using Python's `library for regular expressions
530 <https://docs.python.org/3/library/re.html>`__. It is evidently
531 possible to adapt this script in order to use the DICOM conventions
532 about `attribute matching
533 <http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.html>`__.
534
535 .. highlight:: python
536
537 Here is a sample call to retrieve all the studies that were last
538 updated in 2019 thanks to this Python script::
539
540 $ curl http://localhost:8042/tools/find -d '{"Level":"Study","Query":{},"Expand":true,"Metadata":{"LastUpdate":"^2019.*$"}}'
432 541
433 542
434 Performance and concurrency 543 Performance and concurrency
435 --------------------------- 544 ---------------------------
436 545