From 483532999cc5afb7296478654084635089a23e32 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Wed, 3 Jun 2026 10:15:04 +0100 Subject: [PATCH] `MWLServer` now supports C-ECHO `MWLServer`: registers a C-ECHO handler and adds `Verification` to its supported presentation contexts. `PACSServer`: adds `Verification` to its supported presentation contexts. A C-ECHO handler was already registered, but without the Verification context the association would be rejected before reaching it. pynetdicom's [Verification Service Examples](https://pydicom.github.io/pynetdicom/stable/examples/verification.html) shows that an SCP must explicitly call `ae.add_supported_context(Verification)`. --- src/server.py | 4 ++++ tests/test_server.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/server.py b/src/server.py index d985b21d..716e5b0a 100644 --- a/src/server.py +++ b/src/server.py @@ -14,6 +14,7 @@ DigitalMammographyXRayImageStorageForProcessing, # type: ignore[attr-defined] ModalityPerformedProcedureStep, # type: ignore[attr-defined] ModalityWorklistInformationFind, # type: ignore[attr-defined] + Verification, # type: ignore[attr-defined] ) from services.dicom.c_echo import CEcho @@ -68,6 +69,7 @@ def start(self): dicom_uid.JPEG2000Lossless, ] self.ae = AE(ae_title=self.ae_title) + self.ae.add_supported_context(Verification) self.ae.add_supported_context(DigitalMammographyXRayImageStorageForPresentation, transfer_syntaxes) self.ae.add_supported_context(DigitalMammographyXRayImageStorageForProcessing, transfer_syntaxes) @@ -120,10 +122,12 @@ def start(self): logger.info(f"Starting MWL server: {self.ae_title} on port {self.port}") self.ae = AE(ae_title=self.ae_title) + self.ae.add_supported_context(Verification) self.ae.add_supported_context(ModalityWorklistInformationFind) self.ae.add_supported_context(ModalityPerformedProcedureStep) handlers = [ + (evt.EVT_C_ECHO, CEcho().call), (evt.EVT_C_FIND, CFind(self.storage).call), (evt.EVT_N_CREATE, NCreate(self.storage).call), (evt.EVT_N_SET, NSet(self.storage).call), diff --git a/tests/test_server.py b/tests/test_server.py index da9844e8..19f583a5 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -7,6 +7,7 @@ DigitalMammographyXRayImageStorageForProcessing, ModalityPerformedProcedureStep, ModalityWorklistInformationFind, + Verification, ) from server import MWLServer, PACSServer @@ -54,6 +55,7 @@ def test_start(self, mock_c_store, mock_c_echo, mock_ae, _mock_pacs_storage, _mo mock_ae.assert_called_once_with(ae_title="SCREENING_PACS") add_context_calls = [call.args[0] for call in mock_ae.return_value.add_supported_context.call_args_list] + assert Verification in add_context_calls assert DigitalMammographyXRayImageStorageForPresentation in add_context_calls assert DigitalMammographyXRayImageStorageForProcessing in add_context_calls mock_ae.return_value.start_server.assert_called_once_with( @@ -100,7 +102,8 @@ def test_init_defaults(self, mock_storage): mock_storage.assert_called_once_with("/var/lib/pacs/worklist.db") @patch(f"{MWLServer.__module__}.AE") - def test_start(self, mock_ae, _): + @patch(f"{MWLServer.__module__}.CEcho") + def test_start(self, mock_c_echo, mock_ae, _): subject = MWLServer() mock_ae_instance = MagicMock() mock_ae.return_value = mock_ae_instance @@ -110,6 +113,7 @@ def test_start(self, mock_ae, _): assert subject.ae == mock_ae_instance mock_ae.assert_called_once_with(ae_title="MWL_SCP") + mock_ae_instance.add_supported_context.assert_any_call(Verification) mock_ae_instance.add_supported_context.assert_any_call( ModalityWorklistInformationFind, ) @@ -121,7 +125,8 @@ def test_start(self, mock_ae, _): assert args[0] == ("0.0.0.0", 4243) assert kwargs["block"] is True assert "evt_handlers" in kwargs - assert len(kwargs["evt_handlers"]) == 3 + assert (evt.EVT_C_ECHO, mock_c_echo.return_value.call) in kwargs["evt_handlers"] + assert len(kwargs["evt_handlers"]) == 4 @patch(f"{MWLServer.__module__}.AE") def test_stop(self, *_):