changeset 776:932d09c3be0a

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 21 Jan 2025 18:50:03 +0100 (4 months ago)
parents be7312788221 (current diff) 82c61e3f7737 (diff)
children 287aae544b31
files
diffstat 6 files changed, 254 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NewTests/CGet/docker-compose-c-get.yml	Tue Jan 21 18:50:03 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	Tue Jan 21 18:50:03 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	Tue Jan 21 18:50:03 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/test_transfer.py	Tue Jan 21 18:48:59 2025 +0100
+++ b/NewTests/Concurrency/test_transfer.py	Tue Jan 21 18:50:03 2025 +0100
@@ -45,7 +45,7 @@
 
     @classmethod
     def tearDownClass(cls):
-        #cls.cleanup()
+        cls.cleanup()
         pass
 
     def clean_start(self):
--- a/NewTests/README	Tue Jan 21 18:48:59 2025 +0100
+++ b/NewTests/README	Tue Jan 21 18:50:03 2025 +0100
@@ -197,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.
 
@@ -212,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	Tue Jan 21 18:48:59 2025 +0100
+++ b/NewTests/requirements.txt	Tue Jan 21 18:50:03 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