# HG changeset patch # User Alain Mazy # Date 1741629487 -3600 # Node ID 5140a8917254261719420fcc1006b39eb2a1e6f6 # Parent d08518883f2fa44f4c28b42c71166819db85fd76# Parent 3da6edb11ee99f58ea95c008e997251b11e399fd merged default -> attach-custom-data diff -r d08518883f2f -r 5140a8917254 Database/sample-pdf.dcm Binary file Database/sample-pdf.dcm has changed diff -r d08518883f2f -r 5140a8917254 NewTests/Authorization/test_authorization.py --- a/NewTests/Authorization/test_authorization.py Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/Authorization/test_authorization.py Mon Mar 10 18:58:07 2025 +0100 @@ -57,7 +57,8 @@ }, "DicomWeb": { "Enable": True - } + }, + "StableAge": 5000 # not to be disturbed by StableAge events while debugging } config_path = cls.generate_configuration( @@ -121,6 +122,17 @@ cls.no_label_series_dicom_id = o.series.get_tags(cls.no_label_series_id)["SeriesInstanceUID"] cls.no_label_instance_dicom_id = o.instances.get_tags(cls.no_label_instance_id)["SOPInstanceUID"] + cls.both_labels_instance_id = o.upload_file(here / "../../Database/Phenix/IM-0001-0001.dcm")[0] + cls.both_labels_study_id = o.instances.get_parent_study_id(cls.both_labels_instance_id) + cls.both_labels_series_id = o.instances.get_parent_series_id(cls.both_labels_instance_id) + cls.both_labels_study_dicom_id = o.studies.get_tags(cls.both_labels_study_id)["StudyInstanceUID"] + cls.both_labels_series_dicom_id = o.series.get_tags(cls.both_labels_series_id)["SeriesInstanceUID"] + cls.both_labels_instance_dicom_id = o.instances.get_tags(cls.both_labels_instance_id)["SOPInstanceUID"] + o.studies.add_label(cls.both_labels_study_id, "label_a") + o.studies.add_label(cls.both_labels_study_id, "label_b") + o.series.add_label(cls.both_labels_series_id, "label_a") + o.series.add_label(cls.both_labels_series_id, "label_b") + def assert_is_forbidden(self, api_call): with self.assertRaises(orthanc_exceptions.HttpError) as ctx: @@ -145,14 +157,15 @@ instances_ids = o.series.get_instances_ids(series_ids[0]) o.instances.get_tags(instances_ids[0]) - # make sure labels filtering still works - self.assertEqual(3, len(o.studies.find(query={}, - labels=[], - labels_constraint='Any'))) + if o.is_plugin_version_at_least("authorization", 0, 9, 0): + # make sure labels filtering still works + self.assertEqual(4, len(o.studies.find(query={}, + labels=[], + labels_constraint='Any'))) - self.assertEqual(2, len(o.studies.find(query={}, - labels=['label_a', 'label_b'], - labels_constraint='Any'))) + self.assertEqual(3, len(o.studies.find(query={}, + labels=['label_a', 'label_b'], + labels_constraint='Any'))) self.assertEqual(2, len(o.studies.find(query={}, labels=['label_a'], @@ -192,19 +205,24 @@ # make sure we can not access series and instances of the label_b studies self.assert_is_forbidden(lambda: o.studies.get_series_ids(self.label_b_study_id)) - # make sure tools/find only returns the label_a studies - studies = o.studies.find(query={}, - labels=[], - labels_constraint='Any') - self.assertEqual(1, len(studies)) - self.assertEqual(self.label_a_study_id, studies[0].orthanc_id) + if o_admin.is_plugin_version_at_least("authorization", 0, 9, 0): + # make sure tools/find only returns the label_a studies + studies = o.studies.find(query={}, + labels=[], + labels_constraint='Any') + studies_orthanc_ids = [x.orthanc_id for x in studies] + self.assertEqual(2, len(studies_orthanc_ids)) + self.assertIn(self.label_a_study_id, studies_orthanc_ids) + self.assertIn(self.both_labels_study_id, studies_orthanc_ids) - # if searching Any of label_a & label_b, return only label_a - studies = o.studies.find(query={}, - labels=['label_a', 'label_b'], - labels_constraint='Any') - self.assertEqual(1, len(studies)) - self.assertEqual(self.label_a_study_id, studies[0].orthanc_id) + # if searching Any of label_a & label_b, return only label_a + studies = o.studies.find(query={}, + labels=['label_a', 'label_b'], + labels_constraint='Any') + studies_orthanc_ids = [x.orthanc_id for x in studies] + self.assertEqual(2, len(studies_orthanc_ids)) + self.assertIn(self.label_a_study_id, studies_orthanc_ids) + self.assertIn(self.both_labels_study_id, studies_orthanc_ids) # if searching Any of label_b, expect a Forbidden access self.assert_is_forbidden(lambda: o.studies.find(query={}, @@ -272,6 +290,31 @@ i = o_admin.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}/instances/{self.label_a_instance_dicom_id}") i = o_admin.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011") + if o_admin.is_plugin_version_at_least("authorization", 0, 9, 0): + # the user_a shall only see the label_a in the returned labels + studies = o.post(endpoint="/tools/find", json={"Level": "Study", "Query": {}, "Labels": [], "LabelsConstraint": "Any", "Expand": True}).json() + self.assertEqual(2, len(studies)) + self.assertEqual(1, len(studies[0]["Labels"])) + self.assertEqual("label_a", studies[0]["Labels"][0]) + self.assertEqual(1, len(studies[1]["Labels"])) + self.assertEqual("label_a", studies[1]["Labels"][0]) + + r = o.get(endpoint=f"/studies/{self.both_labels_study_id}").json() + self.assertEqual(1, len(r["Labels"])) + self.assertEqual("label_a", r["Labels"][0]) + + r = o.get(endpoint=f"/studies/{self.both_labels_study_id}/series?expand").json() + self.assertEqual(1, len(r[0]["Labels"])) + self.assertEqual("label_a", r[0]["Labels"][0]) + + r = o.get(endpoint=f"/studies/{self.both_labels_study_id}/labels").json() + self.assertEqual(1, len(r)) + self.assertEqual("label_a", r[0]) + + r = o.get(endpoint=f"/series/{self.both_labels_series_id}/study").json() + self.assertEqual(1, len(r["Labels"])) + self.assertEqual("label_a", r["Labels"][0]) + def test_uploader_a(self): o_admin = OrthancApiClient(self.o._root_url, headers={"user-token-key": "token-admin"}) diff -r d08518883f2f -r 5140a8917254 NewTests/PostgresUpgrades/docker-compose.yml --- a/NewTests/PostgresUpgrades/docker-compose.yml Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/PostgresUpgrades/docker-compose.yml Mon Mar 10 18:58:07 2025 +0100 @@ -16,9 +16,9 @@ AC_AUTHENTICATION_ENABLED: "false" # Orthanc previous version - orthanc-pg-15-6rev3: - image: orthancteam/orthanc:25.1.1 - container_name: orthanc-pg-15-6rev3 + orthanc-pg-15-previous-revision: + image: orthancteam/orthanc:25.2.0 + container_name: orthanc-pg-15-previous-revision depends_on: [pg-15] restart: unless-stopped ports: ["8052:8042"] @@ -29,9 +29,9 @@ ORTHANC__AUTHENTICATION_ENABLED: "false" # Orthanc previous version to run the integration tests - orthanc-pg-15-6rev3-for-integ-tests: - image: orthancteam/orthanc:25.1.1 - container_name: orthanc-pg-15-6rev3-for-integ-tests + orthanc-pg-15-previous-revision-for-integ-tests: + image: orthancteam/orthanc:25.2.0 + container_name: orthanc-pg-15-previous-revision-for-integ-tests depends_on: [pg-15] restart: unless-stopped ports: ["8053:8042"] @@ -50,7 +50,7 @@ image: jodogne/orthanc-tests container_name: orthanc-tests depends_on: - - orthanc-pg-15-6rev3-for-integ-tests + - orthanc-pg-15-previous-revision-for-integ-tests volumes: - ../../:/tests/orthanc-tests - ./wait-for-it.sh:/scripts/wait-for-it.sh diff -r d08518883f2f -r 5140a8917254 NewTests/PostgresUpgrades/downgrade.sh --- a/NewTests/PostgresUpgrades/downgrade.sh Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/PostgresUpgrades/downgrade.sh Mon Mar 10 18:58:07 2025 +0100 @@ -8,7 +8,7 @@ pushd orthanc-databases 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/Rev5ToRev4.sql # if you want to test a downgrade procedure, you may use this code ... # psql -U postgres -f downgrade.sql diff -r d08518883f2f -r 5140a8917254 NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh --- a/NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/PostgresUpgrades/run-integ-tests-from-docker.sh Mon Mar 10 18:58:07 2025 +0100 @@ -2,6 +2,6 @@ set -ex -/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 +/scripts/wait-for-it.sh orthanc-pg-15-previous-revision-for-integ-tests:8042 -t 60 +# python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-previous-revision-for-integ-tests --force --docker -- -v Orthanc.test_lua_deadlock +python /tests/orthanc-tests/Tests/Run.py --server=orthanc-pg-15-previous-revision-for-integ-tests --force --docker -- -v diff -r d08518883f2f -r 5140a8917254 NewTests/PostgresUpgrades/test_pg_upgrades.py --- a/NewTests/PostgresUpgrades/test_pg_upgrades.py Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/PostgresUpgrades/test_pg_upgrades.py Mon Mar 10 18:58:07 2025 +0100 @@ -27,7 +27,7 @@ cls.cleanup() - def test_upgrade_6rev2_to_6rev3(self): + def test_upgrade_previous_revision_to_current(self): # remove everything including the DB from previous tests TestPgUpgrades.cleanup() @@ -38,16 +38,16 @@ subprocess.run(["docker", "compose", "up", "pg-15", "-d"], check=True) wait_container_healthy("pg-15") - print("Launching Orthanc with DB 6rev3") - subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev3", "-d"], check=True) + print("Launching Orthanc with DB previous-revision") + subprocess.run(["docker", "compose", "up", "orthanc-pg-15-previous-revision", "-d"], check=True) o = OrthancApiClient("http://localhost:8052") o.wait_started() instances = o.upload_folder(here / "../../Database/Knee") - print("Stopping Orthanc with DB 6rev3") - subprocess.run(["docker", "compose", "stop", "orthanc-pg-15-6rev3"], check=True) + print("Stopping Orthanc with DB previous-revision") + subprocess.run(["docker", "compose", "stop", "orthanc-pg-15-previous-revision"], 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 6rev3") + print("Downgrading Orthanc DB to previous-revision") subprocess.run(["docker", "exec", "pg-15", "./scripts/downgrade.sh"], check=True) time.sleep(2) - print("Launching previous Orthanc (DB 6rev3)") - subprocess.run(["docker", "compose", "up", "orthanc-pg-15-6rev3", "-d"], check=True) + print("Launching previous Orthanc (DB previous-revision)") + subprocess.run(["docker", "compose", "up", "orthanc-pg-15-previous-revision", "-d"], check=True) o = OrthancApiClient("http://localhost:8052") o.wait_started() @@ -135,10 +135,10 @@ 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-6rev3-for-integ-tests) so they know each other + # first create the containers (orthanc-tests + orthanc-pg-15-previous-revision-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-6rev3-for-integ-tests", "-d"], check=True) + subprocess.run(["docker", "compose", "up", "orthanc-pg-15-previous-revision-for-integ-tests", "-d"], check=True) o = OrthancApiClient("http://localhost:8053", user="alice", pwd="orthanctest") o.wait_started() diff -r d08518883f2f -r 5140a8917254 NewTests/requirements.txt --- a/NewTests/requirements.txt Fri Feb 07 12:24:05 2025 +0100 +++ b/NewTests/requirements.txt Mon Mar 10 18:58:07 2025 +0100 @@ -1,3 +1,8 @@ +<<<<<<< working copy orthanc-api-client>=0.18.0 orthanc-tools>=0.15.1 +======= +orthanc-api-client>=0.18.4 +orthanc-tools>=0.13.0 +>>>>>>> merge rev uvicorn \ No newline at end of file diff -r d08518883f2f -r 5140a8917254 Tests/Tests.py --- a/Tests/Tests.py Fri Feb 07 12:24:05 2025 +0100 +++ b/Tests/Tests.py Mon Mar 10 18:58:07 2025 +0100 @@ -278,6 +278,12 @@ self.assertIn('Uuid', attachmentInfo) self.assertEqual(1, attachmentInfo['ContentType']) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7): + resp, content = DoGetRaw(_REMOTE, '/instances/%s/attachments/dicom/data?filename=toto.dcm' % instance) + self.assertEqual('filename="toto.dcm"', resp['content-disposition']) + + s = sizeDummyCT + j if isCompressed: @@ -727,7 +733,7 @@ kneeStudy = DoGet(_REMOTE, '/studies')[0] kneeSeries = DoGet(_REMOTE, '/series')[0] - z = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) + z, resp = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) self.assertEqual(2, len(z.namelist())) if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) @@ -735,7 +741,7 @@ else: self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) - z = GetArchive(_REMOTE, '/studies/%s/archive' % kneeStudy) + z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % kneeStudy) self.assertEqual(2, len(z.namelist())) if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) @@ -743,7 +749,7 @@ else: self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000000.dcm', z.namelist()) - z = GetArchive(_REMOTE, '/series/%s/archive' % kneeSeries) + z, resp = GetArchive(_REMOTE, '/series/%s/archive' % kneeSeries) self.assertEqual(1, len(z.namelist())) if IsOrthancVersionAbove(_REMOTE, 1, 12, 6): self.assertIn('887 KNEE/A10003245599 IRM DU GENOU/MR T1W_aTSE/MR000001.dcm', z.namelist()) @@ -754,7 +760,7 @@ brainixPatient = '16738bc3-e47ed42a-43ce044c-a3414a45-cb069bd0' brainixStudy = '27f7126f-4f66fb14-03f4081b-f9341db2-53925988' - z = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) + z, resp = GetArchive(_REMOTE, '/patients/%s/archive' % kneePatient) self.assertEqual(2, len(z.namelist())) # archive with 2 patients @@ -814,15 +820,15 @@ self.assertEqual(helloPatient, worldPatient) # when downloading the Patient, we do not really know what PatientName we will get in the zip - z = GetArchive(_REMOTE, '/patients/%s/archive' % helloPatient) + z, resp = GetArchive(_REMOTE, '/patients/%s/archive' % helloPatient) self.assertEqual(2, len(z.namelist())) # when downloading studies individually, we want to have the PatientName that appears in the study - z = GetArchive(_REMOTE, '/studies/%s/archive' % helloStudy) + z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % helloStudy) self.assertEqual(1, len(z.namelist())) self.assertIn('COMMON HELLO/HELLO SERIES/Unknown Series/00000000.dcm', z.namelist()) - z = GetArchive(_REMOTE, '/studies/%s/archive' % worldStudy) + z, resp = GetArchive(_REMOTE, '/studies/%s/archive' % worldStudy) self.assertEqual(1, len(z.namelist())) self.assertIn('COMMON WORLD/WORLD SERIES/Unknown Series/00000000.dcm', z.namelist()) @@ -832,7 +838,7 @@ UploadInstance(_REMOTE, 'Knee/T1/IM-0001-0001.dcm') UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0001.dcm') - z = GetArchive(_REMOTE, '/patients/%s/media' % DoGet(_REMOTE, '/patients')[0]) + z, resp = GetArchive(_REMOTE, '/patients/%s/media' % DoGet(_REMOTE, '/patients')[0]) self.assertEqual(3, len(z.namelist())) self.assertTrue('IMAGES/IM0' in z.namelist()) self.assertTrue('IMAGES/IM1' in z.namelist()) @@ -4745,7 +4751,7 @@ def test_extended_media(self): UploadInstance(_REMOTE, 'Knee/T1/IM-0001-0001.dcm') - z = GetArchive(_REMOTE, '/patients/%s/media?extended' % DoGet(_REMOTE, '/patients')[0]) + z, resp = GetArchive(_REMOTE, '/patients/%s/media?extended' % DoGet(_REMOTE, '/patients')[0]) self.assertEqual(2, len(z.namelist())) self.assertTrue('IMAGES/IM0' in z.namelist()) self.assertTrue('DICOMDIR' in z.namelist()) @@ -5176,12 +5182,15 @@ job = MonitorJob2(_REMOTE, lambda: DoPost (_REMOTE, '/series/%s/archive' % kneeT1, { - 'Synchronous' : False + 'Synchronous' : False, + 'Filename': 'toto.zip' })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) self.assertEqual(1, len(z.namelist())) self.assertFalse('DICOMDIR' in z.namelist()) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7): + self.assertEqual('filename="toto.zip"', resp['content-disposition']) info = DoGet(_REMOTE, '/jobs/%s' % job) self.assertEqual(0, info['Content']['ArchiveSizeMB']) # New in Orthanc 1.8.1 @@ -5197,7 +5206,7 @@ # archive from second job (as MediaArchiveSize == 1) self.assertRaises(Exception, lambda: GetArchive(_REMOTE, '/jobs/%s/archive' % job)) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job2) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job2) self.assertEqual(2, len(z.namelist())) self.assertTrue('DICOMDIR' in z.namelist()) @@ -5212,7 +5221,7 @@ 'Resources' : [ kneeT1, kneeT2 ], })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) self.assertEqual(2, len(z.namelist())) self.assertFalse('DICOMDIR' in z.namelist()) @@ -5227,7 +5236,7 @@ 'Resources' : [ kneeT1, kneeT2 ], })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) self.assertEqual(3, len(z.namelist())) self.assertTrue('DICOMDIR' in z.namelist()) @@ -5249,7 +5258,7 @@ 'Synchronous' : False })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) # delete the output DoDelete(_REMOTE, '/jobs/%s/archive' % job) # make sure it is not available anymore afterwards @@ -5260,7 +5269,7 @@ (_REMOTE, '/series/%s/archive' % kneeT2, { 'Synchronous' : False })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) # delete the output DoDelete(_REMOTE, '/jobs/%s/archive' % job) # make sure it is not available anymore afterwards @@ -5278,7 +5287,7 @@ (_REMOTE, '/series/%s/archive' % kneeT2, { 'Synchronous' : False })) - z = GetArchive(_REMOTE, '/jobs/%s/archive' % job) + z, resp = GetArchive(_REMOTE, '/jobs/%s/archive' % job) # delete the job itself DoDelete(_REMOTE, '/jobs/%s' % job) # make sure it is not available anymore afterwards (and its output is not available either) @@ -5523,7 +5532,7 @@ a = UploadInstance(_REMOTE, 'Issue124.dcm')['ID'] s = DoGet(_REMOTE, '/instances/%s/series' % a)['ID'] - z = GetArchive(_REMOTE, '/series/%s/media' % s) + z, resp = GetArchive(_REMOTE, '/series/%s/media' % s) self.assertEqual(2, len(z.namelist())) @@ -6693,7 +6702,7 @@ SYNTAXES = [ '1.2.840.10008.1.2', '1.2.840.10008.1.2.1', - #'1.2.840.10008.1.2.1.99', # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5) + '1.2.840.10008.1.2.1.99', # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5) '1.2.840.10008.1.2.2', '1.2.840.10008.1.2.4.50', '1.2.840.10008.1.2.4.51', @@ -6729,25 +6738,48 @@ else: self.assertEqual(a, b) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + transcoded = DoPost(_REMOTE, '/instances/%s/modify' % i, { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + ratio40 = ExtractDicomTags(transcoded, [ 'LossyImageCompressionRatio' ]) [0] + + transcoded = DoPost(_REMOTE, '/instances/%s/modify' % i, { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + ratio80 = ExtractDicomTags(transcoded, [ 'LossyImageCompressionRatio' ]) [0] + self.assertGreater(ratio40, ratio80) + + def test_archive_transcode(self): info = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm') # GET on "/media" - z = GetArchive(_REMOTE, '/patients/%s/media' % info['ParentPatient']) + z, resp = GetArchive(_REMOTE, '/patients/%s/media' % info['ParentPatient']) self.assertEqual(2, len(z.namelist())) self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax(z.read('IMAGES/IM0'))) self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients/%s/media?transcode=nope' % info['ParentPatient'])) - z = GetArchive(_REMOTE, '/patients/%s/media?transcode=1.2.840.10008.1.2.4.50' % info['ParentPatient']) + z, resp = GetArchive(_REMOTE, '/patients/%s/media?transcode=1.2.840.10008.1.2.4.50' % info['ParentPatient']) self.assertEqual('1.2.840.10008.1.2.4.50', GetTransferSyntax(z.read('IMAGES/IM0'))) - z = GetArchive(_REMOTE, '/studies/%s/media?transcode=1.2.840.10008.1.2.4.51' % info['ParentStudy']) + z, resp = GetArchive(_REMOTE, '/studies/%s/media?transcode=1.2.840.10008.1.2.4.51' % info['ParentStudy']) self.assertEqual('1.2.840.10008.1.2.4.51', GetTransferSyntax(z.read('IMAGES/IM0'))) - z = GetArchive(_REMOTE, '/series/%s/media?transcode=1.2.840.10008.1.2.4.57' % info['ParentSeries']) + z, resp = GetArchive(_REMOTE, '/series/%s/media?transcode=1.2.840.10008.1.2.4.57' % info['ParentSeries']) self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read('IMAGES/IM0'))) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + z40, resp40 = GetArchive(_REMOTE, '/patients/%s/media?transcode=1.2.840.10008.1.2.4.50&lossy-quality=40' % info['ParentPatient']) + z80, resp80 = GetArchive(_REMOTE, '/patients/%s/media?transcode=1.2.840.10008.1.2.4.50&lossy-quality=80' % info['ParentPatient']) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + # POST on "/media" self.assertRaises(Exception, lambda: PostArchive( @@ -6768,23 +6800,45 @@ }) self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read('IMAGES/IM0'))) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + z40 = PostArchive(_REMOTE, '/series/%s/media' % info['ParentSeries'], { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + z80 = PostArchive(_REMOTE, '/series/%s/media' % info['ParentSeries'], { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + # GET on "/archive" - z = GetArchive(_REMOTE, '/patients/%s/archive' % info['ParentPatient']) + z, resp = GetArchive(_REMOTE, '/patients/%s/archive' % info['ParentPatient']) self.assertEqual(1, len(z.namelist())) self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax(z.read(z.namelist()[0]))) self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/patients/%s/archive?transcode=nope' % info['ParentPatient'])) - z = GetArchive(_REMOTE, '/patients/%s/archive?transcode=1.2.840.10008.1.2' % info['ParentPatient']) + z, resp = GetArchive(_REMOTE, '/patients/%s/archive?transcode=1.2.840.10008.1.2' % info['ParentPatient']) self.assertEqual('1.2.840.10008.1.2', GetTransferSyntax(z.read(z.namelist()[0]))) - z = GetArchive(_REMOTE, '/studies/%s/archive?transcode=1.2.840.10008.1.2.2' % info['ParentStudy']) + z, resp = GetArchive(_REMOTE, '/studies/%s/archive?transcode=1.2.840.10008.1.2.2' % info['ParentStudy']) self.assertEqual('1.2.840.10008.1.2.2', GetTransferSyntax(z.read(z.namelist()[0]))) - z = GetArchive(_REMOTE, '/series/%s/archive?transcode=1.2.840.10008.1.2.4.70' % info['ParentSeries']) + z, resp = GetArchive(_REMOTE, '/series/%s/archive?transcode=1.2.840.10008.1.2.4.70' % info['ParentSeries']) self.assertEqual('1.2.840.10008.1.2.4.70', GetTransferSyntax(z.read(z.namelist()[0]))) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + z40, resp40 = GetArchive(_REMOTE, '/patients/%s/archive?transcode=1.2.840.10008.1.2.4.50&lossy-quality=40' % info['ParentPatient']) + z80, resp80 = GetArchive(_REMOTE, '/patients/%s/archive?transcode=1.2.840.10008.1.2.4.50&lossy-quality=80' % info['ParentPatient']) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + # POST on "/archive" self.assertRaises(Exception, lambda: PostArchive( @@ -6805,6 +6859,20 @@ }) self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read(z.namelist()[0]))) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + z40 = PostArchive(_REMOTE, '/series/%s/archive' % info['ParentSeries'], { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + z80 = PostArchive(_REMOTE, '/series/%s/archive' % info['ParentSeries'], { + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + # "/tools/create-*" z = PostArchive(_REMOTE, '/tools/create-archive', { @@ -6829,30 +6897,96 @@ self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read('IMAGES/IM0'))) if IsOrthancVersionAbove(_REMOTE, 1, 12, 2): - z = GetArchive(_REMOTE, '/tools/create-archive?resources=%s&transcode=1.2.840.10008.1.2.4.50' % info['ParentStudy']) + z, resp = GetArchive(_REMOTE, '/tools/create-archive?resources=%s&transcode=1.2.840.10008.1.2.4.50' % info['ParentStudy']) self.assertEqual(1, len(z.namelist())) self.assertEqual('1.2.840.10008.1.2.4.50', GetTransferSyntax(z.read(z.namelist()[0]))) - z = GetArchive(_REMOTE, '/tools/create-media?resources=%s&transcode=1.2.840.10008.1.2.4.51' % info['ParentStudy']) + z, resp = GetArchive(_REMOTE, '/tools/create-media?resources=%s&transcode=1.2.840.10008.1.2.4.51' % info['ParentStudy']) self.assertEqual(2, len(z.namelist())) self.assertEqual('1.2.840.10008.1.2.4.51', GetTransferSyntax(z.read('IMAGES/IM0'))) - z = GetArchive(_REMOTE, '/tools/create-media-extended?resources=%s&transcode=1.2.840.10008.1.2.4.57' % info['ParentStudy']) + z, resp = GetArchive(_REMOTE, '/tools/create-media-extended?resources=%s&transcode=1.2.840.10008.1.2.4.57&filename=toto.zip' % info['ParentStudy']) self.assertEqual(2, len(z.namelist())) self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read('IMAGES/IM0'))) - - + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7): + self.assertEqual('filename="toto.zip"', resp['content-disposition']) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + z40 = PostArchive(_REMOTE, '/tools/create-archive', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + z80 = PostArchive(_REMOTE, '/tools/create-archive', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + + z40 = PostArchive(_REMOTE, '/tools/create-media', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + z80 = PostArchive(_REMOTE, '/tools/create-media', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + + z40 = PostArchive(_REMOTE, '/tools/create-media-extended', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 40 + }) + z80 = PostArchive(_REMOTE, '/tools/create-media-extended', { + 'Resources' : [ info['ParentStudy'] ], + 'Transcode' : '1.2.840.10008.1.2.4.50', + 'LossyQuality': 80 + }) + + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) + + z40, resp = GetArchive(_REMOTE, '/tools/create-archive?resources=%s&transcode=1.2.840.10008.1.2.4.50&lossy-quality=40' % info['ParentStudy']) + z80, resp = GetArchive(_REMOTE, '/tools/create-archive?resources=%s&transcode=1.2.840.10008.1.2.4.50&lossy-quality=80' % info['ParentStudy']) + size40 = sum([zinfo.file_size for zinfo in z40.filelist]) + size80 = sum([zinfo.file_size for zinfo in z80.filelist]) + self.assertLess(size40, size80) def test_download_file_transcode(self): if IsOrthancVersionAbove(_REMOTE, 1, 12, 2): info = UploadInstance(_REMOTE, 'TransferSyntaxes/1.2.840.10008.1.2.1.dcm') - self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax( - DoGet(_REMOTE, '/instances/%s/file' % info['ID']))) - - self.assertEqual('1.2.840.10008.1.2.4.50', GetTransferSyntax( - DoGet(_REMOTE, '/instances/%s/file?transcode=1.2.840.10008.1.2.4.50' % info['ID']))) + # self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax( + # DoGet(_REMOTE, '/instances/%s/file' % info['ID']))) + + # self.assertEqual('1.2.840.10008.1.2.4.50', GetTransferSyntax( + # DoGet(_REMOTE, '/instances/%s/file?transcode=1.2.840.10008.1.2.4.50' % info['ID']))) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7): + # resp, content = DoGetRaw(_REMOTE, '/instances/%s/file?filename=toto.dcm' % info['ID']) + # self.assertEqual('filename="toto.dcm"', resp['content-disposition']) + + # resp, content = DoGetRaw(_REMOTE, '/instances/%s/file?transcode=1.2.840.10008.1.2.4.50&filename=toto.dcm' % info['ID']) + # self.assertEqual('filename="toto.dcm"', resp['content-disposition']) + + # resp, content = DoGetRaw(_REMOTE, '/instances/%s/file?filename="toto".dcm' % info['ID']) + # self.assertEqual('filename="\"toto\".dcm"', resp['content-disposition']) + + resp, content40 = DoGetRaw(_REMOTE, '/instances/%s/file?transcode=1.2.840.10008.1.2.4.50&lossy-quality=40' % info['ID']) + resp, content80 = DoGetRaw(_REMOTE, '/instances/%s/file?transcode=1.2.840.10008.1.2.4.50&lossy-quality=80' % info['ID']) + self.assertLess(len(content40), len(content80)) def test_modify_keep_source(self): @@ -7243,7 +7377,7 @@ SYNTAXES = [ '1.2.840.10008.1.2', '1.2.840.10008.1.2.1', - #'1.2.840.10008.1.2.1.99', # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5) + '1.2.840.10008.1.2.1.99', # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5) '1.2.840.10008.1.2.2', '1.2.840.10008.1.2.4.50', '1.2.840.10008.1.2.4.51', @@ -11767,3 +11901,55 @@ 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']) + + def test_order_by_non_existing_metadata(self): + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + r = UploadInstance(_REMOTE, 'sample-pdf.dcm') + + # order by a metadata that does not exist (PDF do not have IndexInSeries) + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instances', + 'ParentSeries': r['ParentSeries'], + 'Query' : { + }, + 'OrderBy' : [ + { + 'Type': 'Metadata', + 'Key': 'IndexInSeries', + 'Direction': 'ASC' + } + ] + }) + self.assertEqual(1, len(a)) + + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instances', + 'ParentSeries': r['ParentSeries'], + 'Query' : { + }, + 'OrderBy' : [ + { + 'Type': 'Metadata', + 'Key': '9876', + 'Direction': 'ASC' + } + ] + }) + self.assertEqual(1, len(a)) + + def test_order_by_non_existing_tag(self): + if IsOrthancVersionAbove(_REMOTE, 1, 12, 7) and HasExtendedFind(_REMOTE): + r = UploadInstance(_REMOTE, 'sample-pdf.dcm') + + # order by a DICOM Tag that does not exist (PDF do not have ROWS) + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instances', + 'ParentSeries': r['ParentSeries'], + 'Query' : { + }, + 'OrderBy' : [ + { + 'Type': 'DicomTag', + 'Key': 'Rows', + 'Direction': 'ASC' + } + ] + }) + self.assertEqual(1, len(a)) diff -r d08518883f2f -r 5140a8917254 Tests/Toolbox.py --- a/Tests/Toolbox.py Fri Feb 07 12:24:05 2025 +0100 +++ b/Tests/Toolbox.py Mon Mar 10 18:58:07 2025 +0100 @@ -240,7 +240,8 @@ return zipfile.ZipFile(StringIO(s), "r") def GetArchive(orthanc, uri): - return ParseArchive(DoGet(orthanc, uri)) + (resp, content) = DoGetRaw(orthanc, uri) + return ParseArchive(content), resp def PostArchive(orthanc, uri, body): # http://stackoverflow.com/a/1313868/881731