# HG changeset patch # User Alain Mazy # Date 1787643092 -7200 # Node ID b3b5f8990265322b20470372a768dfb54f982c3e # Parent df6abc13ad0bef9f074b9a8843154756cefa8bec# Parent fed1678dc2bfee59c883b4d35895b0c1271229fe merge diff -r df6abc13ad0b -r b3b5f8990265 .github/workflows/mirror.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.github/workflows/mirror.yml Tue Aug 25 09:31:32 2026 +0200 @@ -0,0 +1,53 @@ +name: Mirror Mercurial to GitHub + +env: + HG_SOURCE_URL: https://orthanc.uclouvain.be/hg/orthanc-tests/ + GITHUB_TARGET: jodogne/mirror-orthanc-tests + FAST_EXPORT_VERSION: fast-export-260405 + +on: + schedule: + - cron: '0 3 * * *' + workflow_dispatch: {} + +concurrency: + group: hg-mirror + cancel-in-progress: false + +permissions: + contents: write # GITHUB_TOKEN is read-only by default; this grants push access + +jobs: + mirror: + if: ${{ github.repository == 'jodogne/mirror-orthanc-tests' }} + runs-on: ubuntu-slim + steps: + - name: Install Mercurial and fast-export + run: | + sudo apt-get update + DEBIAN_FRONTEND=noninteractive sudo apt-get install -y mercurial git wget + cd /tmp/ + wget https://orthanc.uclouvain.be/downloads/third-party-downloads/${FAST_EXPORT_VERSION}.tar.gz + tar xvf ${FAST_EXPORT_VERSION}.tar.gz + + - name: Full clone of Mercurial source + run: | + hg clone "${HG_SOURCE_URL}" hg-repo + + - name: Full conversion (fresh git repo every run) + run: | + git init --bare git-repo + cd git-repo + /tmp/${FAST_EXPORT_VERSION}/hg-fast-export.sh -r ../hg-repo --hg-hash --hgtags + + - name: Push (never force, so it can't rewrite or delete history) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd git-repo + git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_TARGET}" + git push origin 'refs/heads/*:refs/heads/*' + git push origin 'refs/tags/*:refs/tags/*' + if git rev-parse --verify refs/notes/hg >/dev/null 2>&1; then + git push origin refs/notes/hg:refs/notes/hg + fi diff -r df6abc13ad0b -r b3b5f8990265 .hgtags --- a/.hgtags Tue Aug 25 09:31:20 2026 +0200 +++ b/.hgtags Tue Aug 25 09:31:32 2026 +0200 @@ -52,3 +52,4 @@ 467d73ff9c02807f8f7f3c029e209fb445f6ef8b Orthanc-1.12.9 655347b0274bea00576ed2f647acf7ce9ee49b5c Orthanc-1.12.10 0cc8f543108ede9301b42cb94148ce10db68180d Orthanc-1.12.11 +f3f666d432bf7c319c9004e8930f7d1ec3c63b31 Orthanc-1.13.0 diff -r df6abc13ad0b -r b3b5f8990265 CITATION.cff --- a/CITATION.cff Tue Aug 25 09:31:20 2026 +0200 +++ b/CITATION.cff Tue Aug 25 09:31:32 2026 +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.11 -date-released: 2026-04-14 +version: 1.13.0 +date-released: 2026-08-15 diff -r df6abc13ad0b -r b3b5f8990265 Database/WSI/NOTES.txt --- a/Database/WSI/NOTES.txt Tue Aug 25 09:31:20 2026 +0200 +++ b/Database/WSI/NOTES.txt Tue Aug 25 09:31:32 2026 +0200 @@ -1,3 +1,12 @@ + +Create a pyramid from the Orthanc logo +-------------------------------------- + +$ convert /tmp/Orthanc-LOGO.png -background grey -alpha remove -alpha off /tmp/FlatLogo.png +$ gdal_translate /tmp/FlatLogo.png ./OrthancLogoRaw.tiff -co TILED=YES -co BLOCKXSIZE=512 -co BLOCKYSIZE=512 +$ gdal_translate /tmp/FlatLogo.png ./OrthancLogoJpeg.tiff -co TILED=YES -co BLOCKXSIZE=512 -co BLOCKYSIZE=512 -co COMPRESS=JPEG + + Create a small image pyramid ---------------------------- diff -r df6abc13ad0b -r b3b5f8990265 Database/WSI/OrthancLogoJpeg.tiff Binary file Database/WSI/OrthancLogoJpeg.tiff has changed diff -r df6abc13ad0b -r b3b5f8990265 Database/WSI/OrthancLogoRaw.tiff Binary file Database/WSI/OrthancLogoRaw.tiff has changed diff -r df6abc13ad0b -r b3b5f8990265 GenerateConfigurationForTests.py --- a/GenerateConfigurationForTests.py Tue Aug 25 09:31:20 2026 +0200 +++ b/GenerateConfigurationForTests.py Tue Aug 25 09:31:32 2026 +0200 @@ -123,7 +123,7 @@ 'LocalAet': 'SELF-FROM-CONFIG' }, 'orthanctest-with-local-aet': { - 'AET': 'ORTHANCTEST', + 'AET': 'ORTHANCTEST2', 'Host': ip, 'Port': 5001, 'LocalAet': 'OT-FROM-CONFIG' @@ -162,7 +162,6 @@ config['MediaArchiveSize'] = 1 config['SaveJobs'] = False config['ExecuteLuaEnabled'] = True -config['HttpTimeout'] = 2 config['SyncStorageArea'] = False # For tests to run more quickly config['WebDavEnabled'] = True config['WebDavDeleteAllowed'] = True @@ -171,8 +170,16 @@ config['CheckRevisions'] = True config['HttpsCACertificates'] = "/etc/ssl/certs/ca-certificates.crt" # for HTTPS lua tests (note: this path is valid only on linux !) +# Release network resources as soon as possible +config['HttpTimeout'] = 2 +config['HttpRequestTimeout'] = 5 +config['DicomScpTimeout'] = 5 +config['DicomScuTimeout'] = 5 + del config['DeidentifyLogsDicomVersion'] -del config['KeepAlive'] + +if 'KeepAlive' in config: # It could be absent, as this option has been moved to "AdvancedConfiguration.json" + del config['KeepAlive'] config['Dictionary'] = { '00e1,10c2' : [ 'UI', 'PET-CT Multi Modality Name', 1, 1, 'ELSCINT1' ], diff -r df6abc13ad0b -r b3b5f8990265 NewTests/ExtraMainDicomTags/test_extra_main_dicom_tags.py --- a/NewTests/ExtraMainDicomTags/test_extra_main_dicom_tags.py Tue Aug 25 09:31:20 2026 +0200 +++ b/NewTests/ExtraMainDicomTags/test_extra_main_dicom_tags.py Tue Aug 25 09:31:32 2026 +0200 @@ -105,7 +105,7 @@ count = int(m.get('orthanc_storage_cache_hit_count') or '0') + int(m.get('orthanc_storage_cache_miss_count') or '0') - if self.o.get_system()["ApiVersion"] >= 34: # 1.13.0 and 34 = streaming branch + if self.o.is_orthanc_version_at_least(1, 13, 0): count += int(m.get('orthanc_dicom_parser_cache_hit_count') or '0') print(f"storage access count = {count}") diff -r df6abc13ad0b -r b3b5f8990265 NewTests/InterruptedDownloads/test_interrupted_downloads.py --- a/NewTests/InterruptedDownloads/test_interrupted_downloads.py Tue Aug 25 09:31:20 2026 +0200 +++ b/NewTests/InterruptedDownloads/test_interrupted_downloads.py Tue Aug 25 09:31:32 2026 +0200 @@ -81,7 +81,7 @@ def test_interrupting_a_download_should_cancel_the_archive_job(self): # note: there was a regression between 1.12.10 and 1.12.11 - if self.o.is_orthanc_version_at_least(1, 12, 12) and self.o.get_system()["ApiVersion"] >= 34: # 1.13.0 and 34 = streaming branch + if self.o.is_orthanc_version_at_least(1, 13, 0): self.o.delete_all_content() populator = OrthancTestDbPopulator(self.o, studies_count=1, series_count=2, instances_count=200, random_seed=65) populator.execute() @@ -98,7 +98,7 @@ def test_interrupting_a_download_should_release_the_http_thread(self): # note: there was a regression between 1.12.10 and 1.12.11 - if self.o.is_orthanc_version_at_least(1, 12, 12) and self.o.get_system()["ApiVersion"] >= 34: # 1.13.0 and 34 = streaming branch + if self.o.is_orthanc_version_at_least(1, 13, 0): self.o.delete_all_content() populator = OrthancTestDbPopulator(self.o, studies_count=1, series_count=2, instances_count=200, random_seed=65) populator.execute() diff -r df6abc13ad0b -r b3b5f8990265 Tests/CheckHttpServerSecurity.py --- a/Tests/CheckHttpServerSecurity.py Tue Aug 25 09:31:20 2026 +0200 +++ b/Tests/CheckHttpServerSecurity.py Tue Aug 25 09:31:32 2026 +0200 @@ -43,11 +43,14 @@ os.mkdir(TMP) -ORTHANC = Toolbox.DefineOrthanc(username = 'orthanc', - password = 'orthanc') -def IsHttpServerSecure(config): +def IsHttpServerSecure(config, + username = 'orthanc', + password = 'orthanc'): + ORTHANC = Toolbox.DefineOrthanc(username = username, + password = password) + with open(CONFIG, 'w') as f: f.write(json.dumps(config)) @@ -59,66 +62,93 @@ #shell=True ) - time.sleep(1) + success = False - while True: + while process.poll() is None: # Orthanc is still running try: system = Toolbox.DoGet(ORTHANC, '/system') + success = True break except: - time.sleep(0.1) + pass + + time.sleep(0.1) process.terminate() process.wait() - return system['IsHttpServerSecure'] + if success: + return system['IsHttpServerSecure'] + else: + return None # Orthanc has not started -def Assert(b): - if not b: +def Assert(expected, actual): + if expected != actual: raise Exception('Bad result') print('==== TEST 1 ====') -Assert(IsHttpServerSecure({ +Assert(True, IsHttpServerSecure({ 'RemoteAccessAllowed': False, 'RegisteredUsers' : { } })) print('==== TEST 2 ====') -Assert(IsHttpServerSecure({ +Assert(True, IsHttpServerSecure({ 'RemoteAccessAllowed': False, 'AuthenticationEnabled': False, 'RegisteredUsers' : { } })) -print('==== TEST 3 ====') -Assert(IsHttpServerSecure({ +print('==== TEST 3a ====') +Assert(False, IsHttpServerSecure({ 'RemoteAccessAllowed': False, 'AuthenticationEnabled': True, 'RegisteredUsers' : { 'orthanc' : 'orthanc' } })) -print('==== TEST 4 ====') -Assert(not IsHttpServerSecure({ +print('==== TEST 3b ====') +Assert(False, IsHttpServerSecure({ + 'RemoteAccessAllowed': False, + 'AuthenticationEnabled': True, + 'RegisteredUsers' : { 'alice' : 'orthanctest' } + }, 'alice', 'orthanctest')) + +print('==== TEST 3c ====') +Assert(True, IsHttpServerSecure({ + 'RemoteAccessAllowed': False, + 'AuthenticationEnabled': True, + 'RegisteredUsers' : { 'orthanc' : 'SECURE' } + }, 'orthanc', 'SECURE')) + +print('==== TEST 4 ====') # Orthanc refuses to start since Orthanc 1.13.0 +Assert(None, IsHttpServerSecure({ 'RemoteAccessAllowed': True })) print('==== TEST 5 (server application scenario) ====') -Assert(not IsHttpServerSecure({ +Assert(False, IsHttpServerSecure({ 'RemoteAccessAllowed': True, 'AuthenticationEnabled': False, })) -print('==== TEST 6 ====') -Assert(IsHttpServerSecure({ +print('==== TEST 6a ====') +Assert(False, IsHttpServerSecure({ 'RemoteAccessAllowed': True, 'AuthenticationEnabled': True, 'RegisteredUsers' : { 'orthanc' : 'orthanc' } })) -print('==== TEST 7 (Docker scenario) ====') -Assert(not IsHttpServerSecure({ +print('==== TEST 6b ====') +Assert(True, IsHttpServerSecure({ + 'RemoteAccessAllowed': True, + 'AuthenticationEnabled': True, + 'RegisteredUsers' : { 'orthanc' : 'SECURE' } + }, 'orthanc', 'SECURE')) + +print('==== TEST 7 (Docker scenario) ====') # Orthanc refuses to start since Orthanc 1.13.0 +Assert(None, IsHttpServerSecure({ 'RemoteAccessAllowed': True, 'AuthenticationEnabled': True })) diff -r df6abc13ad0b -r b3b5f8990265 Tests/Tests.py --- a/Tests/Tests.py Tue Aug 25 09:31:20 2026 +0200 +++ b/Tests/Tests.py Tue Aug 25 09:31:32 2026 +0200 @@ -761,7 +761,7 @@ z, resp = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) self.assertEqual(2, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/601 - MR - T2W_TSE/MR000003.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 6): @@ -770,7 +770,7 @@ z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % kneeStudy) self.assertEqual(2, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/601 - MR - T2W_TSE/MR000003.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 6): @@ -779,7 +779,7 @@ z, resp = GetArchive(_REMOTE, '/series/%s/archive' % kneeSeries) self.assertEqual(1, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 6): self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) @@ -797,7 +797,7 @@ 'Utf8' : False, # Necessary since Orthanc 1.12.11 }) self.assertEqual(3, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('5Yp0E - BRAINIX/0 - IRM crbrale, neuro-crne/401 - MR - sT2W FLAIR/MR000001.dcm', z.namelist()) self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 11): @@ -815,7 +815,7 @@ 'Utf8' : False, # Necessary since Orthanc 1.12.11 }) self.assertEqual(3, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('5Yp0E - BRAINIX/0 - IRM crbrale, neuro-crne/401 - MR - sT2W FLAIR/MR000001.dcm', z.namelist()) self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 11): @@ -828,7 +828,7 @@ 'Utf8' : False, # Necessary since Orthanc 1.12.11 }) self.assertEqual(3, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('5Yp0E - BRAINIX/0 - IRM crbrale, neuro-crne/401 - MR - sT2W FLAIR/MR000001.dcm', z.namelist()) self.assertIn('887 - KNEE/A10003245599 - IRM DU GENOU/301 - MR - T1W_aTSE/MR000001.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 11): @@ -857,14 +857,14 @@ # when downloading studies individually, we want to have the PatientName that appears in the study z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % helloStudy) self.assertEqual(1, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('COMMON - HELLO/HELLO^SERIES/Unknown Series/00000000.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 11): self.assertIn('COMMON HELLO/HELLO^SERIES/Unknown Series/00000000.dcm', z.namelist()) z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % worldStudy) self.assertEqual(1, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('COMMON - WORLD/WORLD^SERIES/Unknown Series/00000000.dcm', z.namelist()) elif IsOrthancVersionAbove(_REMOTE, 1, 12, 11): self.assertIn('COMMON WORLD/WORLD^SERIES/Unknown Series/00000000.dcm', z.namelist()) @@ -1922,7 +1922,7 @@ # Test returning sequence values (only since Orthanc 0.9.5) i = CallFindScu([ '-k', '0008,0052=SERIES', '-k', '0008,2112' ]) # "ColorTestImageJ" has this sequence tag sequences = re.findall(r'\(0008,2112\)', i) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): # the content of sequences was not returned prior to this version + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): # the content of sequences was not returned prior to this version # From this version, Orthanc returns an empty sequence for the instance that does not contain the sequence # and a sequence with a content for the instance that contains the sequence self.assertEqual(2, len(sequences)) @@ -1931,7 +1931,7 @@ # and no sequence at all for the instance that contained the sequence self.assertEqual(1, len(sequences)) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): # the content of sequences was not returned prior to this version + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): # the content of sequences was not returned prior to this version sequences = re.findall(r'\(0008,0016\)', i) self.assertEqual(1, len(sequences)) sequences = re.findall(r'\(0008,0018\)', i) @@ -1960,7 +1960,7 @@ def test_incoming_findscu_private_tags_and_sequences(self): UploadInstance(_REMOTE, 'ZeissOCT.dcm') - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): # this query requests a private sequence and a private tag that is not present in the data + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): # this query requests a private sequence and a private tag that is not present in the data i = CallFindScu([ GetDatabasePath('ZeissOCT-cfind-query.dcm') ]) self.assertIn('Find Response: 1', i) sequences = re.findall(r'\(0407,1013\)', i) @@ -3399,11 +3399,11 @@ WaitJobDone(_REMOTE, j['ID']) self.assertEqual(1, len(DoGet(_LOCAL, '/instances'))) self.assertEqual('ORTHANC', DoGet(_LOCAL, '/instances/%s/metadata/RemoteAET' % c['ID'])) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertEqual('ORTHANC', DoGet(_REMOTE, '/jobs/%s' % j['ID'])['Content']['LocalAet']) # use the LocalAet from the "DicomModalities" config - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): DropOrthanc(_LOCAL) self.assertEqual(0, len(DoGet(_LOCAL, '/instances'))) @@ -3419,7 +3419,7 @@ # use the LocalAet from the payload - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): DropOrthanc(_LOCAL) j = DoPost(_REMOTE, '/modalities/orthanctest-with-local-aet/store', { 'LocalAet' : 'FROM-PAYLOAD', @@ -3429,7 +3429,7 @@ WaitJobDone(_REMOTE, j['ID']) self.assertEqual(2, len(DoGet(_LOCAL, '/instances'))) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertEqual('FROM-PAYLOAD', DoGet(_LOCAL, '/instances/%s/metadata/RemoteAET' % a['ID'])) self.assertEqual('FROM-PAYLOAD', DoGet(_REMOTE, '/jobs/%s' % j['ID'])['Content']['LocalAet']) @@ -4389,7 +4389,7 @@ def test_move_scu_local_aet(self): # test localAet wrt Move - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): DropOrthanc(_LOCAL) DropOrthanc(_REMOTE) DropOrthancJobs(_REMOTE) @@ -12683,10 +12683,10 @@ }) self.assertEqual(1, len(z.namelist())) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): self.assertIn('5Yp0E - BRAINIX/0 - IRM crbrale, neuro-crne/401 - MR - sT2W FLAIR/MR000001.dcm', z.namelist()) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): z = PostArchive(_REMOTE, '/tools/create-archive', { 'Resources' : [ brainix ], 'Utf8' : True, @@ -12697,6 +12697,7 @@ def test_od_of_ol_array(self): if IsOrthancVersionAbove(_REMOTE, 1, 12, 11): + # Issue reported by Yusuf Sayıta (Philips) instance = UploadInstance(_REMOTE, '2026-03-16-OD-OF-OL.dcm') ['ID'] tags = DoGet(_REMOTE, '/instances/%s/tags' % instance) @@ -12796,10 +12797,12 @@ self.assertEqual(42, v[2]) def test_deeply_nested_sequence(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if (IsThirdPartyLibraryVersionAbove(_REMOTE, 'dcmtk', 3, 7, 1) or + IsPatchApplied(_REMOTE, 'dcmtk-3.7.0-max-nested-sequence.patch')): + # note: unable to test with storescu since it does not support deeply nested sequences and crashes self.assertRaises(Exception, lambda: UploadInstance(_REMOTE, '2026-05-06-Deeply-Nested-Sequence.dcm')) - - # note: unable to test with storescu since it does not support deeply nested sequences and crashes + else: + self.skipTest('Skipping because the required resource is unavailable') # small functional test, mainly to detect memory leaks (https://github.com/orthanc-server/orthanc-builder/issues/36) @@ -12822,7 +12825,7 @@ 'Level': 'Study' }) def test_oob_monochrome_2(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): r = UploadInstance(_REMOTE, '2026-05-26-oob-monochrome-65535.dcm') self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/%s/preview' % r['ID'])) @@ -12853,7 +12856,7 @@ def test_multiframe_rgb48_raw(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): i = UploadInstance(_REMOTE, 'US-multiframe-RGB48-RAW.dcm')['ID'] # note: the execution paths are different when we are using GDCM or not (at least in 1.12.11), even if the TS is RAW !!! @@ -12884,7 +12887,7 @@ def test_multiframe_rgb48_rle(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 12): + if IsOrthancVersionAbove(_REMOTE, 1, 13, 0): i = UploadInstance(_REMOTE, 'US-multiframe-RGB48-RLE.dcm')['ID'] # note: the execution paths are different when we are using GDCM or not (at least in 1.12.11), even if the TS is RAW !!! diff -r df6abc13ad0b -r b3b5f8990265 Tests/Toolbox.py --- a/Tests/Toolbox.py Tue Aug 25 09:31:20 2026 +0200 +++ b/Tests/Toolbox.py Tue Aug 25 09:31:32 2026 +0200 @@ -347,20 +347,6 @@ return name -def IsOrthancVersionAbove(orthanc, major, minor, revision): - v = DoGet(orthanc, '/system')['Version'] - - if v.startswith('mainline'): - return True - else: - tmp = v.split('.') - a = int(tmp[0]) - b = int(tmp[1]) - c = int(tmp[2]) - return (a > major or - (a == major and b > minor) or - (a == major and b == minor and c >= revision)) - def HasExtendedFind(orthanc): v = DoGet(orthanc, '/system') @@ -588,3 +574,47 @@ raise Exception() return result + + +def IsVersionAbove(version, major, minor, revision = None): + tmp = version.split('.') + + if revision == None: + assert(len(tmp) == 2) + a = int(tmp[0]) + b = int(tmp[1]) + return (a > major or + (a == major and b > minor)) + else: + assert(len(tmp) == 3) + a = int(tmp[0]) + b = int(tmp[1]) + c = int(tmp[2]) + return (a > major or + (a == major and b > minor) or + (a == major and b == minor and c >= revision)) + + +def IsOrthancVersionAbove(orthanc, major, minor, revision): + v = DoGet(orthanc, '/system')['Version'] + + if v.startswith('mainline'): + return True + else: + return IsVersionAbove(v, major, minor, revision) + + +def IsThirdPartyLibraryVersionAbove(orthanc, library, major, minor, revision): + try: + v = DoGet(orthanc, '/system') ['ThirdPartyVersions'][library] + return IsVersionAbove(v, major, minor, revision) + except: + return False + + +def IsPatchApplied(orthanc, patch): + try: + patches = DoGet(orthanc, '/system') ['ThirdPartyPatches'] + return patch in patches + except: + return False