Mercurial > hg > orthanc-tests
changeset 678:72f186c739d0 large-queries
merged default -> large-queries
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 05 Sep 2024 18:49:09 +0200 |
parents | 599ff47f609d (current diff) 85c1447fa86b (diff) |
children | db7cf82a881b 60cdae616275 |
files | NewTests/Authorization/test_authorization.py Tests/Tests.py |
diffstat | 28 files changed, 639 insertions(+), 62 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Thu Sep 05 18:46:25 2024 +0200 +++ b/.hgignore Thu Sep 05 18:49:09 2024 +0200 @@ -10,4 +10,7 @@ *~ NewTests/storages/** NewTests/configurations/*.json -.env/ \ No newline at end of file +.env/ +Tests/*.crt +Tests/*.key +Tests/dicom-tls.json \ No newline at end of file
--- a/.hgtags Thu Sep 05 18:46:25 2024 +0200 +++ b/.hgtags Thu Sep 05 18:49:09 2024 +0200 @@ -44,3 +44,4 @@ 86456045ac80545a2eca1fe0d8d7eb337a0b4ceb Orthanc-1.12.0 855c3720902a1dade9accf91571ee6719e0c1eb6 Orthanc-1.12.1 ec657d1a62a6c5eebfe5255a8afe082e92d973c1 Orthanc-1.12.3 +7bfc8992ab8fc44bd811bc60ebf3332303bc87ed Orthanc-1.12.4
--- a/AUTHORS Thu Sep 05 18:46:25 2024 +0200 +++ b/AUTHORS Thu Sep 05 18:49:09 2024 +0200 @@ -14,16 +14,22 @@ 4000 Liege Belgium -* Osimis S.A. <info@osimis.io> +* Osimis S.A. Quai Banning 6 4000 Liege Belgium - http://www.osimis.io/ + +* Orthanc Team SRL <info@orthanc.team> + Rue Joseph Marchal 14 + 4910 Theux + Belgium + https://orthanc.team/ * ICTEAM, UCLouvain Place de l'Universite 1 1348 Ottignies-Louvain-la-Neuve Belgium + https://uclouvain.be/icteam Contributors
--- a/CITATION.cff Thu Sep 05 18:46:25 2024 +0200 +++ b/CITATION.cff Thu Sep 05 18:49:09 2024 +0200 @@ -10,5 +10,5 @@ doi: "10.1007/s10278-018-0082-y" license: "GPL-3.0-or-later" repository-code: "https://orthanc.uclouvain.be/hg/orthanc/" -version: 1.12.3 -date-released: 2024-01-31 +version: 1.12.4 +date-released: 2024-06-05
--- a/GenerateConfigurationForTests.py Thu Sep 05 18:46:25 2024 +0200 +++ b/GenerateConfigurationForTests.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/NewTests/Authorization/auth_service.py Thu Sep 05 18:46:25 2024 +0200 +++ b/NewTests/Authorization/auth_service.py Thu Sep 05 18:49:09 2024 +0200 @@ -47,6 +47,13 @@ authorized_labels=["label_a"], validity=60 ) + elif user_profile_request.token_value == "token-uploader-a": # this use shall be able to upload anything but view only the labeled studies + p = UserProfileResponse( + name="uploader-a", + permissions=["view", "upload"], + authorized_labels=["label_a"], + validity=60 + ) return p
--- a/NewTests/Authorization/test_authorization.py Thu Sep 05 18:46:25 2024 +0200 +++ b/NewTests/Authorization/test_authorization.py Thu Sep 05 18:49:09 2024 +0200 @@ -177,6 +177,10 @@ self.assert_is_forbidden(lambda: o.studies.get_tags(self.label_b_study_id)) self.assert_is_forbidden(lambda: o.studies.get_tags(self.no_label_study_id)) + # user_a shall not be able to upload a study + self.assert_is_forbidden(lambda: o.upload_file(here / "../../Database/Beaufix/IM-0001-0001.dcm")) + self.assert_is_forbidden(lambda: o.upload_files_dicom_web(paths = [here / "../../Database/Beaufix/IM-0001-0001.dcm"])) + # should not raise o.studies.get_tags(self.label_a_study_id) @@ -269,6 +273,32 @@ i = o_admin.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011") + def test_uploader_a(self): + o_admin = OrthancApiClient(self.o._root_url, headers={"user-token-key": "token-admin"}) + o = OrthancApiClient(self.o._root_url, headers={"user-token-key": "token-uploader-a"}) + + if o_admin.is_plugin_version_at_least("authorization", 0, 7, 3): + + # # make sure we can access all these urls (they would throw if not) + system = o.get_system() + + all_labels = o.get_all_labels() + self.assertEqual(1, len(all_labels)) + self.assertEqual("label_a", all_labels[0]) + + # make sure we can access only the label_a studies + self.assert_is_forbidden(lambda: o.studies.get_tags(self.label_b_study_id)) + self.assert_is_forbidden(lambda: o.studies.get_tags(self.no_label_study_id)) + + # uploader-a shall be able to upload a study + instances_ids = o.upload_file(here / "../../Database/Beaufix/IM-0001-0001.dcm") + o_admin.instances.delete(orthanc_ids=instances_ids) + + # uploader-a shall be able to upload a study through DICOMWeb too + o.upload_files_dicom_web(paths = [here / "../../Database/Beaufix/IM-0001-0001.dcm"]) + o_admin.instances.delete(orthanc_ids=instances_ids) + + def test_resource_token(self): o = OrthancApiClient(self.o._root_url, headers={"resource-token-key": "token-a-study"})
--- a/NewTests/Concurrency/test_transfer.py Thu Sep 05 18:46:25 2024 +0200 +++ b/NewTests/Concurrency/test_transfer.py Thu Sep 05 18:49:09 2024 +0200 @@ -76,9 +76,9 @@ for i in range(0, repeat_count): oa.transfers.send(target_peer='b', - resources_ids=all_studies_ids, - resource_type=ResourceType.STUDY, - compress=compression) + resources_ids=all_studies_ids, + resource_type=ResourceType.STUDY, + compress=compression) self.assertEqual(instances_count, ob.get_statistics().instances_count) self.assertEqual(disk_size, ob.get_statistics().total_disk_size)
--- a/Plugins/CGet/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/CGet/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Plugins/DicomWeb/DicomWeb.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/DicomWeb/DicomWeb.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Plugins/DicomWeb/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/DicomWeb/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -1,11 +1,12 @@ -#!/usr/bin/python +#!/usr/bin/python3 # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or @@ -302,7 +303,10 @@ { 'Resources' : [ 'nope' ], 'Synchronous' : True })) # inexisting resource - l = 3 # For >= 1.10.1 + if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + l = 4 # "Server" has been added + else: + l = 3 # For >= 1.10.1 # study r = DoPost(ORTHANC, '/dicom-web/servers/sample/stow', @@ -311,6 +315,8 @@ self.assertEqual(l, len(r)) self.assertEqual("0a9b3153-2512774b-2d9580de-1fc3dcf6-3bd83918", r['Resources']['Studies'][0]) + if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + self.assertEqual("sample", r['Server']) # series r = DoPost(ORTHANC, '/dicom-web/servers/sample/stow', @@ -599,6 +605,16 @@ self.assertEqual('Wang^XiaoDong', pn['Value'][0]['Alphabetic']) self.assertEqual(u'王^小東', pn['Value'][0]['Ideographic']) + # new derivated test added later + if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + a = DoGet(ORTHANC, '/dicom-web/studies?StudyInstanceUID=1.3.6.1.4.1.5962.1.2.0.1175775771.5711.0') + self.assertEqual(1, len(a)) + pn = a[0]['00100010'] # Patient name + self.assertEqual('PN', pn['vr']) + self.assertEqual(1, len(pn['Value'])) + self.assertEqual('Wang^XiaoDong', pn['Value'][0]['Alphabetic']) # before 1.18, one of the 2 values was empty ! + self.assertEqual(u'王^小東', pn['Value'][0]['Ideographic']) + def test_bitbucket_issue_96(self): # WADO-RS RetrieveFrames rejects valid accept headers @@ -681,6 +697,14 @@ self.assertTrue('00280010' in a[0]) self.assertEqual(512, a[0]['00280010']['Value'][0]) + if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 17, 0): + a = DoGet(ORTHANC, '/dicom-web/studies/1.2.840.113619.2.176.2025.1499492.7391.1171285944.390/series/1.2.840.113619.2.176.2025.1499492.7391.1171285944.394/instances?includefield=00081140') + self.assertEqual(1, len(a)) + self.assertTrue('00081140' in a[0]) + self.assertEqual(2, len(a[0]['00081140']['Value'])) + self.assertEqual('1.2.840.113619.2.176.2025.1499492.7040.1171286241.719', a[0]['00081140']['Value'][0]['00081155']['Value'][0]) + + def test_stow_errors(self): def CheckSequences(a):
--- a/Plugins/Recycling/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/Recycling/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Plugins/Transfers/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/Transfers/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -5,7 +5,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Plugins/WSI/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/WSI/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -5,7 +5,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or @@ -459,12 +460,21 @@ self.assertEqual(512, info['height']) self.assertEqual(3, len(info['sizes'])) - self.assertEqual(512, info['sizes'][0]['width']) - self.assertEqual(512, info['sizes'][0]['height']) - self.assertEqual(256, info['sizes'][1]['width']) - self.assertEqual(256, info['sizes'][1]['height']) - self.assertEqual(128, info['sizes'][2]['width']) - self.assertEqual(128, info['sizes'][2]['height']) + + if IsPluginVersionAbove(ORTHANC, "wsi", 2, 1, 0): # https://orthanc.uclouvain.be/hg/orthanc-wsi/rev/9dc7f1e8716d + self.assertEqual(512, info['sizes'][2]['width']) + self.assertEqual(512, info['sizes'][2]['height']) + self.assertEqual(256, info['sizes'][1]['width']) + self.assertEqual(256, info['sizes'][1]['height']) + self.assertEqual(128, info['sizes'][0]['width']) + self.assertEqual(128, info['sizes'][0]['height']) + else: + self.assertEqual(512, info['sizes'][0]['width']) + self.assertEqual(512, info['sizes'][0]['height']) + self.assertEqual(256, info['sizes'][1]['width']) + self.assertEqual(256, info['sizes'][1]['height']) + self.assertEqual(128, info['sizes'][2]['width']) + self.assertEqual(128, info['sizes'][2]['height']) self.assertEqual(1, len(info['tiles'])) self.assertEqual(128, info['tiles'][0]['width'])
--- a/Plugins/WebDav/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/WebDav/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Plugins/Worklists/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Plugins/Worklists/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -5,7 +5,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/README Thu Sep 05 18:46:25 2024 +0200 +++ b/README Thu Sep 05 18:49:09 2024 +0200 @@ -139,6 +139,23 @@ To run the IngestTranscoding tests: # rm -rf /tmp/OrthancTest && python ./Tests/CheckIngestTranscoding.py /home/alain/o/build/orthanc/orthanc +To run the DICOM TLS tests without Client certificate checks: +# cd ./Tests +# python CheckDicomTls.py --config-no-check-client +# /home/alain/o/build/orthanc/Orthanc --verbose dicom-tls.json +#### or +# docker run -p 8042:8042 -p 4242:4242 -e ORTHANC__DICOM_TLS_ENABLED=true -e ORTHANC__DICOM_TLS_CERTIFICATE=/certs/dicom-tls-a.crt -e ORTHANC__DICOM_TLS_PRIVATE_KEY=/certs/dicom-tls-a.key -e ORTHANC__DICOM_TLS_REMOTE_CERTIFICATE_REQUIRED=false -e ORTHANC__DICOM_TLS_TRUSTED_CERTIFICATES=/certs/dicom-tls-trusted.crt -e ORTHANC__EXECUTE_LUA_ENABLED=true -v .:/certs/ -e ORTHANC__AUTHENTICATION_ENABLED=false -e VERBOSE_ENABLED=true orthancteam/orthanc:24.6.1 +# python CheckDicomTls.py --force OrthancNoCheckClient + +To run the DICOM TLS tests without Client certificate checks: +# cd ./Tests +# python CheckDicomTls.py --config-check-client +# /home/alain/o/build/orthanc/Orthanc --verbose dicom-tls.json +#### or +# docker run -p 8042:8042 -p 4242:4242 -e ORTHANC__DICOM_TLS_ENABLED=true -e ORTHANC__DICOM_TLS_CERTIFICATE=/certs/dicom-tls-a.crt -e ORTHANC__DICOM_TLS_PRIVATE_KEY=/certs/dicom-tls-a.key -e ORTHANC__DICOM_TLS_REMOTE_CERTIFICATE_REQUIRED=true -e ORTHANC__DICOM_TLS_TRUSTED_CERTIFICATES=/certs/dicom-tls-trusted.crt -e ORTHANC__EXECUTE_LUA_ENABLED=true -v .:/certs/ -e ORTHANC__AUTHENTICATION_ENABLED=false -e VERBOSE_ENABLED=true orthancteam/orthanc:24.6.1 +# python CheckDicomTls.py --force OrthancCheckClient + + (Option 2b) With Docker:
--- a/Tests/CheckDicomTls.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/CheckDicomTls.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or @@ -60,7 +61,9 @@ help = 'Password to the REST API') parser.add_argument('--force', help = 'Do not warn the user', action = 'store_true') -parser.add_argument('--config', help = 'Create the configuration files for this test in the current folder', +parser.add_argument('--config-no-check-client', help = 'Create the configuration files for the "no-check-client" tests in the current folder', + action = 'store_true') +parser.add_argument('--config-check-client', help = 'Create the configuration files for the "check-client" tests test in the current folder', action = 'store_true') parser.add_argument('options', metavar = 'N', nargs = '*', help='Arguments to Python unittest') @@ -73,14 +76,14 @@ ## -if args.config: +if args.config_no_check_client or args.config_check_client: def CreateCertificate(name): subprocess.check_call([ 'openssl', 'req', '-x509', '-nodes', '-days', '365', '-newkey', 'rsa:2048', '-keyout', '%s.key' % name, '-out', '%s.crt' % name, '-subj', '/C=BE/CN=localhost' ]) - print('Writing configuration to folder: %s' % args.config) + print('Writing configuration for the %s tests to current folder' % ('no-check-client' if args.config_no_check_client else 'check-client')) CreateCertificate('dicom-tls-a') CreateCertificate('dicom-tls-b') CreateCertificate('dicom-tls-c') # Not trusted by Orthanc @@ -101,7 +104,7 @@ 'RegisteredUsers' : { 'alice' : 'orthanctest' }, - 'DicomTlsRemoteCertificateRequired' : False, # New in Orthanc 1.9.3 + 'DicomTlsRemoteCertificateRequired' : args.config_check_client, # New in Orthanc 1.9.3 })) exit(0) @@ -135,7 +138,8 @@ FNULL = open(os.devnull, 'w') # Emulates "subprocess.DEVNULL" on Python 2.7 -class Orthanc(unittest.TestCase): +# in these tests, Orthanc does not check client certificates +class OrthancNoCheckClient(unittest.TestCase): def setUp(self): if (sys.version_info >= (3, 0)): # Remove annoying warnings about unclosed socket in Python 3 @@ -146,7 +150,7 @@ def test_incoming(self): - # No certificate + # No client certificate provided and client does not check server cert -> raise self.assertRaises(Exception, lambda: subprocess.check_call([ FindExecutable('echoscu'), ORTHANC['Server'], @@ -154,6 +158,16 @@ '-aec', 'ORTHANC', ], stderr = FNULL)) + # No client certificate provided and client does check server cert -> no raise + self.assertRaises(Exception, lambda: subprocess.check_call([ + FindExecutable('echoscu'), + ORTHANC['Server'], + str(ORTHANC['DicomPort']), + '-aec', 'ORTHANC', + '+cf', 'dicom-tls-a.crt' + ], stderr = FNULL)) + + # random client certificate provided and client does check server cert -> no raise since Orthanc does not check the client cert subprocess.check_call([ FindExecutable('echoscu'), ORTHANC['Server'], @@ -163,23 +177,6 @@ '+cf', 'dicom-tls-a.crt', ], stderr = FNULL) - self.assertRaises(Exception, lambda: subprocess.check_call([ - FindExecutable('echoscu'), - ORTHANC['Server'], - str(ORTHANC['DicomPort']), - '-aec', 'ORTHANC', - '+tls', 'dicom-tls-c.key', 'dicom-tls-c.crt', # Not trusted by Orthanc - '+cf', 'dicom-tls-a.crt', - ], stderr = FNULL)) - - self.assertRaises(Exception, lambda: subprocess.check_call([ - FindExecutable('echoscu'), - ORTHANC['Server'], - str(ORTHANC['DicomPort']), - '-aec', 'ORTHANC', - '+tls', 'dicom-tls-b.key', 'dicom-tls-b.crt', - '+cf', 'dicom-tls-b.crt', # Not the certificate of Orthanc - ], stderr = FNULL)) def test_outgoing_to_self(self): @@ -217,7 +214,40 @@ '+cf', 'dicom-tls-a.crt', ], stderr = FNULL) + +# in these tests, Orthanc do checks client certificates +class OrthancCheckClient(unittest.TestCase): + def setUp(self): + if (sys.version_info >= (3, 0)): + # Remove annoying warnings about unclosed socket in Python 3 + import warnings + warnings.simplefilter('ignore', ResourceWarning) + + DropOrthanc(ORTHANC) + + def test_check_client_incoming(self): + # client provides an untrusted certificate -> Orthanc will complain -> raise + self.assertRaises(Exception, lambda: subprocess.check_call([ + FindExecutable('echoscu'), + ORTHANC['Server'], + str(ORTHANC['DicomPort']), + '-aec', 'ORTHANC', + '+tls', 'dicom-tls-c.key', 'dicom-tls-c.crt', # Not trusted by Orthanc + '+cf', 'dicom-tls-a.crt', + ], stderr = FNULL)) + + # client provides a trusted certificate but expects another cert from Orthanc -> raise + self.assertRaises(Exception, lambda: subprocess.check_call([ + FindExecutable('echoscu'), + ORTHANC['Server'], + str(ORTHANC['DicomPort']), + '-aec', 'ORTHANC', + '+tls', 'dicom-tls-b.key', 'dicom-tls-b.crt', + '+cf', 'dicom-tls-b.crt', # Not the certificate of Orthanc + ], stderr = FNULL)) + + try: print('\nStarting the tests...') unittest.main(argv = [ sys.argv[0] ] + args.options)
--- a/Tests/CheckHttpServerSecurity.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/CheckHttpServerSecurity.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Tests/CheckIngestTranscoding.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/CheckIngestTranscoding.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Tests/CheckScuTranscoding.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/CheckScuTranscoding.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Tests/CheckZipStreams.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/CheckZipStreams.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Tests/Run.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/Run.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or
--- a/Tests/Tests.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/Tests.py Thu Sep 05 18:49:09 2024 +0200 @@ -5,7 +5,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or @@ -1581,6 +1582,12 @@ self.assertTrue('Test Patient BG ' in patientNames) self.assertTrue('Anonymized' in patientNames) + i = CallFindScu([ '-k', '0008,0052=PATIENT', '-k', '0010,0010=*' ]) + patientNames = re.findall('\(0010,0010\).*?\[(.*?)\]', i) + self.assertEqual(2, len(patientNames)) + self.assertTrue('Test Patient BG ' in patientNames) + self.assertTrue('Anonymized' in patientNames) + i = CallFindScu([ '-k', '0008,0052=SERIES', '-k', '0008,0021' ]) series = re.findall('\(0008,0021\).*?\[\s*(.*?)\s*\]', i) self.assertEqual(2, len(series)) @@ -2253,7 +2260,16 @@ 'Expand' : True, 'Query' : { 'StudyDate' : '20080820-' }}) self.assertEqual(0, len(a)) - + + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Expand' : True, + 'Query' : { 'PatientPosition' : 'HFS' }}) + self.assertEqual(2, len(a)) + + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Expand' : False, + 'Query' : { 'PatientPosition' : 'HFS' }}) + self.assertEqual(2, len(a)) def test_rest_query_retrieve(self): @@ -2981,7 +2997,8 @@ self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients&since=10' % i)) self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients&limit=10' % i)) - self.assertEqual(0, len(DoGet(_REMOTE, '/patients?since=0&limit=0'))) + if not IsOrthancVersionAbove(_REMOTE, 1, 12, 5): # with ExtendedFind, the limit=0 means no-limit like in /tools/find + self.assertEqual(0, len(DoGet(_REMOTE, '/patients?since=0&limit=0'))) self.assertEqual(2, len(DoGet(_REMOTE, '/patients?since=0&limit=100'))) self.assertEqual(2, len(DoGet(_REMOTE, '/studies?since=0&limit=100'))) self.assertEqual(4, len(DoGet(_REMOTE, '/series?since=0&limit=100'))) @@ -4217,6 +4234,7 @@ knee.append(UploadInstance(_REMOTE, 'Knee/T1/IM-0001-000%d.dcm' % (i + 1)) ['ID']) # Check using BRAINIX + # The tests below correspond to "isSimpleLookup_ == true" in "ResourceFinder" a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', 'Query' : { 'PatientName' : 'B*' }, 'Limit' : 10 }) @@ -4238,6 +4256,11 @@ 'Limit' : 3 }) self.assertEqual(3, len(a)) + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'Query' : { 'PatientName' : 'B*' }, + 'Limit' : 0 }) # This is an arbitrary convention + self.assertEqual(4, len(a)) + b = [] for i in range(4): a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', @@ -4250,6 +4273,12 @@ # Check whether the two sets are equal through symmetric difference self.assertEqual(0, len(set(b) ^ set(brainix))) + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'Query' : { 'PatientName' : 'B*' }, + 'Limit' : 1, + 'Since' : 4 }) + self.assertEqual(0, len(a)) + # Check using KNEE a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', 'Query' : { 'PatientName' : 'K*' }, @@ -4272,6 +4301,99 @@ self.assertEqual(0, len(set(b) ^ set(knee))) + # Now test "isSimpleLookup_ == false" + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }}) + self.assertEqual(3, len(a)) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Limit' : 0}) + self.assertEqual(3, len(b)) + self.assertEqual(a[0], b[0]) + self.assertEqual(a[1], b[1]) + self.assertEqual(a[2], b[2]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Limit' : 1}) + self.assertEqual(1, len(b)) + self.assertEqual(a[0], b[0]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 0, + 'Limit' : 1}) + self.assertEqual(1, len(b)) + self.assertEqual(a[0], b[0]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 0, + 'Limit' : 3}) + self.assertEqual(3, len(b)) + self.assertEqual(a[0], b[0]) + self.assertEqual(a[1], b[1]) + self.assertEqual(a[2], b[2]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 0, + 'Limit' : 4}) + self.assertEqual(3, len(b)) + self.assertEqual(a[0], b[0]) + self.assertEqual(a[1], b[1]) + self.assertEqual(a[2], b[2]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 1, + 'Limit' : 1}) + self.assertEqual(1, len(b)) + self.assertEqual(a[1], b[0]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 1, + 'Limit' : 2}) + self.assertEqual(2, len(b)) + self.assertEqual(a[1], b[0]) + self.assertEqual(a[2], b[1]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 1, + 'Limit' : 3}) + self.assertEqual(2, len(b)) + self.assertEqual(a[1], b[0]) + self.assertEqual(a[2], b[1]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 2, + 'Limit' : 1}) + self.assertEqual(1, len(b)) + self.assertEqual(a[2], b[0]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 2, + 'Limit' : 2}) + self.assertEqual(1, len(b)) + self.assertEqual(a[2], b[0]) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 3, + 'Limit' : 1}) + self.assertEqual(0, len(b)) + + b = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', + 'Query' : { 'PatientPosition' : '*' }, + 'Since' : 3, + 'Limit' : 10}) + self.assertEqual(0, len(b)) + def test_bitbucket_issue_46(self): # "PHI remaining after anonymization" @@ -5411,13 +5533,18 @@ UploadInstance(_REMOTE, 'DummyCT.dcm') UploadInstance(_REMOTE, 'DummyCTInvalidRows.dcm') - i = CallFindScu([ '-k', '0008,0052=IMAGES', '-k', 'Rows', '-k', 'PatientName' ]) + i = CallFindScu([ '-k', '0008,0052=IMAGES', '-k', 'PatientName', '-k', 'Rows', '-k', 'Columns' ]) # We have 2 instances... patientNames = re.findall('\(0010,0010\).*?\[(.*?)\]', i) self.assertEqual(2, len(patientNames)) self.assertEqual('KNIX', patientNames[0]) self.assertEqual('KNIX', patientNames[1]) + + columns = re.findall('\(0028,0011\) US ([0-9]+)', i) + self.assertEqual(2, len(columns)) + self.assertEqual('512', columns[0]) + self.assertEqual('512', columns[1]) # ...but only 1 value for the "Rows" tag rows = re.findall('\(0028,0010\) US ([0-9]+)', i) @@ -5825,7 +5952,14 @@ # This test fails on Orthanc <= 1.5.8 'Level' : 'Study', 'Query' : { - 'SeriesDescription' : '*' # Wildcard matching => no match, as the tag is absent + 'ImageComments' : '*' # Wildcard matching => no match, as the tag is absent + }, + 'Normalize' : False + })) + self.assertEqual(1, CountAnswers({ + 'Level' : 'Study', + 'Query' : { + 'ImageComments' : '' }, 'Normalize' : False })) @@ -5839,7 +5973,14 @@ self.assertEqual(1, CountAnswers({ 'Level' : 'Study', 'Query' : { - 'SeriesDescription' : '*' # Matches, as wiped out by the normalization + 'ImageComments' : '*' # Matches, as wiped out by the normalization + }, + 'Normalize' : True + })) + self.assertEqual(1, CountAnswers({ + 'Level' : 'Study', + 'Query' : { + 'ImageComments' : '' }, 'Normalize' : True })) @@ -9554,7 +9695,6 @@ a = UploadInstance(_REMOTE, '2023-04-21-RLEPlanarConfigurationYBR_FULL.dcm') ['ID'] uri = '/instances/%s/preview' % a im = GetImage(_REMOTE, uri) - pprint.pprint(im) self.assertEqual('RGB', im.mode) self.assertEqual(1260, im.size[0]) self.assertEqual(910, im.size[1]) @@ -10238,3 +10378,299 @@ # print(i) s = re.findall('\(0008,0000\).*?\[(.*?)\]', i) self.assertEqual(0, len(s)) + + + def test_tags_after_pixel_data(self): + if IsOrthancVersionAbove(_REMOTE, 1, 12, 4): + # https://discourse.orthanc-server.org/t/private-tags-with-group-7fe0-are-not-provided-via-rest-api/4744 + u = UploadInstance(_REMOTE, '2024-05-30-GuillemVela.dcm') ['ID'] + + a = DoGet(_REMOTE, '/instances/%s/tags' % u) + self.assertFalse('8e05,1000' in a) + + a = DoGet(_REMOTE, '/instances/%s/tags?whole' % u) + self.assertTrue('8e05,1000' in a) + self.assertEqual('XEOS_Attributes', a['8e05,0010']['Value']) + self.assertEqual('acquisition', a['8e05,1000']['Value']) + self.assertEqual('specimen', a['8e05,1001']['Value']) + + a = DoGet(_REMOTE, '/instances/%s/tags?full' % u) + self.assertFalse('8e05,1000' in a) + + a = DoGet(_REMOTE, '/instances/%s/tags?full&whole' % u) + self.assertTrue('8e05,1000' in a) + self.assertEqual('XEOS_Attributes', a['8e05,0010']['Value']) + self.assertEqual('acquisition', a['8e05,1000']['Value']) + self.assertEqual('specimen', a['8e05,1001']['Value']) + + a = DoGet(_REMOTE, '/instances/%s/tags?short' % u) + self.assertFalse('8e05,1000' in a) + + a = DoGet(_REMOTE, '/instances/%s/tags?short&whole' % u) + self.assertTrue('8e05,1000' in a) + self.assertEqual('XEOS_Attributes', a['8e05,0010']) + self.assertEqual('acquisition', a['8e05,1000']) + self.assertEqual('specimen', a['8e05,1001']) + + a = DoGet(_REMOTE, '/instances/%s/tags?simplify' % u) + self.assertFalse('Unknown Tag & Data' in a) + + a = DoGet(_REMOTE, '/instances/%s/tags?simplify&whole' % u) + self.assertTrue('Unknown Tag & Data' in a) + + a = DoGet(_REMOTE, '/instances/%s/simplified-tags' % u) + self.assertFalse('Unknown Tag & Data' in a) + + a = DoGet(_REMOTE, '/instances/%s/simplified-tags?whole' % u) + self.assertTrue('Unknown Tag & Data' in a) + + + def test_requested_tags(self): + u = UploadInstance(_REMOTE, 'DummyCT.dcm') + + def CheckPatientContent(patient): + self.assertEqual(u['ParentPatient'], patient['ID']) + self.assertEqual('Patient', patient['Type']) + self.assertFalse(patient['IsStable']) + self.assertEqual(0, len(patient['Labels'])) + self.assertTrue('LastUpdate' in patient) + self.assertEqual(2, len(patient['MainDicomTags'])) + self.assertEqual('ozp00SjY2xG', patient['MainDicomTags']['PatientID']) + self.assertEqual('KNIX', patient['MainDicomTags']['PatientName']) + self.assertEqual(1, len(patient['Studies'])) + self.assertEqual(u['ParentStudy'], patient['Studies'][0]) + + def CheckStudyContent(study): + self.assertEqual(u['ParentStudy'], study['ID']) + self.assertEqual(u['ParentPatient'], study['ParentPatient']) + self.assertEqual('Study', study['Type']) + self.assertFalse(study['IsStable']) + self.assertEqual(0, len(study['Labels'])) + self.assertTrue('LastUpdate' in study) + self.assertEqual(7, len(study['MainDicomTags'])) + self.assertEqual('0ECJ52puWpVIjTuhnBA0um', study['MainDicomTags']['InstitutionName']) + self.assertEqual('1', study['MainDicomTags']['ReferringPhysicianName']) + self.assertEqual('20070101', study['MainDicomTags']['StudyDate']) + self.assertEqual('Knee (R)', study['MainDicomTags']['StudyDescription']) + self.assertEqual('1', study['MainDicomTags']['StudyID']) + self.assertEqual('1.2.840.113619.2.176.2025.1499492.7391.1171285944.390', study['MainDicomTags']['StudyInstanceUID']) + self.assertEqual('120000.000000', study['MainDicomTags']['StudyTime']) + self.assertEqual(2, len(study['PatientMainDicomTags'])) + self.assertEqual('ozp00SjY2xG', study['PatientMainDicomTags']['PatientID']) + self.assertEqual('KNIX', study['PatientMainDicomTags']['PatientName']) + self.assertEqual(1, len(study['Series'])) + self.assertEqual(u['ParentSeries'], study['Series'][0]) + + def CheckSeriesContent(series): + self.assertEqual(None, series['ExpectedNumberOfInstances']) + self.assertEqual('Unknown', series['Status']) + self.assertEqual(u['ParentSeries'], series['ID']) + self.assertEqual(u['ParentStudy'], series['ParentStudy']) + self.assertEqual('Series', series['Type']) + self.assertFalse(series['IsStable']) + self.assertEqual(0, len(series['Labels'])) + self.assertTrue('LastUpdate' in series) + self.assertEqual(13, len(series['MainDicomTags'])) + self.assertEqual('0', series['MainDicomTags']['CardiacNumberOfImages']) + self.assertEqual('0.999841\\0.000366209\\0.0178227\\-0.000427244\\0.999995\\0.00326545', series['MainDicomTags']['ImageOrientationPatient']) + self.assertEqual('24', series['MainDicomTags']['ImagesInAcquisition']) + self.assertEqual('GE MEDICAL SYSTEMS', series['MainDicomTags']['Manufacturer']) + self.assertEqual('MR', series['MainDicomTags']['Modality']) + self.assertEqual('ca', series['MainDicomTags']['OperatorsName']) + self.assertEqual('324-58-2995/6', series['MainDicomTags']['ProtocolName']) + self.assertEqual('20070101', series['MainDicomTags']['SeriesDate']) + self.assertEqual('AX. FSE PD', series['MainDicomTags']['SeriesDescription']) + self.assertEqual('1.2.840.113619.2.176.2025.1499492.7391.1171285944.394', series['MainDicomTags']['SeriesInstanceUID']) + self.assertEqual('5', series['MainDicomTags']['SeriesNumber']) + self.assertEqual('120000.000000', series['MainDicomTags']['SeriesTime']) + self.assertEqual('TWINOW', series['MainDicomTags']['StationName']) + self.assertEqual(1, len(series['Instances'])) + self.assertEqual(u['ID'], series['Instances'][0]) + + def CheckInstanceContent(instance): + self.assertEqual(2472, instance['FileSize']) + self.assertTrue('FileUuid' in instance) + self.assertEqual(u['ID'], instance['ID']) + self.assertEqual(u['ParentSeries'], instance['ParentSeries']) + self.assertEqual('Instance', instance['Type']) + self.assertEqual(1, instance['IndexInSeries']) + self.assertEqual(0, len(instance['Labels'])) + self.assertEqual(7, len(instance['MainDicomTags'])) + self.assertEqual('1', instance['MainDicomTags']['AcquisitionNumber']) + self.assertEqual('0.999841\\0.000366209\\0.0178227\\-0.000427244\\0.999995\\0.00326545', instance['MainDicomTags']['ImageOrientationPatient']) + self.assertEqual('-149.033\\-118.499\\-61.0464', instance['MainDicomTags']['ImagePositionPatient']) + self.assertEqual('20070101', instance['MainDicomTags']['InstanceCreationDate']) + self.assertEqual('120000.000000', instance['MainDicomTags']['InstanceCreationTime']) + self.assertEqual('1', instance['MainDicomTags']['InstanceNumber']) + self.assertEqual('1.2.840.113619.2.176.2025.1499492.7040.1171286242.109', instance['MainDicomTags']['SOPInstanceUID']) + + def CheckRequestedTags(resource): + self.assertEqual(6, len(resource['RequestedTags'])) + self.assertEqual('ozp00SjY2xG', resource['RequestedTags']['PatientID']) + self.assertEqual('Knee (R)', resource['RequestedTags']['StudyDescription']) + self.assertEqual('AX. FSE PD', resource['RequestedTags']['SeriesDescription']) + self.assertEqual('1.2.840.10008.5.1.4.1.1.4', resource['RequestedTags']['SOPClassUID']) + self.assertEqual('2800', resource['RequestedTags']['RepetitionTime']) + self.assertEqual(3, len(resource['RequestedTags']['DerivationCodeSequence'][0])) + self.assertEqual('121327', resource['RequestedTags']['DerivationCodeSequence'][0]['CodeValue']) + + requestedTags = 'PatientID;StudyDescription;SeriesDescription;SOPClassUID;RepetitionTime;DerivationCodeSequence' + + a = DoGet(_REMOTE, '/patients?expand') + self.assertEqual(1, len(a)) + self.assertEqual(7, len(a[0])) + CheckPatientContent(a[0]) + self.assertFalse('RequestedTags' in a[0]) + + a = DoGet(_REMOTE, '/patients?expand&requestedTags=%s' % requestedTags) + self.assertEqual(1, len(a)) + self.assertEqual(8, len(a[0])) + CheckPatientContent(a[0]) + CheckRequestedTags(a[0]) + + a = DoGet(_REMOTE, '/studies?expand') + self.assertEqual(1, len(a)) + self.assertEqual(9, len(a[0])) + CheckStudyContent(a[0]) + self.assertFalse('RequestedTags' in a[0]) + + a = DoGet(_REMOTE, '/studies?expand&requestedTags=%s' % requestedTags) + self.assertEqual(1, len(a)) + self.assertEqual(10, len(a[0])) + CheckStudyContent(a[0]) + CheckRequestedTags(a[0]) + + a = DoGet(_REMOTE, '/series?expand') + self.assertEqual(1, len(a)) + self.assertEqual(10, len(a[0])) + CheckSeriesContent(a[0]) + self.assertFalse('RequestedTags' in a[0]) + + a = DoGet(_REMOTE, '/series?expand&requestedTags=%s' % requestedTags) + self.assertEqual(1, len(a)) + self.assertEqual(11, len(a[0])) + CheckSeriesContent(a[0]) + CheckRequestedTags(a[0]) + + a = DoGet(_REMOTE, '/instances?expand') + self.assertEqual(1, len(a)) + self.assertEqual(8, len(a[0])) + CheckInstanceContent(a[0]) + self.assertFalse('RequestedTags' in a[0]) + + a = DoGet(_REMOTE, '/instances?expand&requestedTags=%s' % requestedTags) + self.assertEqual(1, len(a)) + self.assertEqual(9, len(a[0])) + CheckInstanceContent(a[0]) + CheckRequestedTags(a[0]) + + a = DoGet(_REMOTE, '/patients/%s' % u['ParentPatient']) + self.assertEqual(7, len(a)) + CheckPatientContent(a) + self.assertFalse('RequestedTags' in a) + + a = DoGet(_REMOTE, '/patients/%s?requestedTags=%s' % (u['ParentPatient'], requestedTags)) + self.assertEqual(8, len(a)) + CheckPatientContent(a) + CheckRequestedTags(a) + + a = DoGet(_REMOTE, '/studies/%s' % u['ParentStudy']) + self.assertEqual(9, len(a)) + CheckStudyContent(a) + self.assertFalse('RequestedTags' in a) + + a = DoGet(_REMOTE, '/studies/%s?requestedTags=%s' % (u['ParentStudy'], requestedTags)) + self.assertEqual(10, len(a)) + CheckStudyContent(a) + CheckRequestedTags(a) + + a = DoGet(_REMOTE, '/series/%s' % u['ParentSeries']) + self.assertEqual(10, len(a)) + CheckSeriesContent(a) + self.assertFalse('RequestedTags' in a) + + a = DoGet(_REMOTE, '/series/%s?requestedTags=%s' % (u['ParentSeries'], requestedTags)) + self.assertEqual(11, len(a)) + CheckSeriesContent(a) + CheckRequestedTags(a) + + a = DoGet(_REMOTE, '/instances/%s' % u['ID']) + self.assertEqual(8, len(a)) + CheckInstanceContent(a) + self.assertFalse('RequestedTags' in a) + + a = DoGet(_REMOTE, '/instances/%s?requestedTags=%s' % (u['ID'], requestedTags)) + self.assertEqual(9, len(a)) + CheckInstanceContent(a) + CheckRequestedTags(a) + + + def test_computed_tags(self): + # curl 'http://alice:orthanctest@localhost:8042/patients/0946fcb6-cf12ab43-bad958c1-bf057ad5-0fc6f54c?requested-tags=0020,1200;0020,1202;0020,1204' + # curl 'http://alice:orthanctest@localhost:8042/studies/6c65289b-db2fcb71-7eaf73f4-8e12470c-a4d6d7cf?requested-tags=0008,0061;0008,0062;0020,1206;0020,1208' + # curl 'http://alice:orthanctest@localhost:8042/series/318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb?requested-tags=0020,1209' + # curl 'http://alice:orthanctest@localhost:8042/instances/ee693caa-9786a685-4f0f9fb0-4411cc8b-988f5574?requested-tags=0008,0056' + + UploadInstance(_REMOTE, 'Comunix/Ct/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Comunix/Ct/IM-0001-0002.dcm') + UploadInstance(_REMOTE, 'Comunix/Pet/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Comunix/Pet/IM-0001-0002.dcm') + + instance = 'ee693caa-9786a685-4f0f9fb0-4411cc8b-988f5574' + series = '318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb' + study = '6c65289b-db2fcb71-7eaf73f4-8e12470c-a4d6d7cf' + patient = '0946fcb6-cf12ab43-bad958c1-bf057ad5-0fc6f54c' + + a = DoGet(_REMOTE, '/instances/%s?requested-tags=0008,0056' % instance) + self.assertEqual(1, len(a['RequestedTags'])) + self.assertEqual('ONLINE', a['RequestedTags']['InstanceAvailability']) + + a = DoGet(_REMOTE, '/series/%s?requested-tags=0020,1209' % series) + self.assertEqual(1, len(a['RequestedTags'])) + self.assertEqual(2, int(a['RequestedTags']['NumberOfSeriesRelatedInstances'])) + + a = DoGet(_REMOTE, '/studies/%s?requested-tags=0008,0061;0008,0062;0020,1206;0020,1208' % study) + self.assertEqual(4, len(a['RequestedTags'])) + self.assertEqual('CT\\PT', a['RequestedTags']['ModalitiesInStudy']) + self.assertEqual('1.2.840.10008.5.1.4.1.1.128\\1.2.840.10008.5.1.4.1.1.2', a['RequestedTags']['SOPClassesInStudy']) + self.assertEqual(2, int(a['RequestedTags']['NumberOfStudyRelatedSeries'])) + self.assertEqual(4, int(a['RequestedTags']['NumberOfStudyRelatedInstances'])) + + a = DoGet(_REMOTE, '/patients/%s?requested-tags=0020,1200;0020,1202;0020,1204' % patient) + self.assertEqual(3, len(a['RequestedTags'])) + self.assertEqual(1, int(a['RequestedTags']['NumberOfPatientRelatedStudies'])) + self.assertEqual(2, int(a['RequestedTags']['NumberOfPatientRelatedSeries'])) + self.assertEqual(4, int(a['RequestedTags']['NumberOfPatientRelatedInstances'])) + + def test_computed_tags_and_patient_comments(self): + UploadInstance(_REMOTE, 'WithEmptyPatientComments.dcm') + + # without requesting PatientComments, we get the computed tags + i = CallFindScu([ '-k', 'PatientID=WITH_COMMENTS', '-k', 'QueryRetrieveLevel=Study', '-k', 'ModalitiesInStudy', '-k', 'NumberOfStudyRelatedSeries', '-k', 'NumberOfStudyRelatedInstances' ]) + modalitiesInStudy = re.findall('\(0008,0061\).*?\[(.*?)\]', i) + self.assertEqual(1, len(modalitiesInStudy)) + self.assertEqual('CT', modalitiesInStudy[0]) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): + # when requesting PatientComments, with 1.12.4, we did not get the computed tags + i = CallFindScu([ '-k', 'PatientID=WITH_COMMENTS', '-k', 'QueryRetrieveLevel=Study', '-k', 'ModalitiesInStudy', '-k', 'NumberOfStudyRelatedSeries', '-k', 'NumberOfStudyRelatedInstances', '-k', 'PatientComments' ]) + modalitiesInStudy = re.findall('\(0008,0061\).*?\[(.*?)\]', i) + self.assertEqual(1, len(modalitiesInStudy)) + self.assertEqual('CT', modalitiesInStudy[0]) + numberOfStudyRelatedSeries = re.findall('\(0020,1206\).*?\[(.*?)\]', i) + self.assertEqual(1, len(numberOfStudyRelatedSeries)) + self.assertEqual(1, int(numberOfStudyRelatedSeries[0])) + numberOfStudyRelatedInstances = re.findall('\(0020,1208\).*?\[(.*?)\]', i) + self.assertEqual(1, len(numberOfStudyRelatedInstances)) + self.assertEqual(1, int(numberOfStudyRelatedInstances[0])) + + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', + 'Expand': True, + 'Query' : { 'PatientID' : 'WITH_COMMENTS'}, + 'RequestedTags': ['ModalitiesInStudy', 'NumberOfStudyRelatedSeries', 'NumberOfStudyRelatedInstances', 'PatientComments']}) + + self.assertEqual(4, len(a[0]['RequestedTags'].keys())) + self.assertEqual(1, int(a[0]['RequestedTags']['NumberOfStudyRelatedSeries'])) + self.assertEqual(1, int(a[0]['RequestedTags']['NumberOfStudyRelatedInstances'])) + self.assertEqual('CT', a[0]['RequestedTags']['ModalitiesInStudy']) + self.assertEqual('', a[0]['RequestedTags']['PatientComments'])
--- a/Tests/Toolbox.py Thu Sep 05 18:46:25 2024 +0200 +++ b/Tests/Toolbox.py Thu Sep 05 18:49:09 2024 +0200 @@ -3,7 +3,8 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2024 Osimis S.A., Belgium +# Copyright (C) 2017-2023 Osimis S.A., Belgium +# Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or