Mercurial > hg > orthanc-tests
changeset 779:7b29eaf4ab82 attach-custom-data
merged default -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 30 Jan 2025 17:38:39 +0100 |
parents | e1b7654fb58d (current diff) 15669253744c (diff) |
children | dd7fcf28b86b |
files | NewTests/PostgresUpgrades/docker-compose.yml NewTests/PostgresUpgrades/downgrade.sh NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh NewTests/PostgresUpgrades/test_pg_upgrades.py |
diffstat | 34 files changed, 1353 insertions(+), 435 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Wed Oct 09 11:07:09 2024 +0200 +++ b/.hgtags Thu Jan 30 17:38:39 2025 +0100 @@ -45,3 +45,5 @@ 855c3720902a1dade9accf91571ee6719e0c1eb6 Orthanc-1.12.1 ec657d1a62a6c5eebfe5255a8afe082e92d973c1 Orthanc-1.12.3 7bfc8992ab8fc44bd811bc60ebf3332303bc87ed Orthanc-1.12.4 +847b3c6b360b9b0daeab327133703c60e14e51f0 Orthanc-1.12.5 +287aae544b3133f6cecdf768f5a09dacbd75cf91 Orthanc-1.12.6
--- a/CITATION.cff Wed Oct 09 11:07:09 2024 +0200 +++ b/CITATION.cff Thu Jan 30 17:38:39 2025 +0100 @@ -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.4 -date-released: 2024-06-05 +version: 1.12.6 +date-released: 2025-01-22
--- a/GenerateConfigurationForTests.py Wed Oct 09 11:07:09 2024 +0200 +++ b/GenerateConfigurationForTests.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -143,6 +143,8 @@ config['RemoteAccessAllowed'] = True config['OverwriteInstances'] = True config['StableAge'] = 1 +config['LimitFindInstances'] = 20 +config['LimitFindResults'] = 10 config['JobsHistorySize'] = 1000 config['SynchronousCMove'] = False config['MediaArchiveSize'] = 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NewTests/CGet/docker-compose-c-get.yml Thu Jan 30 17:38:39 2025 +0100 @@ -0,0 +1,69 @@ +services: + + orthanc-a: + image: ${ORTHANC_IMAGE_UNDER_TESTS:-orthancteam/orthanc:latest} + container_name: orthanc-a + restart: unless-stopped + ports: ["8072:8042"] + volumes: ["storage-orthanc-a:/var/lib/orthanc/db"] + environment: + VERBOSE_STARTUP: "true" + VERBOSE_ENABLED: "true" + ORTHANC_JSON: | + { + "AuthenticationEnabled": false, + "DicomAet": "ORTHANCA", + "Name": "Orthanc A", + "OverwriteInstances": true, + + "DicomModalities": { + "b": { + "AET": "ORTHANCB", + "Port": 4242, + "Host": "orthanc-b" + }, + "b-move": { + "AET": "ORTHANCB", + "Port": 4242, + "Host": "orthanc-b", + "RetrieveMethod": "C-MOVE" + }, + "b-get": { + "AET": "ORTHANCB", + "Port": 4242, + "Host": "orthanc-b", + "RetrieveMethod": "C-GET" + } + } + } + + + orthanc-b: + # last version before C-GET SCU + image: orthancteam/orthanc:24.12.0 + container_name: orthanc-b + restart: unless-stopped + ports: ["8073:8042"] + volumes: ["storage-orthanc-b:/var/lib/orthanc/db"] + environment: + VERBOSE_STARTUP: "true" + VERBOSE_ENABLED: "true" + ORTHANC_JSON: | + { + "AuthenticationEnabled": false, + "DicomAet": "ORTHANCB", + "Name": "Orthanc B", + "OverwriteInstances": true, + + "DicomModalities": { + "a": { + "AET": "ORTHANCA", + "Port": 4242, + "Host": "orthanc-a" + } + } + } + +volumes: + storage-orthanc-a: + storage-orthanc-b:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NewTests/CGet/get-scp.py Thu Jan 30 17:38:39 2025 +0100 @@ -0,0 +1,104 @@ +import os + +from pydicom import dcmread +from pydicom.dataset import Dataset + +from pydicom.uid import ImplicitVRLittleEndian, ExplicitVRLittleEndian +from pynetdicom import AE, StoragePresentationContexts, evt, AllStoragePresentationContexts +from pynetdicom.sop_class import PatientRootQueryRetrieveInformationModelGet, StudyRootQueryRetrieveInformationModelGet, MRImageStorage, CTImageStorage + +import logging + +# Configure logging +logging.basicConfig(level=logging.DEBUG) + +def transform_to_transfer_syntax(dataset, target_transfer_syntax): + # Create a new dataset with the new transfer syntax + new_dataset = Dataset() + new_dataset.file_meta = Dataset() + new_dataset.file_meta.TransferSyntaxUID = target_transfer_syntax + new_dataset.update(dataset) + return new_dataset + +# Implement the handler for evt.EVT_C_GET +def handle_get(event): + """Handle a C-GET request event.""" + ds = event.identifier + if 'QueryRetrieveLevel' not in ds: + # Failure + yield 0xC000, None + return + + # Import stored SOP Instances + instances = [] + matching = [] + fdir = '/home/alain/o/orthanc-tests/Database/Brainix/Epi' + for fpath in os.listdir(fdir): + instances.append(dcmread(os.path.join(fdir, fpath))) + + if ds.QueryRetrieveLevel == 'PATIENT': + if 'PatientID' in ds: + matching = [ + inst for inst in instances if inst.PatientID == ds.PatientID + ] + elif ds.QueryRetrieveLevel == 'STUDY': + if 'StudyInstanceUID' in ds: + matching = [ + inst for inst in instances if inst.StudyInstanceUID == ds.StudyInstanceUID + ] + + print(f"GET-SCP: instances to send: {len(instances)}") + # Yield the total number of C-STORE sub-operations required + yield len(instances) + + # Yield the matching instances + for instance in matching: + # Check if C-CANCEL has been received + if event.is_cancelled: + yield (0xFE00, None) + return + + # Pending + accepted_transfer_syntax = event.assoc.accepted_contexts[0].transfer_syntax + + if accepted_transfer_syntax != instance.file_meta.TransferSyntaxUID: + transformed_instance = transform_to_transfer_syntax(instance, accepted_transfer_syntax) + yield (0xFF00, transformed_instance) + else: + yield (0xFF00, instance) + + +handlers = [(evt.EVT_C_GET, handle_get)] + +# Create application entity +ae = AE("PYNETDICOM") + +accepted_transfer_syntaxes = [ + '1.2.840.10008.1.2', # Implicit VR Little Endian + '1.2.840.10008.1.2.1', # Explicit VR Little Endian + '1.2.840.10008.1.2.2', # Explicit VR Big Endian + '1.2.840.10008.1.2.4.50', # JPEG Baseline (Process 1) + '1.2.840.10008.1.2.4.70', # JPEG Lossless, Non-Hierarchical (Process 14) +] + +# # Add the supported presentation contexts (Storage SCU) +# ae.supported_contexts = StoragePresentationContexts + +# # Accept the association requestor's proposed SCP role in the +# # SCP/SCU Role Selection Negotiation items +# for cx in ae.supported_contexts: +# cx.scp_role = True +# cx.scu_role = False + +# # Add a supported presentation context (QR Get SCP) +ae.add_supported_context(PatientRootQueryRetrieveInformationModelGet) +ae.add_supported_context(StudyRootQueryRetrieveInformationModelGet) +# ae.add_supported_context(MRImageStorage, accepted_transfer_syntaxes, scu_role=True, scp_role=True) +# ae.add_supported_context(CTImageStorage, accepted_transfer_syntaxes, scu_role=True, scp_role=True) + + +for context in AllStoragePresentationContexts: + ae.add_supported_context(context.abstract_syntax, ImplicitVRLittleEndian) + +# Start listening for incoming association requests +ae.start_server(("0.0.0.0", 11112), evt_handlers=handlers) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NewTests/CGet/test_cget.py Thu Jan 30 17:38:39 2025 +0100 @@ -0,0 +1,69 @@ +import unittest +import time +import os +import threading +from helpers import OrthancTestCase, Helpers + +from orthanc_api_client import OrthancApiClient, ChangeType +from orthanc_api_client import helpers as OrthancHelpers + +import pathlib +import subprocess +import glob +here = pathlib.Path(__file__).parent.resolve() + +class TestCGet(OrthancTestCase): + + @classmethod + def cleanup(cls): + os.chdir(here) + print("Cleaning old compose") + subprocesss_env = os.environ.copy() + subprocesss_env["ORTHANC_IMAGE_UNDER_TESTS"] = Helpers.orthanc_under_tests_docker_image + subprocess.run(["docker", "compose", "-f", "docker-compose-c-get.yml", "down", "-v", "--remove-orphans"], + env=subprocesss_env, check=True) + + @classmethod + def compose_up(cls): + # print("Pullling containers") + # subprocesss_env = os.environ.copy() + # subprocesss_env["ORTHANC_IMAGE_UNDER_TESTS"] = Helpers.orthanc_under_tests_docker_image + # subprocess.run(["docker", "compose", "-f", "docker-compose-transfers-concurrency.yml", "pull"], + # env=subprocesss_env, check=True) + + print("Compose up") + subprocesss_env = os.environ.copy() + subprocesss_env["ORTHANC_IMAGE_UNDER_TESTS"] = Helpers.orthanc_under_tests_docker_image + subprocess.run(["docker", "compose", "-f", "docker-compose-c-get.yml", "up", "-d"], + env=subprocesss_env, check=True) + + @classmethod + def setUpClass(cls): + cls.cleanup() + cls.compose_up() + + @classmethod + def tearDownClass(cls): + cls.cleanup() + pass + + def clean_start(self): + oa = OrthancApiClient("http://localhost:8072") + ob = OrthancApiClient("http://localhost:8073") + + oa.wait_started() + ob.wait_started() + + oa.delete_all_content() + ob.delete_all_content() + + return oa, ob + + def test_cget(self): + + oa, ob = self.clean_start() + + instances_ids = ob.upload_folder( here / "../../Database/Brainix") + + oa.modalities.get_study(from_modality='b', dicom_id='2.16.840.1.113669.632.20.1211.10000357775') + self.assertEqual(len(instances_ids), len(oa.instances.get_all_ids()))
--- a/NewTests/Concurrency/docker-compose-transfers-concurrency.yml Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/Concurrency/docker-compose-transfers-concurrency.yml Thu Jan 30 17:38:39 2025 +0100 @@ -9,8 +9,12 @@ ports: ["8062:8042"] volumes: ["storage-orthanc-a:/var/lib/orthanc/db"] environment: - # VERBOSE_ENABLED: "true" + VERBOSE_STARTUP: "true" + VERBOSE_ENABLED: "true" TRANSFERS_PLUGIN_ENABLED: "true" + # increase this timeout for large transfers (it is configured at 2sec by the default integration tests config) + ORTHANC__HTTP_TIMEOUT: "60" + ORTHANC__TRANSFERS__PEER_CONNECTIVITY_TIMEOUT: "10" # disable DICOMWEB to avoid the metadata cache to consume disk space after StableStudy -> difficult to compare disk sizes DICOM_WEB_PLUGIN_ENABLED: "false" ORTHANC__POSTGRESQL: | @@ -39,8 +43,12 @@ ports: ["8063:8042"] volumes: ["storage-orthanc-b:/var/lib/orthanc/db"] environment: - # VERBOSE_ENABLED: "true" + VERBOSE_STARTUP: "true" + VERBOSE_ENABLED: "true" TRANSFERS_PLUGIN_ENABLED: "true" + # increase this timeout for large transfers (it is configured at 2sec by the default integration tests config) + ORTHANC__HTTP_TIMEOUT: "60" + ORTHANC__TRANSFERS__PEER_CONNECTIVITY_TIMEOUT: "10" DICOM_WEB_PLUGIN_ENABLED: "false" ORTHANC__POSTGRESQL: | {
--- a/NewTests/Concurrency/test_concurrency.py Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/Concurrency/test_concurrency.py Thu Jan 30 17:38:39 2025 +0100 @@ -176,43 +176,49 @@ for t in workers: t.join() - # TODO: reactivate once 1.12.4 is released. It needs this fix: https://orthanc.uclouvain.be/hg/orthanc/rev/acdb8d78bf99 - # def test_concurrent_uploads_same_study(self): - # if self.o.is_orthanc_version_at_least(1, 12, 4): + def test_concurrent_uploads_same_study(self): + if self.o.is_orthanc_version_at_least(1, 12, 4): - # self.o.delete_all_content() - # self.clear_storage(storage_name=self._storage_name) + self.o.delete_all_content() + self.clear_storage(storage_name=self._storage_name) + + start_time = time.time() + workers_count = 20 + repeat_count = 5 - # start_time = time.time() - # workers_count = 20 - # repeat_count = 10 + # massively reupload the same study multiple times with OverwriteInstances set to true + # Make sure the studies, series and instances are created only once + self.execute_workers( + worker_func=worker_upload_folder, + worker_args=(self.o._root_url, here / "../../Database/Knee", repeat_count,), + workers_count=workers_count) - # # massively reupload the same study multiple times with OverwriteInstances set to true - # # Make sure the studies, series and instances are created only once - # self.execute_workers( - # worker_func=worker_upload_folder, - # worker_args=(self.o._root_url, here / "../../Database/Knee", repeat_count,), - # workers_count=workers_count) + elapsed = time.time() - start_time + print(f"TIMING test_concurrent_uploads_same_study with {workers_count} workers and {repeat_count}x repeat: {elapsed:.3f} s") + + self.assertTrue(self.o.is_alive()) - # elapsed = time.time() - start_time - # print(f"TIMING test_concurrent_uploads_same_study with {workers_count} workers and {repeat_count}x repeat: {elapsed:.3f} s") + self.assertEqual(1, len(self.o.studies.get_all_ids())) + self.assertEqual(2, len(self.o.series.get_all_ids())) + self.assertEqual(50, len(self.o.instances.get_all_ids())) - # self.assertTrue(self.o.is_alive()) + # check the computed count tags + patients = self.o.get_json("/patients?requested-tags=NumberOfPatientRelatedInstances;NumberOfPatientRelatedSeries;NumberOfPatientRelatedStudies&expand=true") + self.assertEqual(50, int(patients[0]['RequestedTags']['NumberOfPatientRelatedInstances'])) + self.assertEqual(2, int(patients[0]['RequestedTags']['NumberOfPatientRelatedSeries'])) + self.assertEqual(1, int(patients[0]['RequestedTags']['NumberOfPatientRelatedStudies'])) - # self.assertEqual(1, len(self.o.studies.get_all_ids())) - # self.assertEqual(2, len(self.o.series.get_all_ids())) - # self.assertEqual(50, len(self.o.instances.get_all_ids())) - # stats = self.o.get_json("statistics") - # self.assertEqual(1, stats.get("CountPatients")) - # self.assertEqual(1, stats.get("CountStudies")) - # self.assertEqual(2, stats.get("CountSeries")) - # self.assertEqual(50, stats.get("CountInstances")) - # self.assertEqual(4118738, int(stats.get("TotalDiskSize"))) + stats = self.o.get_json("statistics") + self.assertEqual(1, stats.get("CountPatients")) + self.assertEqual(1, stats.get("CountStudies")) + self.assertEqual(2, stats.get("CountSeries")) + self.assertEqual(50, stats.get("CountInstances")) + self.assertEqual(4118738, int(stats.get("TotalDiskSize"))) - # self.o.instances.delete(orthanc_ids=self.o.instances.get_all_ids()) + self.o.instances.delete(orthanc_ids=self.o.instances.get_all_ids()) - # self.check_is_empty() + self.check_is_empty() def test_concurrent_anonymize_same_study(self): self.o.delete_all_content() @@ -254,6 +260,13 @@ self.assertEqual(2 * (1 + workers_count * repeat_count), count_changes(changes, ChangeType.NEW_SERIES)) self.assertEqual(50 * (1 + workers_count * repeat_count), count_changes(changes, ChangeType.NEW_INSTANCE)) + # check the computed count tags + patients = self.o.get_json("/patients?requested-tags=NumberOfPatientRelatedInstances;NumberOfPatientRelatedSeries;NumberOfPatientRelatedStudies&expand=true") + for patient in patients: + self.assertEqual(50, int(patient['RequestedTags']['NumberOfPatientRelatedInstances'])) + self.assertEqual(2, int(patient['RequestedTags']['NumberOfPatientRelatedSeries'])) + self.assertEqual(1, int(patient['RequestedTags']['NumberOfPatientRelatedStudies'])) + start_time = time.time() self.o.instances.delete(orthanc_ids=self.o.instances.get_all_ids()) @@ -284,6 +297,13 @@ self.check_is_empty() + # let's upload it one more time and check the children counts + self.o.upload_folder(here / "../../Database/Knee") + patients = self.o.get_json("/patients?requested-tags=NumberOfPatientRelatedInstances;NumberOfPatientRelatedSeries;NumberOfPatientRelatedStudies&expand=true") + self.assertEqual(50, int(patients[0]['RequestedTags']['NumberOfPatientRelatedInstances'])) + self.assertEqual(2, int(patients[0]['RequestedTags']['NumberOfPatientRelatedSeries'])) + self.assertEqual(1, int(patients[0]['RequestedTags']['NumberOfPatientRelatedStudies'])) + elapsed = time.time() - start_time print(f"TIMING test_upload_delete_same_study_from_multiple_threads with {workers_count} workers and {repeat_count}x repeat ({overall_repeat}x): {elapsed:.3f} s")
--- a/NewTests/Concurrency/test_transfer.py Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/Concurrency/test_transfer.py Thu Jan 30 17:38:39 2025 +0100 @@ -45,7 +45,7 @@ @classmethod def tearDownClass(cls): - #cls.cleanup() + cls.cleanup() pass def clean_start(self): @@ -63,7 +63,7 @@ def test_push(self): oa, ob = self.clean_start() - populator = OrthancTestDbPopulator(oa, studies_count=5, random_seed=65) + populator = OrthancTestDbPopulator(oa, studies_count=2, series_count=2, instances_count=200, random_seed=65) populator.execute() all_studies_ids = oa.studies.get_all_ids() @@ -82,6 +82,16 @@ self.assertEqual(instances_count, ob.get_statistics().instances_count) self.assertEqual(disk_size, ob.get_statistics().total_disk_size) + + # check the computed count tags + studies = ob.get_json("/studies?requested-tags=NumberOfStudyRelatedInstances;NumberOfStudyRelatedSeries&expand=true") + for study in studies: + instance_count_a = len(oa.studies.get_instances_ids(study["ID"])) + instance_count_b = len(ob.studies.get_instances_ids(study["ID"])) + self.assertEqual(instance_count_a, instance_count_b) + self.assertEqual(instance_count_a, int(study['RequestedTags']['NumberOfStudyRelatedInstances'])) + self.assertEqual(2, int(study['RequestedTags']['NumberOfStudyRelatedSeries'])) + ob.delete_all_content() elapsed = time.time() - start_time @@ -91,7 +101,7 @@ def test_pull(self): oa, ob = self.clean_start() - populator = OrthancTestDbPopulator(ob, studies_count=5, random_seed=65) + populator = OrthancTestDbPopulator(ob, studies_count=2, series_count=2, instances_count=200, random_seed=65) populator.execute() all_studies_ids = ob.studies.get_all_ids() @@ -112,6 +122,16 @@ self.assertEqual(instances_count, oa.get_statistics().instances_count) self.assertEqual(disk_size, oa.get_statistics().total_disk_size) + + # check the computed count tags + studies = oa.get_json("/studies?requested-tags=NumberOfStudyRelatedInstances;NumberOfStudyRelatedSeries&expand=true") + for study in studies: + instance_count_a = len(oa.studies.get_instances_ids(study["ID"])) + instance_count_b = len(ob.studies.get_instances_ids(study["ID"])) + self.assertEqual(instance_count_a, instance_count_b) + self.assertEqual(instance_count_a, int(study['RequestedTags']['NumberOfStudyRelatedInstances'])) + self.assertEqual(2, int(study['RequestedTags']['NumberOfStudyRelatedSeries'])) + oa.delete_all_content()
--- a/NewTests/Housekeeper/test_housekeeper.py Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/Housekeeper/test_housekeeper.py Thu Jan 30 17:38:39 2025 +0100 @@ -76,7 +76,7 @@ while not completed: print('-------------- waiting for housekeeper to finish processing') time.sleep(1) - housekeeper_status = cls.o.get_json("plugins/housekeeper/status") + housekeeper_status = cls.o.get_json("/plugins/housekeeper/status") completed = (housekeeper_status["LastProcessedConfiguration"]["StorageCompressionEnabled"] == True) \ and (housekeeper_status["LastChangeToProcess"] == housekeeper_status["LastProcessedChange"])
--- a/NewTests/PostgresUpgrades/docker-compose.yml Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/PostgresUpgrades/docker-compose.yml Thu Jan 30 17:38:39 2025 +0100 @@ -17,9 +17,9 @@ AC_AUTHENTICATION_ENABLED: "false" # Orthanc previous version - orthanc-pg-15-6rev2: - image: orthancteam/orthanc:24.9.1 - container_name: orthanc-pg-15-6rev2 + orthanc-pg-15-6rev3: + image: orthancteam/orthanc:25.1.1 + container_name: orthanc-pg-15-6rev3 depends_on: [pg-15] restart: unless-stopped ports: ["8052:8042"] @@ -30,9 +30,9 @@ ORTHANC__AUTHENTICATION_ENABLED: "false" # Orthanc previous version to run the integration tests - orthanc-pg-15-6rev2-for-integ-tests: - image: orthancteam/orthanc:24.9.1 - container_name: orthanc-pg-15-6rev2-for-integ-tests + orthanc-pg-15-6rev3-for-integ-tests: + image: orthancteam/orthanc:25.1.1 + container_name: orthanc-pg-15-6rev3-for-integ-tests depends_on: [pg-15] restart: unless-stopped ports: ["8053:8042"] @@ -51,7 +51,7 @@ image: jodogne/orthanc-tests container_name: orthanc-tests depends_on: - - orthanc-pg-15-6rev2-for-integ-tests + - orthanc-pg-15-6rev3-for-integ-tests volumes: - ../../:/tests/orthanc-tests - ./wait-for-it.sh:/scripts/wait-for-it.sh
--- a/NewTests/PostgresUpgrades/downgrade.sh Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/PostgresUpgrades/downgrade.sh Thu Jan 30 17:38:39 2025 +0100 @@ -2,10 +2,13 @@ pushd /scripts +apt-get update && apt-get install -y wget mercurial +hg clone https://orthanc.uclouvain.be/hg/orthanc-databases # TODO: change attach-custom-data by the plugin version number or "default" ! -apt-get update && apt-get install -y wget && wget https://orthanc.uclouvain.be/hg/orthanc-databases/raw-file/attach-custom-data/PostgreSQL/Plugins/SQL/Downgrades/Rev3ToRev2.sql -psql -U postgres -f Rev3ToRev2.sql +hg update -r attach-custom-data +psql -U postgres -f /scripts/orthanc-databases/PostgreSQL/Plugins/SQL/Downgrades/Rev4ToRev3.sql +psql -U postgres -f /scripts/orthanc-databases/PostgreSQL/Plugins/SQL/Downgrades/Rev3ToRev2.sql # if you want to test a downgrade procedure, you may use this code ... # psql -U postgres -f downgrade.sql -popd \ No newline at end of file +popd
--- a/NewTests/PostgresUpgrades/orthanc-for-integ-tests.json Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/PostgresUpgrades/orthanc-for-integ-tests.json Thu Jan 30 17:38:39 2025 +0100 @@ -114,8 +114,8 @@ "IngestTranscodingOfCompressed": true, "IngestTranscodingOfUncompressed": true, "JobsHistorySize": 1000, - "LimitFindInstances": 0, - "LimitFindResults": 0, + "LimitFindInstances": 20, + "LimitFindResults": 10, "LoadPrivateDictionary": true, "LogExportedResources": true, "LuaScripts": [],
--- a/NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh Thu Jan 30 17:38:39 2025 +0100 @@ -2,6 +2,6 @@ set -ex -/scripts/wait-for-it.sh orthanc-pg-15-6rev2-for-integ-tests:8042 -t 60 -# python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-6rev2-for-integ-tests --force --docker -- -v Orthanc.test_lua_deadlock -python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-6rev2-for-integ-tests --force --docker -- -v +/scripts/wait-for-it.sh orthanc-pg-15-6rev3-for-integ-tests:8042 -t 60 +# python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-6rev3-for-integ-tests --force --docker -- -v Orthanc.test_lua_deadlock +python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-6rev3-for-integ-tests --force --docker -- -v
--- a/NewTests/PostgresUpgrades/test_pg_upgrades.py Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/PostgresUpgrades/test_pg_upgrades.py Thu Jan 30 17:38:39 2025 +0100 @@ -38,16 +38,16 @@ subprocess.run(["docker", "compose", "up", "pg-15", "-d"], check=True) wait_container_healthy("pg-15") - print("Launching Orthanc with 6rev2 DB") - subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev2", "-d"], check=True) + print("Launching Orthanc with DB 6rev3") + subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev3", "-d"], check=True) o = OrthancApiClient("http://localhost:8052") o.wait_started() instances = o.upload_folder(here / "../../Database/Knee") - print("Stopping Orthanc with 6rev2 DB") - subprocess.run(["docker", "compose", "stop", "orthanc-pg-15-6rev2"], check=True) + print("Stopping Orthanc with DB 6rev3") + subprocess.run(["docker", "compose", "stop", "orthanc-pg-15-6rev3"], check=True) time.sleep(2) print("Launching newest Orthanc") @@ -115,12 +115,12 @@ subprocess.run(["docker", "compose", "stop", "orthanc-pg-15-under-tests"], check=True) time.sleep(2) - print("Downgrading Orthanc DB to 6rev2") + print("Downgrading Orthanc DB to 6rev3") subprocess.run(["docker", "exec", "pg-15", "./scripts/downgrade.sh"], check=True) time.sleep(2) - print("Launching previous Orthanc (DB 6rev2)") - subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev2", "-d"], check=True) + print("Launching previous Orthanc (DB 6rev3)") + subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev3", "-d"], check=True) o = OrthancApiClient("http://localhost:8052") o.wait_started() @@ -135,10 +135,6 @@ self.assertEqual(0, int(o.get_json('statistics')['TotalDiskSize'])) print("run the integration tests after a downgrade") - # first create the containers (orthanc-tests + orthanc-pg-15-6rev2-for-integ-tests) so they know each other - subprocess.run(["docker", "compose", "create", "orthanc-tests"], check=True) - - subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev2-for-integ-tests", "-d"], check=True) o = OrthancApiClient("http://localhost:8053", user="alice", pwd="orthanctest") o.wait_started()
--- a/NewTests/README Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/README Thu Jan 30 17:38:39 2025 +0100 @@ -50,8 +50,10 @@ Run the Housekeeper tests with your locally build version and break between preparation and execution to allow you to start your debugger. +// --orthanc_under_tests_exe=/home/alain/o/build/orthanc/Orthanc \ + python3 NewTests/main.py --pattern=Housekeeper.test_housekeeper.TestHousekeeper.test_before_after_reconstruction \ - --orthanc_under_tests_exe=/home/alain/o/build/orthanc/Orthanc \ + --orthanc_under_tests_docker_image=orthancteam/orthanc:current \ --orthanc_under_tests_http_port=8043 \ --plugin=/home/alain/o/build/orthanc/libHousekeeper.so \ --break_after_preparation @@ -195,7 +197,7 @@ Read Only PG: --------------- +------------ Run the Read Only tests with your locally build version and break before execution to allow you to start your debugger. @@ -210,4 +212,12 @@ python3 NewTests/main.py --pattern=ReadOnly.test_readonly_pg.TestReadOnlyPG.* \ --orthanc_under_tests_docker_image=orthancteam/orthanc:current \ - --orthanc_under_tests_http_port=8043 \ No newline at end of file + --orthanc_under_tests_http_port=8043 + +C-Get: +----- + +with Docker: + +python3 NewTests/main.py --pattern=CGet.test_cget.TestCGet.* \ + --orthanc_under_tests_docker_image=orthancteam/orthanc-pre-release:2025.01.20 \ No newline at end of file
--- a/NewTests/requirements.txt Wed Oct 09 11:07:09 2024 +0200 +++ b/NewTests/requirements.txt Thu Jan 30 17:38:39 2025 +0100 @@ -1,3 +1,3 @@ -orthanc-api-client>=0.16.2 +orthanc-api-client>=0.18.0 orthanc-tools>=0.13.0 uvicorn \ No newline at end of file
--- a/Plugins/CGet/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/CGet/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/DicomWeb/DicomWeb.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/DicomWeb/DicomWeb.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/DicomWeb/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/DicomWeb/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -6,8 +6,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -303,7 +303,7 @@ { 'Resources' : [ 'nope' ], 'Synchronous' : True })) # inexisting resource - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + if IsPluginVersionAtLeast(ORTHANC, "dicom-web", 1, 18, 0): l = 4 # "Server" has been added else: l = 3 # For >= 1.10.1 @@ -315,7 +315,7 @@ self.assertEqual(l, len(r)) self.assertEqual("0a9b3153-2512774b-2d9580de-1fc3dcf6-3bd83918", r['Resources']['Studies'][0]) - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + if IsPluginVersionAtLeast(ORTHANC, "dicom-web", 1, 18, 0): self.assertEqual("sample", r['Server']) # series @@ -607,7 +607,7 @@ self.assertEqual(u'王^小東', pn['Value'][0]['Ideographic']) # new derivated test added later - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 18, 0): + if IsPluginVersionAtLeast(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 @@ -670,7 +670,7 @@ # WADO-RS RetrieveFrames shall transcode ExplicitBigEndian to ExplicitLittleEndian # https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=219 - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 17, 0): + if IsPluginVersionAtLeast(ORTHANC, "dicom-web", 1, 17, 0): UploadInstance(ORTHANC, 'TransferSyntaxes/1.2.840.10008.1.2.2.dcm') @@ -698,7 +698,7 @@ self.assertTrue('00280010' in a[0]) self.assertEqual(512, a[0]['00280010']['Value'][0]) - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 17, 0): + if IsPluginVersionAtLeast(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]) @@ -1729,7 +1729,7 @@ }) self.assertIn("https://my-domain/dicom-web", m[0][u'7FE00010']['BulkDataURI']) - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 13, 1): + if IsPluginVersionAtLeast(ORTHANC, "dicom-web", 1, 13, 1): m = DoGet(ORTHANC, '/dicom-web/studies/%s/metadata' % studyId, headers= { 'X-Forwarded-Host': 'my-domain', 'X-Forwarded-Proto': 'https' @@ -1764,7 +1764,7 @@ self.assertEqual(1, len(m)) self.assertEqual(studyUid, m[0]['0020000D']['Value'][0]) - if IsPluginVersionAbove(ORTHANC, "dicom-web", 1, 13, 1) and IsOrthancVersionAbove(ORTHANC, 1, 12, 1): + if IsPluginVersionAtLeast(ORTHANC, "dicom-web", 1, 13, 1) and IsOrthancVersionAbove(ORTHANC, 1, 12, 1): # This fails on DICOMweb <= 1.13 because of the "; q=.2", # since multiple accepts were not supported # https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=216
--- a/Plugins/Recycling/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/Recycling/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/Transfers/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/Transfers/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -6,8 +6,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/WSI/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/WSI/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # -*- coding: utf-8 -*- @@ -6,8 +6,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -144,6 +144,12 @@ return tiff +def IsWhite(self, image): + e = image.getextrema() + self.assertEqual(3, len(e)) + return e == ((255, 255), (255, 255), (255, 255)) + + class Orthanc(unittest.TestCase): def setUp(self): if (sys.version_info >= (3, 0)): @@ -461,7 +467,7 @@ self.assertEqual(3, len(info['sizes'])) - if IsPluginVersionAbove(ORTHANC, "wsi", 2, 1, 0): # https://orthanc.uclouvain.be/hg/orthanc-wsi/rev/9dc7f1e8716d + if IsPluginVersionAtLeast(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']) @@ -578,6 +584,149 @@ self.assertEqual(height, info['tiles'][0]['height']) self.assertEqual([ 1 ], info['tiles'][0]['scaleFactors']) + def test_on_the_fly(self): + a = UploadInstance(ORTHANC, 'Implicit-vr-us-palette.dcm') ['ID'] + + self.assertRaises(Exception, lambda: DoGet(ORTHANC, '/wsi/frames-pyramids/%s/1' % a)) + + info = DoGet(ORTHANC, '/wsi/frames-pyramids/%s/0' % a) + self.assertEqual('#ffffff', info['BackgroundColor']) + self.assertEqual(0, info['FrameNumber']) + self.assertEqual(a, info['ID']) + self.assertEqual(2, len(info['Resolutions'])) + self.assertEqual(1, info['Resolutions'][0]) + self.assertEqual(2, info['Resolutions'][1]) + self.assertEqual(2, len(info['Sizes'])) + self.assertEqual(832, info['Sizes'][0][0]) # Default padding is (64,64) for an image size (800,600) + self.assertEqual(640, info['Sizes'][0][1]) + self.assertEqual(416, info['Sizes'][1][0]) + self.assertEqual(320, info['Sizes'][1][1]) + self.assertEqual(2, info['TilesCount'][0][0]) + self.assertEqual(2, info['TilesCount'][0][1]) + self.assertEqual(1, info['TilesCount'][1][0]) + self.assertEqual(1, info['TilesCount'][1][1]) + self.assertEqual(512, info['TilesSizes'][0][0]) + self.assertEqual(512, info['TilesSizes'][0][1]) + self.assertEqual(512, info['TilesSizes'][1][0]) + self.assertEqual(512, info['TilesSizes'][1][1]) + self.assertEqual(832, info['TotalWidth']) + self.assertEqual(640, info['TotalHeight']) + + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/0/0' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/1/0' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/0/1' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/1/1' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/1/2' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/2/1' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/0/2/2' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/1/0/0' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/1/0/1' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/1/1/0' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/frames-tiles/%s/0/1/1/1' % a) + self.assertEqual((512, 512), tile.size) + self.assertTrue(IsWhite(self, tile)) + + def test_iiif_on_the_fly(self): + a = UploadInstance(ORTHANC, 'Implicit-vr-us-palette.dcm') ['ID'] + + uri = '/wsi/iiif/frames-pyramids/%s/0' % a + manifest = DoGet(ORTHANC, uri + '/manifest.json') + + self.assertEqual('http://iiif.io/api/presentation/3/context.json', manifest['@context']) + self.assertEqual('http://localhost:8042%s/manifest.json' % uri, manifest['id']) + + self.assertEqual(1, len(manifest['items'])) + self.assertEqual(1, len(manifest['items'][0]['items'])) + self.assertEqual(1, len(manifest['items'][0]['items'][0]['items'])) + + self.assertEqual('Manifest', manifest['type']) + self.assertEqual('Canvas', manifest['items'][0]['type']) + self.assertEqual('AnnotationPage', manifest['items'][0]['items'][0]['type']) + self.assertEqual('Annotation', manifest['items'][0]['items'][0]['items'][0]['type']) + + self.assertEqual(' - US - - ', manifest['label']['en'][0]) + self.assertEqual('http://localhost:8042%s/canvas/p1' % uri, manifest['items'][0]['id']) + + annotation = manifest['items'][0]['items'][0] + self.assertEqual('http://localhost:8042%s/page/p1/1' % uri, annotation['id']) + self.assertEqual('AnnotationPage', annotation['type']) + self.assertEqual(1, len(annotation['items'])) + + item = manifest['items'][0]['items'][0]['items'][0] + self.assertEqual('image/jpeg', item['body']['format']) + self.assertEqual('Image', item['body']['type']) + self.assertEqual(832, item['body']['width']) + self.assertEqual(640, item['body']['height']) + self.assertEqual('http://localhost:8042%s/full/max/0/default.jpg' % uri, item['body']['id']) + self.assertEqual(1, len(item['body']['service'])) + self.assertEqual('http://localhost:8042%s' % uri, item['body']['service'][0]['id']) + self.assertEqual('level0', item['body']['service'][0]['profile']) + self.assertEqual('ImageService3', item['body']['service'][0]['type']) + self.assertEqual('http://localhost:8042%s/annotation/p1-image' % uri, item['id']) + self.assertEqual('painting', item['motivation']) + self.assertEqual('Annotation', item['type']) + self.assertEqual(manifest['items'][0]['id'], item['target']) + + self.assertEqual(832, manifest['items'][0]['width']) # Default padding is (64,64) for an image size (800,600) + self.assertEqual(640, manifest['items'][0]['height']) + + info = DoGet(ORTHANC, uri + '/info.json') + self.assertEqual('http://iiif.io/api/image/3/context.json', info['@context']) + self.assertEqual('http://iiif.io/api/image', info['protocol']) + self.assertEqual('http://localhost:8042%s' % uri, info['id']) + self.assertEqual('level0', info['profile']) + self.assertEqual('ImageService3', info['type']) + self.assertEqual(832, info['width']) + self.assertEqual(640, info['height']) + + self.assertEqual(2, len(info['sizes'])) + self.assertEqual(320, info['sizes'][0]['height']) + self.assertEqual(416, info['sizes'][0]['width']) + self.assertEqual(640, info['sizes'][1]['height']) + self.assertEqual(832, info['sizes'][1]['width']) + + self.assertEqual(1, len(info['tiles'])) + self.assertEqual(512, info['tiles'][0]['width']) + self.assertEqual(512, info['tiles'][0]['height']) + self.assertEqual([1, 2], info['tiles'][0]['scaleFactors']) + + # Those are the calls made by Mirador + tile = GetImage(ORTHANC, '/wsi/iiif/frames-pyramids/%s/0/full/416,320/0/default.jpg' % a) + self.assertEqual((416, 320), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/iiif/frames-pyramids/%s/0/0,0,512,512/512,512/0/default.jpg' % a) + self.assertEqual((512, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/iiif/frames-pyramids/%s/0/512,0,320,512/320,512/0/default.jpg' % a) + self.assertEqual((320, 512), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/iiif/frames-pyramids/%s/0/0,512,512,128/512,128/0/default.jpg' % a) + self.assertEqual((512, 128), tile.size) + self.assertFalse(IsWhite(self, tile)) + tile = GetImage(ORTHANC, '/wsi/iiif/frames-pyramids/%s/0/512,512,320,128/320,128/0/default.jpg' % a) + self.assertEqual((320, 128), tile.size) + self.assertFalse(IsWhite(self, tile)) + try: print('\nStarting the tests...') unittest.main(argv = [ sys.argv[0] ] + args.options)
--- a/Plugins/WebDav/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/WebDav/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/Worklists/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Plugins/Worklists/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -6,8 +6,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/README Wed Oct 09 11:07:09 2024 +0200 +++ b/README Thu Jan 30 17:38:39 2025 +0100 @@ -156,6 +156,9 @@ # python CheckDicomTls.py --force OrthancCheckClient +To run the Recycling tests: +# python2 Plugins/Recycling/Run.py --force + (Option 2b) With Docker:
--- a/Tests/CheckDicomTls.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/CheckDicomTls.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Tests/CheckHttpServerSecurity.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/CheckHttpServerSecurity.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Tests/CheckIngestTranscoding.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/CheckIngestTranscoding.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Tests/CheckScuTranscoding.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/CheckScuTranscoding.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Tests/CheckZipStreams.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/CheckZipStreams.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -128,6 +128,8 @@ Assert(streaming == True or streaming == None) try: + #if (sys.version_info >= (3, 0)): + # z = bytearray(z, 'utf-8') Toolbox.ParseArchive(z) print('error, got valid archive') queue.put(False) # The archive is not corrupted as expected
--- a/Tests/Run.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/Run.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Tests/Tests.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/Tests.py Thu Jan 30 17:38:39 2025 +0100 @@ -6,8 +6,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -722,22 +722,33 @@ def test_archive(self): UploadInstance(_REMOTE, 'Knee/T1/IM-0001-0001.dcm') - UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0003.dcm') kneePatient = 'ca29faea-b6a0e17f-067743a1-8b778011-a48b2a17' kneeStudy = DoGet(_REMOTE, '/studies')[0] kneeSeries = DoGet(_REMOTE, '/series')[0] z = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) self.assertEqual(2, len(z.namelist())) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T2W_TSE/MR000003.dcm', z.namelist()) + else: + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) z = GetArchive(_REMOTE, '/studies/%s/archive' % kneeStudy) self.assertEqual(2, len(z.namelist())) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T2W_TSE/MR000003.dcm', z.namelist()) + else: + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) z = GetArchive(_REMOTE, '/series/%s/archive' % kneeSeries) self.assertEqual(1, len(z.namelist())) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + else: + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-0001.dcm') brainixPatient = '16738bc3-e47ed42a-43ce044c-a3414a45-cb069bd0' @@ -751,8 +762,12 @@ 'Resources' : [ brainixPatient, kneePatient ] }) self.assertEqual(3, len(z.namelist())) - self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000001.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + else: + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) z = PostArchive(_REMOTE, '/patients/%s/archive' % kneePatient, { 'Synchronous' : True @@ -764,16 +779,24 @@ 'Resources' : [ brainixStudy, kneeStudy ] }) self.assertEqual(3, len(z.namelist())) - self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000001.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + else: + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) # archive with 1 patient & 1 study z = PostArchive(_REMOTE, '/tools/create-archive', { 'Resources' : [ brainixPatient, kneeStudy ] }) self.assertEqual(3, len(z.namelist())) - self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) - self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000001.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) + else: + self.assertIn('5Yp0E BRAINIX/0 IRM crbrale neurocrne/MR sT2WFLAIR/MR000000.dcm', z.namelist()) + self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) def test_archive_with_patient_ids_collision(self): @@ -1307,7 +1330,7 @@ self.assertTrue('LastUpdate' in m) m = DoGet(_REMOTE, '/series/%s/metadata' % series) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): self.assertEqual(4, len(m)) self.assertTrue('MainDicomSequences' in m) # since RequestAttributeSequence is now in the MainDicomTags elif IsOrthancVersionAbove(_REMOTE, 1, 11, 0): @@ -1567,7 +1590,7 @@ series = DoGet(_REMOTE, '/series')[0] m = DoGet(_REMOTE, '/series/%s/metadata' % series) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): self.assertEqual(4, len(m)) self.assertTrue('MainDicomSequences' in m) # since RequestAttributeSequence is now in the MainDicomTags elif IsOrthancVersionAbove(_REMOTE, 1, 11, 0): @@ -1979,12 +2002,12 @@ self.assertTrue('0010,0010' in DoGet(_REMOTE, '/patients/%s/module' % p)) self.assertTrue('PatientName' in DoGet(_REMOTE, '/patients/%s/module?simplify' % p)) - self.assertTrue('0010,0010' in DoGet(_REMOTE, '/studies/%s/module-patient' % p)) - self.assertTrue('PatientName' in DoGet(_REMOTE, '/studies/%s/module-patient?simplify' % p)) + self.assertTrue('0010,0010' in DoGet(_REMOTE, '/studies/%s/module-patient' % s)) + self.assertTrue('PatientName' in DoGet(_REMOTE, '/studies/%s/module-patient?simplify' % s)) self.assertTrue('0008,1030' in DoGet(_REMOTE, '/studies/%s/module' % s)) self.assertTrue('StudyDescription' in DoGet(_REMOTE, '/studies/%s/module?simplify' % s)) - self.assertTrue('0008,103e' in DoGet(_REMOTE, '/series/%s/module' % p)) - self.assertTrue('SeriesDescription' in DoGet(_REMOTE, '/series/%s/module?simplify' % p)) + self.assertTrue('0008,103e' in DoGet(_REMOTE, '/series/%s/module' % t)) + self.assertTrue('SeriesDescription' in DoGet(_REMOTE, '/series/%s/module?simplify' % t)) self.assertTrue('0008,0018' in DoGet(_REMOTE, '/instances/%s/module' % a)) self.assertTrue('SOPInstanceUID' in DoGet(_REMOTE, '/instances/%s/module?simplify' % a)) @@ -2111,8 +2134,38 @@ 'Query' : { 'StationName' : 'SMR4-MP3' }}) self.assertEqual(1, len(a)) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): + a = DoPost(_REMOTE, '/tools/count-resources', { 'Level' : 'Series', + 'CaseSensitive' : False, + 'Query' : { 'StationName' : 'SMR4-MP3' }}) + self.assertEqual(1, len(a)) + self.assertEqual(1, a['Count']) + def test_rest_find(self): + def CheckFind(query, expectedAnswers, shouldThrow = False): + + if not shouldThrow: + a = DoPost(_REMOTE, '/tools/find', query) + self.assertEqual(expectedAnswers, len(a)) + return a + else: + self.assertRaises(Exception, lambda: DoPost(_REMOTE, '/tools/find', query)) + + + + def CheckCount(query, expectedAnswers, shouldThrow = False): + if not shouldThrow: + b = DoPost(_REMOTE, '/tools/count-resources', query) + self.assertEqual(1, len(b)) + self.assertEqual(expectedAnswers, b['Count']) + return b + else: + self.assertRaises(Exception, lambda: DoPost(_REMOTE, '/tools/count-resources', query)) + + + + # Upload 12 instances for i in range(3): UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-000%d.dcm' % (i + 1)) @@ -2121,175 +2174,248 @@ UploadInstance(_REMOTE, 'Knee/T2/IM-0001-000%d.dcm' % (i + 1)) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'CaseSensitive' : True, - 'Query' : { - 'PatientName' : '*NE*', - 'StudyDate': '20080819' - }}) - self.assertEqual(1, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'CaseSensitive' : True, - 'Query' : { - 'PatientName' : '*NE*', - 'PatientBirthDate': '20080101-20081231', - 'PatientSex': '0000' - }}) - self.assertEqual(1, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', - 'CaseSensitive' : True, - 'Query' : { - 'StudyInstanceUID' : '2.16.840.1.113669.632.20.121711.10000160881' - }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', - 'CaseSensitive' : True, - 'Query' : { - 'StudyInstanceUID' : '2.16.840.1.113669.632.20.121711.10000160881', - 'SeriesInstanceUID': '1.3.46.670589.11.17521.5.0.3124.2008081908564160709' - }}) - self.assertEqual(3, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', - 'CaseSensitive' : True, - 'Query' : { - 'StudyDate' : '20080818-20080820', - 'Modality': 'MR' - }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'CaseSensitive' : True, - 'Query' : { - 'StudyDate' : '20080818-', - 'ModalitiesInStudy': 'MR' - }}) - self.assertEqual(1, len(a)) - - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', - 'CaseSensitive' : False, - 'Query' : { 'PatientName' : 'BRAINIX' }}) - self.assertEqual(1, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', - 'CaseSensitive' : False, - 'Query' : { 'PatientName' : 'BRAINIX\\KNEE\\NOPE' }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', - 'CaseSensitive' : False, - 'Query' : { 'PatientName' : '*n*' }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', - 'CaseSensitive' : True, - 'Query' : { 'PatientName' : '*n*' }}) - self.assertEqual(0, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Expand' : True, - 'Level' : 'Patient', - 'CaseSensitive' : False, - 'Query' : { 'PatientName' : '*ne*' }}) - self.assertEqual(1, len(a)) + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'PatientName' : '*NE*', + 'StudyDate': '20080819' + }} + CheckFind(query, 1) + CheckCount(query, 1, True) # tools/count does not support CaseSensitive + + query = { 'Level' : 'Study', + 'CaseSensitive' : False, + 'Query' : { + 'PatientName' : '*NE*', + 'StudyDate': '20080819' + }} + CheckFind(query, 1) + CheckCount(query, 1) + + query = { 'Level' : 'Study', + 'CaseSensitive' : False, + 'Query' : { + 'PatientName' : '*NE*', + 'StudyDate': '20080819' + }, + 'Since' : 1 + } + if HasExtendedFind(_REMOTE): # usage of 'Since' is not reliable without ExtendedFind + CheckFind(query, 0) + CheckCount(query, 1) # Since is ignored in tools/count-resources + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'PatientName' : '*NE*', + 'PatientBirthDate': '20080101-20081231', + 'PatientSex': '0000' + }} + CheckFind(query, 1) + CheckCount(query, 1, True) # tools/count-resources does not support CaseSensitive + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'PatientName' : '*NE*', + 'PatientBirthDate': '20080101-20081231', + 'PatientSex': '0000' + }, + 'Since': 1} + if HasExtendedFind(_REMOTE): # usage of 'Since' is not reliable without ExtendedFind + CheckFind(query, 0, True) # 'CaseSensitive' can not be combined with 'Since' + CheckCount(query, 0, True) # tools/count-resources does not support CaseSensitive + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'PatientName' : '*ne*', + 'PatientBirthDate': '20080101-20081231', + 'PatientSex': '0000' + } + } + CheckFind(query, 0) + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'PatientName' : '*ne*', + 'PatientBirthDate': '20080101-20081231', + 'PatientSex': '0000' + }, + 'Since': 1} + CheckFind(query, 0, True) # 'CaseSensitive' can not be combined with 'Since' when searching for lower case (because the DicomIdentifiers are stored in UPPERCASE) + + query = { 'Level' : 'Series', + 'CaseSensitive' : True, + 'Query' : { + 'StudyInstanceUID' : '2.16.840.1.113669.632.20.121711.10000160881' + }} + CheckFind(query, 2) + CheckCount(query, 2, True) # tools/count-resources does not support CaseSensitive + + query = { 'Level' : 'Instance', + 'CaseSensitive' : True, + 'Query' : { + 'StudyInstanceUID' : '2.16.840.1.113669.632.20.121711.10000160881', + 'SeriesInstanceUID': '1.3.46.670589.11.17521.5.0.3124.2008081908564160709' + }} + CheckFind(query, 3) + CheckCount(query, 3, True) # tools/count-resources does not support CaseSensitive + + query = { 'Level' : 'Series', + 'CaseSensitive' : True, + 'Query' : { + 'StudyDate' : '20080818-20080820', + 'Modality': 'MR' + }} + CheckFind(query, 2) + CheckCount(query, 2, True) # tools/count-resources does not support CaseSensitive + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { + 'StudyDate' : '20080818-', + 'ModalitiesInStudy': 'MR' + }} + CheckFind(query, 1) + + query = { 'Level' : 'Study', + 'CaseSensitive' : False, + 'Query' : { + 'StudyDate' : '20080818-', + 'ModalitiesInStudy': 'MR' + }, + 'Since': 1} + + if HasExtendedFind(_REMOTE): # usage of 'Since' is not reliable without ExtendedFind + CheckFind(query, 0) + CheckCount(query, 1) # Since is ignored in tools/count-resources + + query = { 'Level' : 'Patient', + 'CaseSensitive' : False, + 'Query' : { 'PatientName' : 'BRAINIX' }} + CheckFind(query, 1) + CheckCount(query, 1) + + query = { 'Level' : 'Patient', + 'CaseSensitive' : False, + 'Query' : { 'PatientName' : 'BRAINIX\\KNEE\\NOPE' }} + CheckFind(query, 2) + CheckCount(query, 2) + + query = { 'Level' : 'Patient', + 'CaseSensitive' : False, + 'Query' : { 'PatientName' : '*n*' }} + CheckFind(query, 2) + CheckCount(query, 2) + + query = { 'Level' : 'Patient', + 'CaseSensitive' : True, + 'Query' : { 'PatientName' : '*n*' }} + CheckFind(query, 0) + CheckCount(query, 0, True) # "CaseSensitive" is not available in "/tools/count-resources" + + query = { 'Expand' : True, + 'Level' : 'Patient', + 'CaseSensitive' : False, + 'Query' : { 'PatientName' : '*ne*' }} + a = CheckFind(query, 1) self.assertEqual('20080822', a[0]['MainDicomTags']['PatientBirthDate']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', - 'CaseSensitive' : True, - 'Query' : { 'PatientName' : '*ne*' }}) - self.assertEqual(0, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'CaseSensitive' : True, - 'Query' : { 'PatientName' : '*NE*' }}) - self.assertEqual(1, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', - 'CaseSensitive' : True, - 'Query' : { 'PatientName' : '*NE*' }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', - 'CaseSensitive' : True, - 'Query' : { 'PatientName' : '*NE*' }}) - self.assertEqual(6, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patient', 'Query' : { }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', 'Query' : { }}) - self.assertEqual(2, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', 'Query' : { }}) - self.assertEqual(4, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', 'Query' : { }}) - self.assertEqual(12, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '20061201-20061201' }}) - self.assertEqual(1, len(a)) + query = { 'Level' : 'Patient', + 'CaseSensitive' : True, + 'Query' : { 'PatientName' : '*ne*' }} + CheckFind(query, 0) + + query = { 'Level' : 'Study', + 'CaseSensitive' : True, + 'Query' : { 'PatientName' : '*NE*' }} + CheckFind(query, 1) + + query = { 'Level' : 'Series', + 'CaseSensitive' : True, + 'Query' : { 'PatientName' : '*NE*' }} + CheckFind(query, 2) + + query = { 'Level' : 'Instance', + 'CaseSensitive' : True, + 'Query' : { 'PatientName' : '*NE*' }} + CheckFind(query, 6) + + query = { 'Level' : 'Patient', 'Query' : { }} + CheckFind(query, 2) + + query = { 'Level' : 'Study', 'Query' : { }} + CheckFind(query, 2) + + query = { 'Level' : 'Series', 'Query' : { }} + CheckFind(query, 4) + + query = { 'Level' : 'Instance', 'Query' : { }} + CheckFind(query, 12) + + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20061201-20061201' }} + a = CheckFind(query, 1) self.assertEqual('BRAINIX', a[0]['PatientMainDicomTags']['PatientName']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '20061201-20091201' }}) - self.assertEqual(2, len(a)) + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20061201-20091201' }} + a = CheckFind(query, 2) for i in range(2): self.assertTrue(a[i]['PatientMainDicomTags']['PatientName'] in ['BRAINIX', 'KNEE']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Query' : { 'StudyDate' : '20061202-20061202' }}) - self.assertEqual(0, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '-20061201' }}) - self.assertEqual(1, len(a)) + query = { 'Level' : 'Study', + 'Query' : { 'StudyDate' : '20061202-20061202' }} + CheckFind(query, 0) + + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '-20061201' }} + a = CheckFind(query, 1) self.assertEqual('BRAINIX', a[0]['PatientMainDicomTags']['PatientName']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '-20051201' }}) - self.assertEqual(0, len(a)) - - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '20061201-' }}) - self.assertEqual(2, len(a)) + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '-20051201' }} + CheckFind(query, 0) + + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20061201-' }} + a = CheckFind(query, 2) for i in range(2): self.assertTrue(a[i]['PatientMainDicomTags']['PatientName'] in ['BRAINIX', 'KNEE']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '20061202-' }}) - self.assertEqual(1, len(a)) + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20061202-' }} + a = CheckFind(query, 1) self.assertEqual('KNEE', a[0]['PatientMainDicomTags']['PatientName']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - 'Expand' : True, - 'Query' : { 'StudyDate' : '20080819-' }}) - self.assertEqual(1, len(a)) + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20080819-' }} + a = CheckFind(query, 1) self.assertEqual('KNEE', a[0]['PatientMainDicomTags']['PatientName']) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Study', - '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)) + query = { 'Level' : 'Study', + 'Expand' : True, + 'Query' : { 'StudyDate' : '20080820-' }} + CheckFind(query, 0) + + query = { 'Level' : 'Series', + 'Expand' : True, + 'Query' : { 'PatientPosition' : 'HFS' }} + CheckFind(query, 2, False) # "PatientPosition" is not a main DICOM tag, so unavailable in "/tools/count-resources" + + query = { 'Level' : 'Series', + 'Expand' : False, + 'Query' : { 'PatientPosition' : 'HFS' }} + CheckFind(query, 2, False) # "PatientPosition" is not a main DICOM tag, so unavailable in "/tools/count-resources" def test_rest_query_retrieve(self): @@ -2529,21 +2655,21 @@ self.assertEqual('887', i[i.keys()[0]]['PatientID']) self.assertEqual('887', i[i.keys()[1]]['PatientID']) - i = DoGet(_REMOTE, '/patients/%s/instances-tags?simplify' % DoGet(_REMOTE, '/studies')[0]) + i = DoGet(_REMOTE, '/studies/%s/instances-tags?simplify' % DoGet(_REMOTE, '/studies')[0]) self.assertEqual(2, len(i)) self.assertEqual('887', i[i.keys()[0]]['PatientID']) self.assertEqual('887', i[i.keys()[1]]['PatientID']) self.assertEqual(2, len(DoGet(_REMOTE, '/series'))) - i = DoGet(_REMOTE, '/patients/%s/instances-tags?simplify' % DoGet(_REMOTE, '/series')[0]) + i = DoGet(_REMOTE, '/series/%s/instances-tags?simplify' % DoGet(_REMOTE, '/series')[0]) self.assertEqual(1, len(i)) self.assertEqual('887', i[i.keys()[0]]['PatientID']) - i = DoGet(_REMOTE, '/patients/%s/instances-tags?simplify' % DoGet(_REMOTE, '/series')[1]) + i = DoGet(_REMOTE, '/series/%s/instances-tags?simplify' % DoGet(_REMOTE, '/series')[1]) self.assertEqual(1, len(i)) self.assertEqual('887', i[i.keys()[0]]['PatientID']) - i = DoGet(_REMOTE, '/patients/%s/instances-tags?short' % DoGet(_REMOTE, '/series')[1]) + i = DoGet(_REMOTE, '/series/%s/instances-tags?short' % DoGet(_REMOTE, '/series')[1]) self.assertEqual(1, len(i)) self.assertEqual('887', i[i.keys()[0]]['0010,0020']) @@ -3017,7 +3143,7 @@ self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients&since=10' % i)) self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients&limit=10' % i)) - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged: # with ExtendedFind, the limit=0 means no-limit like in /tools/find + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): # with ExtendedFind, the limit=0 means no-limit like in /tools/find self.assertEqual(2, len(DoGet(_REMOTE, '/patients?since=0&limit=0'))) self.assertEqual(1, len(DoGet(_REMOTE, '/patients?since=1&limit=0'))) self.assertEqual(0, len(DoGet(_REMOTE, '/patients?since=2&limit=0'))) @@ -4273,11 +4399,12 @@ 'Limit' : 4 }) self.assertEqual(4, len(a)) - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', - 'Query' : { 'PatientName' : 'B*' }, - 'Since' : 2, - 'Limit' : 4 }) - self.assertEqual(2, len(a)) + if HasExtendedFind(_REMOTE): # usage of since is not reliable without ExtendedFind + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'Query' : { 'PatientName' : 'B*' }, + 'Since' : 2, + 'Limit' : 4 }) + self.assertEqual(2, len(a)) a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', 'Query' : { 'PatientName' : 'B*' }, @@ -4289,23 +4416,24 @@ 'Limit' : 0 }) # This is an arbitrary convention self.assertEqual(4, len(a)) - b = [] - for i in range(4): + if HasExtendedFind(_REMOTE): # usage of since is not reliable without ExtendedFind + b = [] + for i in range(4): + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'Query' : { 'PatientName' : 'B*' }, + 'Limit' : 1, + 'Since' : i }) + self.assertEqual(1, len(a)) + b.append(a[0]) + + # 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' : i }) - self.assertEqual(1, len(a)) - b.append(a[0]) - - # 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)) + 'Query' : { 'PatientName' : 'B*' }, + 'Limit' : 1, + 'Since' : 4 }) + self.assertEqual(0, len(a)) # Check using KNEE a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', @@ -4318,109 +4446,114 @@ 'Limit' : 2 }) self.assertEqual(2, len(a)) - b = [] - for i in range(2): - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', - 'Query' : { 'PatientName' : 'K*' }, - 'Limit' : 1, - 'Since' : i }) - self.assertEqual(1, len(a)) - b.append(a[0]) - - self.assertEqual(0, len(set(b) ^ set(knee))) + if HasExtendedFind(_REMOTE): # usage of since is not reliable without ExtendedFind + b = [] + for i in range(2): + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'Query' : { 'PatientName' : 'K*' }, + 'Limit' : 1, + 'Since' : i }) + self.assertEqual(1, len(a)) + b.append(a[0]) + + 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)) + # TODO: remove these tests for good once 1.12.5 is out + # if not HasExtendedFind(_REMOTE): # once you have ExtendedFind, usage of Limit and Since is forbidden when filtering on tags that are not in DB because that's just impossible to use on real life DB ! + + + # 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): @@ -10247,8 +10380,18 @@ Check('TransferSyntaxes/1.2.840.10008.1.2.1.dcm', True, False, 'OB') # Explicit Little Endian, 8bpp Check('Phenix/IM-0001-0001.dcm', True, False, 'OW') # Explicit Little Endian, 16bpp Check('TransferSyntaxes/1.2.840.10008.1.2.2.dcm', True, False, 'OB') # Explicit Big Endian, 8bpp - Check('TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm', True, False, 'OB') # JPEG - Check('Knee/T1/IM-0001-0001.dcm', True, False, 'OB') # JPEG2k + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): + # From Orthanc 1.12.6, the PixelData is not present. Anyway, it was not usable in 1.12.5 + Check('TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm', False, False, 'OB') # JPEG + Check('Knee/T1/IM-0001-0001.dcm', False, False, 'OB') # JPEG2k + else: + # up to Orthanc 1.12.5, we get this (that is basically useless): + # "7FE00010" : { + # "InlineBinary" : "", + # "vr" : "OB" + # } + Check('TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm', True, False, 'OB') # JPEG + Check('Knee/T1/IM-0001-0001.dcm', True, False, 'OB') # JPEG2k def test_encapsulate_stl(self): if IsOrthancVersionAbove(_REMOTE, 1, 12, 1): @@ -10720,7 +10863,7 @@ if IsOrthancVersionAbove(_REMOTE, 1, 12, 4): # the old syntax is still required for the upgrade/downgrade PG tests a = DoGet(_REMOTE, '/instances/%s?requested-tags=0008,0056' % instance) else: - a = DoGet(_REMOTE, '/instances/%s?RequestedTags=0008,0056' % instance) + a = DoGet(_REMOTE, '/instances/%s?requestedTags=0008,0056' % instance) self.assertEqual(1, len(a['RequestedTags'])) self.assertEqual('ONLINE', a['RequestedTags']['InstanceAvailability']) @@ -10728,14 +10871,14 @@ if IsOrthancVersionAbove(_REMOTE, 1, 12, 4): a = DoGet(_REMOTE, '/series/%s?requested-tags=0020,1209' % series) else: - a = DoGet(_REMOTE, '/series/%s?RequestedTags=0020,1209' % series) + a = DoGet(_REMOTE, '/series/%s?requestedTags=0020,1209' % series) self.assertEqual(1, len(a['RequestedTags'])) self.assertEqual(2, int(a['RequestedTags']['NumberOfSeriesRelatedInstances'])) if IsOrthancVersionAbove(_REMOTE, 1, 12, 4): a = DoGet(_REMOTE, '/studies/%s?requested-tags=0008,0061;0008,0062;0020,1206;0020,1208' % study) else: - a = DoGet(_REMOTE, '/studies/%s?RequestedTags=0008,0061;0008,0062;0020,1206;0020,1208' % study) + a = DoGet(_REMOTE, '/studies/%s?requestedTags=0008,0061;0008,0062;0020,1206;0020,1208' % study) self.assertEqual(4, len(a['RequestedTags'])) self.assertEqual('CT\\PT', a['RequestedTags']['ModalitiesInStudy']) @@ -10746,7 +10889,7 @@ if IsOrthancVersionAbove(_REMOTE, 1, 12, 4): a = DoGet(_REMOTE, '/patients/%s?requested-tags=0020,1200;0020,1202;0020,1204' % patient) else: - a = DoGet(_REMOTE, '/studies/%s?RequestedTags=0020,1200;0020,1202;0020,1204' % study) + a = DoGet(_REMOTE, '/studies/%s?requestedTags=0020,1200;0020,1202;0020,1204' % study) self.assertEqual(3, len(a['RequestedTags'])) self.assertEqual(1, int(a['RequestedTags']['NumberOfPatientRelatedStudies'])) self.assertEqual(2, int(a['RequestedTags']['NumberOfPatientRelatedSeries'])) @@ -10787,14 +10930,17 @@ def test_extended_find_order_by(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged - + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # Upload 12 instances for i in range(3): - UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-000%d.dcm' % (i + 1)) - UploadInstance(_REMOTE, 'Brainix/Epi/IM-0001-000%d.dcm' % (i + 1)) - UploadInstance(_REMOTE, 'Knee/T1/IM-0001-000%d.dcm' % (i + 1)) - UploadInstance(_REMOTE, 'Knee/T2/IM-0001-000%d.dcm' % (i + 1)) + r = UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-000%d.dcm' % (i + 1)) + DoPut(_REMOTE, '/instances/%s/metadata/1234' % r['ID'], '%f' % (10.0 + 0.1 * i)) + r = UploadInstance(_REMOTE, 'Brainix/Epi/IM-0001-000%d.dcm' % (i + 1)) + DoPut(_REMOTE, '/instances/%s/metadata/1234' % r['ID'], '%f' % (20.0 + 0.1 * i)) + r = UploadInstance(_REMOTE, 'Knee/T1/IM-0001-000%d.dcm' % (i + 1)) + DoPut(_REMOTE, '/instances/%s/metadata/1234' % r['ID'], '%f' % (30.0 + 0.1 * i)) + r = UploadInstance(_REMOTE, 'Knee/T2/IM-0001-000%d.dcm' % (i + 1)) + DoPut(_REMOTE, '/instances/%s/metadata/1234' % r['ID'], '%f' % (40.0 + 0.1 * i)) kneeT2SeriesId = 'bbf7a453-0d34251a-03663b55-46bb31b9-ffd74c59' kneeT1SeriesId = '6de73705-c4e65c1b-9d9ea1b5-cabcd8e7-f15e4285' @@ -10924,7 +11070,7 @@ 'Direction': 'ASC' }, { - 'Type': 'DicomTag', + 'Type': 'DicomTagAsInt', 'Key': 'InstanceNumber', 'Direction': 'ASC' }, @@ -10940,7 +11086,7 @@ for i in range(1, len(a)-1): self.assertTrue(a[i-1]['RequestedTags']['PatientBirthDate'] <= a[i]['RequestedTags']['PatientBirthDate']) if a[i-1]['RequestedTags']['PatientBirthDate'] == a[i]['RequestedTags']['PatientBirthDate']: - self.assertTrue(a[i-1]['RequestedTags']['InstanceNumber'] <= a[i]['RequestedTags']['InstanceNumber']) + self.assertTrue(int(a[i-1]['RequestedTags']['InstanceNumber']) <= int(a[i]['RequestedTags']['InstanceNumber'])) if a[i-1]['RequestedTags']['InstanceNumber'] == a[i]['RequestedTags']['InstanceNumber']: self.assertTrue(a[i-1]['RequestedTags']['SeriesTime'] <= a[i]['RequestedTags']['SeriesTime']) @@ -10951,7 +11097,7 @@ }, 'OrderBy' : [ { - 'Type': 'DicomTag', + 'Type': 'DicomTagAsInt', 'Key': 'InstanceNumber', 'Direction': 'DESC' }, @@ -10970,7 +11116,7 @@ }) self.assertEqual(12, len(a)) for i in range(1, len(a)-1): - self.assertTrue(a[i-1]['RequestedTags']['InstanceNumber'] >= a[i]['RequestedTags']['InstanceNumber']) + self.assertTrue(int(a[i-1]['RequestedTags']['InstanceNumber']) >= int(a[i]['RequestedTags']['InstanceNumber'])) if a[i-1]['RequestedTags']['InstanceNumber'] == a[i]['RequestedTags']['InstanceNumber']: self.assertTrue(a[i-1]['RequestedTags']['PatientBirthDate'] <= a[i]['RequestedTags']['PatientBirthDate']) if a[i-1]['RequestedTags']['PatientBirthDate'] == a[i]['RequestedTags']['PatientBirthDate']: @@ -11052,8 +11198,37 @@ self.assertEqual(kneeT1SeriesId, a[3]) + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instance', + 'ResponseContent': ['Metadata', 'RequestedTags'], + 'Query' : { + }, + 'OrderBy' : [ + { + 'Type': 'MetadataAsFloat', + 'Key': '1234', + 'Direction': 'DESC' + } + ], + 'RequestedTags' : ['SeriesDescription'] + }) + self.assertEqual(12, len(a)) + for i in range(0, 2): + self.assertEqual("T2W_TSE", a[i]['RequestedTags']['SeriesDescription']) + self.assertAlmostEqual(40.2, float(a[0]['Metadata']['1234'])) + self.assertAlmostEqual(40.0, float(a[2]['Metadata']['1234'])) + + for i in range(3, 5): + self.assertEqual("T1W_aTSE", a[i]['RequestedTags']['SeriesDescription']) + + for i in range(6, 8): + self.assertEqual("T2W/FE-EPI", a[i]['RequestedTags']['SeriesDescription']) + + for i in range(9, 11): + self.assertEqual("sT2W/FLAIR", a[i]['RequestedTags']['SeriesDescription']) + + def test_extended_find_parent(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # Upload 12 instances for i in range(3): UploadInstance(_REMOTE, 'Knee/T1/IM-0001-000%d.dcm' % (i + 1)) @@ -11098,10 +11273,19 @@ self.assertEqual(6, len(a)) + # same query in count-resources + a = DoPost(_REMOTE, '/tools/count-resources', { 'Level' : 'Instance', + 'Query' : { + 'SeriesDescription' : 'T*' + }, + 'ParentPatient' : kneePatientId + }) + + self.assertEqual(6, a["Count"]) + def test_extended_find_filter_metadata(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged - + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # Upload 12 instances for i in range(3): UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-000%d.dcm' % (i + 1)) @@ -11119,20 +11303,27 @@ DoPut(_REMOTE, '/series/%s/metadata/my-metadata' % brainixEpiSeriesId, 'brainixEpi') # filter on metadata - a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', - 'Query' : { - 'SeriesDescription' : 'T*' - }, - 'QueryMetadata' : { - 'my-metadata': '*2*' - } - }) + q = { + 'Level' : 'Series', + 'Query' : { + 'SeriesDescription' : 'T*' + }, + 'MetadataQuery' : { + 'my-metadata': '*2*' + } + } + a = DoPost(_REMOTE, '/tools/find', q) self.assertEqual(1, len(a)) self.assertEqual(kneeT2SeriesId, a[0]) + a = DoPost(_REMOTE, '/tools/count-resources', q) + self.assertEqual(1, a["Count"]) + + + def test_extended_find_expand(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0001.dcm') a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', @@ -11256,8 +11447,7 @@ def test_extended_find_full(self): - if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged - + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # Upload 12 instances for i in range(3): UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-000%d.dcm' % (i + 1)) @@ -11281,7 +11471,7 @@ 'PatientName' : '*' }, 'RequestedTags': ['StudyDate'], - 'QueryMetadata' : { + 'MetadataQuery' : { 'my-metadata': "*nee*" }, 'OrderBy' : [ @@ -11305,4 +11495,275 @@ self.assertEqual(kneeT2SeriesId, a[1]['ID']) self.assertEqual(kneeStudyId, a[0]['ParentStudy']) self.assertEqual(3, len(a[0]['Instances'])) - self.assertEqual('', a[0]['Metadata']['RemoteAET']) \ No newline at end of file + self.assertEqual('', a[0]['Metadata']['RemoteAET']) + + def test_pagination_and_limit_find_results(self): + # LimitFindInstances is set to 20 + # LimitFindResults is set to 10 + + # Upload 27 instances from KNIX + UploadFolder(_REMOTE, 'Knix/Loc') + + # Upload 13 other series + UploadInstance(_REMOTE, 'DummyCT.dcm') + UploadInstance(_REMOTE, 'Phenix/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Implicit-vr-us-palette.dcm') + UploadInstance(_REMOTE, 'Multiframe.dcm') + UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Knee/T1/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'PrivateTags.dcm') + UploadInstance(_REMOTE, 'PrivateMDNTags.dcm') + UploadInstance(_REMOTE, 'Comunix/Ct/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Comunix/Pet/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Beaufix/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Encodings/Lena-ascii.dcm') + + self.assertEqual(14, len(DoGet(_REMOTE, '/series'))) + + + # knixInstancesNoLimit = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Instances', + # 'Query' : { + # 'PatientName' : 'KNIX' + # }, + # 'Expand': False + # }) + + # # pprint.pprint(knixInstancesNoLimit) + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): + # self.assertEqual(20, len(knixInstancesNoLimit)) + # else: + # self.assertEqual(21, len(knixInstancesNoLimit)) + + # knixInstancesSince5Limit20 = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Instances', + # 'Query' : { + # 'PatientName' : 'KNIX' + # }, + # 'Expand': False, + # 'Since': 5, + # 'Limit': 20 + # }) + # # pprint.pprint(knixInstancesSince5Limit20) + + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): + # self.assertEqual(20, len(knixInstancesSince5Limit20)) # Orthanc actually returns LimitFindInstances + 1 resources + # # the first 5 from previous call shall not be in this answer + # for i in range(0, 5): + # self.assertNotIn(knixInstancesNoLimit[i], knixInstancesSince5Limit20) + # # the last 4 from last call shall not be in the first answer + # for i in range(16, 20): + # self.assertNotIn(knixInstancesSince5Limit20[i], knixInstancesNoLimit) + + # # request more instances than LimitFindInstances + # knixInstancesSince0Limit23 = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Instances', + # 'Query' : { + # 'PatientName' : 'KNIX' + # }, + # 'Expand': False, + # 'Since': 0, + # 'Limit': 23 + # }) + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): + # self.assertEqual(20, len(knixInstancesSince0Limit23)) + + # seriesNoLimit = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Series', + # 'Query' : { + # 'PatientName' : '*' + # }, + # 'Expand': False + # }) + + # # pprint.pprint(seriesNoLimit) + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): + # self.assertEqual(10, len(seriesNoLimit)) + # else: + # self.assertEqual(11, len(seriesNoLimit)) + + # seriesSince8Limit6 = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Series', + # 'Query' : { + # 'PatientName' : '*' + # }, + # 'Expand': False, + # 'Since': 8, + # 'Limit': 6 + # }) + + # # pprint.pprint(seriesSince8Limit6) + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): # TODO: remove HasExtendedFind once find-refactoring branch has been merged and supported by all DB plugins !!! + # self.assertEqual(6, len(seriesSince8Limit6)) + + # # the first 7 from previous call shall not be in this answer + # for i in range(0, 7): + # self.assertNotIn(seriesNoLimit[i], seriesSince8Limit6) + # # the last 3 from last call shall not be in the first answer + # for i in range(3, 5): + # self.assertNotIn(seriesSince8Limit6[i], seriesNoLimit) + + # if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): + # # query by a tag that is not in the DB (there are 27 instances from Knix/Loc + 10 instances from other series that satisfies this criteria) + # a = DoPost(_REMOTE, '/tools/find', { + # 'Level' : 'Instances', + # 'Query' : { + # 'PhotometricInterpretation' : 'MONOCHROME*' + # }, + # 'Expand': True, + # 'OrderBy' : [ + # { + # 'Type': 'DicomTag', + # 'Key': 'InstanceNumber', + # 'Direction': 'ASC' + # } + # ]}) + + # # pprint.pprint(a) + # # print(len(a)) + # # TODO: we should have something in the response that notifies us that the response is not "complete" + # # TODO: we should receive an error if we try to use "since" in this kind of search ? + # self.assertEqual(17, len(a)) # the fast DB filtering returns 20 instances -> only 17 of them meet the criteria but this is not really correct !!! + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE): + # make sur an error is returned when using Since or Limit when querying a tag that is not in DB + self.assertRaises(Exception, lambda: DoPost(_REMOTE, '/tools/find', {'Level' : 'Instances', + 'Query' : { + 'PhotometricInterpretation' : 'MONOCHROME*' + }, + 'Since': 2 + })) + + # make sur an error is returned when using Since when querying a tag that is not in DB + self.assertRaises(Exception, lambda: DoPost(_REMOTE, '/tools/find', {'Level' : 'Instances', + 'Query' : { + 'PhotometricInterpretation' : 'MONOCHROME*' + }, + 'Since': 10 + })) + + # https://github.com/orthanc-server/orthanc-explorer-2/issues/73 + if IsOrthancVersionAbove(_REMOTE, 1, 12, 6) and HasExtendedFind(_REMOTE): + # make sur no error is returned when using Since or Limit when querying against ModalitiesInStudy + a = DoPost(_REMOTE, '/tools/find', {'Level' : 'Studies', + 'Query' : { + 'ModalitiesInStudy' : 'CT\\MR' + }, + 'Since': 2, + 'Limit': 3, + 'Expand': True, + 'OrderBy': [ + { + 'Type': 'DicomTag', + 'Key': 'StudyDate', + 'Direction': 'ASC' + } + ]}) + # pprint.pprint(a) + self.assertEqual('20050927', a[0]['MainDicomTags']['StudyDate']) + self.assertEqual('20061201', a[1]['MainDicomTags']['StudyDate']) + self.assertEqual('20070101', a[2]['MainDicomTags']['StudyDate']) + + + def test_attachment_range(self): + def TestData(path): + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/%s' % (i, path)) + self.assertFalse('content-range' in resp) + self.assertEqual(200, resp.status) + self.assertEqual(2472, len(content)) + self.assertEqual('2472', resp['content-length']) + self.assertEqual('application/dicom', resp['content-type']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/%s' % (i, path), headers = { 'Range' : 'bytes=128-131' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(4, len(content)) + self.assertEqual('D', content[0]) + self.assertEqual('I', content[1]) + self.assertEqual('C', content[2]) + self.assertEqual('M', content[3]) + self.assertEqual('4', resp['content-length']) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 128-131/2472', resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/%s' % (i, path), headers = { 'Range' : 'bytes=-' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(2472, len(content)) + self.assertEqual('2472', resp['content-length']) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 0-2471/2472', resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/%s' % (i, path), headers = { 'Range' : 'bytes=128-' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(2344, len(content)) + self.assertEqual('D', content[0]) + self.assertEqual('I', content[1]) + self.assertEqual('C', content[2]) + self.assertEqual('M', content[3]) + self.assertEqual('2344', resp['content-length']) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 128-2471/2472', resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/%s' % (i, path), headers = { 'Range' : 'bytes=-131' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(132, len(content)) + self.assertEqual('D', content[-4]) + self.assertEqual('I', content[-3]) + self.assertEqual('C', content[-2]) + self.assertEqual('M', content[-1]) + self.assertEqual('132', resp['content-length']) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 0-131/2472', resp['content-range']) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 5): + i = UploadInstance(_REMOTE, 'DummyCT.dcm') ['ID'] + + DoPost(_REMOTE, '/instances/%s/attachments/dicom/uncompress' % i) + TestData('data') + TestData('compressed-data') + + DoPost(_REMOTE, '/instances/%s/attachments/dicom/compress' % i) + TestData('data') + + (resp, compressed) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/compressed-data' % i) + self.assertFalse('content-range' in resp) + self.assertEqual(200, resp.status) + self.assertTrue(len(compressed) < 2000) + self.assertEqual(len(compressed), int(resp['content-length'])) + self.assertEqual('application/octet-stream', resp['content-type']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/compressed-data' % i, headers = { 'Range' : 'bytes=-' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(compressed, content) + self.assertEqual(len(compressed), int(resp['content-length'])) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 0-%d/%d' % (len(compressed) - 1, len(compressed)), resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/compressed-data' % i, headers = { 'Range' : 'bytes=10-' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(compressed[10:], content) + self.assertEqual(len(compressed) - 10, int(resp['content-length'])) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 10-%d/%d' % (len(compressed) - 1, len(compressed)), resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/compressed-data' % i, headers = { 'Range' : 'bytes=-20' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(compressed[0:21], content) + self.assertEqual(21, int(resp['content-length'])) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 0-20/%d' % len(compressed), resp['content-range']) + + (resp, content) = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/compressed-data' % i, headers = { 'Range' : 'bytes=10-20' }) + self.assertTrue('content-range' in resp) + self.assertEqual(206, resp.status) + self.assertEqual(compressed[10:21], content) + self.assertEqual(11, int(resp['content-length'])) + self.assertEqual('application/octet-stream', resp['content-type']) + self.assertEqual('bytes 10-20/%d' % len(compressed), resp['content-range'])
--- a/Tests/Toolbox.py Wed Oct 09 11:07:09 2024 +0200 +++ b/Tests/Toolbox.py Thu Jan 30 17:38:39 2025 +0100 @@ -4,8 +4,8 @@ # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, 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 +# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium +# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -394,7 +394,7 @@ return count -def IsPluginVersionAbove(orthanc, plugin, major, minor, revision): +def IsPluginVersionAtLeast(orthanc, plugin, major, minor, revision): v = DoGet(orthanc, '/plugins/%s' % plugin)['Version'] if v.startswith('mainline'): @@ -412,7 +412,7 @@ a = int(tmp[0]) b = int(tmp[1]) return (a > major or - (a == major and b > minor)) + (a == major and b >= minor)) else: return False