Mercurial > hg > orthanc-book
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 |