Skip to content

Commit

Permalink
Replace obsolete BaseSCP
Browse files Browse the repository at this point in the history
  • Loading branch information
dwikler committed Dec 14, 2024
1 parent d029615 commit d349790
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 77 deletions.
33 changes: 0 additions & 33 deletions tdwii_plus_examples/TDWII_PPVS_subscriber/basescp.py

This file was deleted.

2 changes: 1 addition & 1 deletion tdwii_plus_examples/TDWII_PPVS_subscriber/echoscp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pynetdicom import ALL_TRANSFER_SYNTAXES, evt
from pynetdicom.sop_class import Verification

from tdwii_plus_examples.TDWII_PPVS_subscriber.basescp import BaseSCP
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.nevent_receiver_handlers import (
handle_echo,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pynetdicom import ALL_TRANSFER_SYNTAXES, UnifiedProcedurePresentationContexts, evt

from tdwii_plus_examples.TDWII_PPVS_subscriber.basescp import BaseSCP
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.echoscp import EchoSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.nevent_receiver_handlers import (
handle_nevent,
Expand Down
2 changes: 1 addition & 1 deletion tdwii_plus_examples/TDWII_PPVS_subscriber/ppvsscp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pynetdicom import ALL_TRANSFER_SYNTAXES, AllStoragePresentationContexts

from tdwii_plus_examples import tdwii_config
from tdwii_plus_examples.TDWII_PPVS_subscriber.basescp import BaseSCP
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.nevent_receiver import NEventReceiver
from tdwii_plus_examples.TDWII_PPVS_subscriber.storescp import StoreSCP

Expand Down
2 changes: 1 addition & 1 deletion tdwii_plus_examples/TDWII_PPVS_subscriber/storescp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pynetdicom import ALL_TRANSFER_SYNTAXES, AllStoragePresentationContexts, evt

from tdwii_plus_examples.TDWII_PPVS_subscriber.basescp import BaseSCP
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.cstore_handler import handle_store
from tdwii_plus_examples.TDWII_PPVS_subscriber.echoscp import EchoSCP

Expand Down
55 changes: 39 additions & 16 deletions tdwii_plus_examples/basescp.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from argparse import Namespace
import logging

from pynetdicom import AE, evt, ALL_TRANSFER_SYNTAXES
from pynetdicom import AE, evt
from pynetdicom.apps.common import setup_logging
from pynetdicom.sop_class import Verification

from tdwii_plus_examples.basehandlers import handle_open, handle_close

Expand All @@ -13,7 +11,7 @@ class BaseSCP:
The BaseSCP class is a base class for DICOM Service Class Providers (SCPs).
This class provides basic functionality for creating and managing
a DICOM Service Class Provider (SCP) including setting up the
a DICOM Service Class Provider (SCP) including setting up the
Application Entity (AE) and handling incoming associations.
Usage:
Expand Down Expand Up @@ -44,38 +42,39 @@ def __init__(self,
logger=None):

"""
Initializes a new instance of the BaseSCP class.
Initializes a new instance of the BaseSCP class.
This method creates an AE without presentation contexts.
Parameters
----------
ae_title : str
The title of the Application Entity (AE)
Required, default: "BASE_SCP"
Optional, default: "BASE_SCP"
bind_address : str
The IP address or hostname of the AE
Required, default: ""
A specific IP address or hostname of the AE
Optional, default: "" will bind to all interfaces.
port: int
The port number to listen on
Required, default: 11112 (as registered for DICOM at IANA)
Optional, default: 11112 (as registered for DICOM at IANA)
logger: logging.Logger
A logger instance
Optional, default: None, a debug logger will be used.
"""
if logger is None:
self.logger = setup_logging(Namespace(log_type="d",
log_level="debug"),
"base_scp")
elif not isinstance(logger, logging.Logger):
raise TypeError("logger must be an instance of logging.Logger")
self.logger = setup_logging(
Namespace(log_type="d", log_level="debug"), "base_scp")
# elif not isinstance(self.logger, logging.Logger):
# raise TypeError("logger must be an instance of logging.Logger")
else:
self.logger = logger

if not isinstance(bind_address, str) or not bind_address.strip():
raise ValueError("bind_address must be a non-empty string")
if not isinstance(bind_address, str):
raise ValueError("bind_address must be a string")
if not bind_address:
self.logger.debug("bind_address empty, binding to all interfaces")
self.bind_address = bind_address

if not isinstance(port, int):
Expand All @@ -95,20 +94,44 @@ def __init__(self,
self.threaded_server = None

def _add_contexts(self):
"""
Adds DICOM presentation contexts to the SCP class.
This method is intended to be overridden in derived classes.
"""
pass # base class, do nothing, pure virtual

def _add_handlers(self):
"""
Adds handlers for processing DICOM events from incoming associations.
This method is intended to be overridden in derived classes.
"""
# To define actions when a TCP connection in opened or closed
self.handlers.append((evt.EVT_CONN_OPEN, handle_open,
[self.logger]))
self.handlers.append((evt.EVT_CONN_CLOSE, handle_close,
[self.logger]))

def run(self):
"""
Start the SCP server and listen for incoming association requests
This method starts an SCP server and begins listening for incoming
association requests. The server is run in a separate thread and will
not block the calling thread.
"""
# Listen for incoming association requests
self.threaded_server = self.ae.start_server(
(self.bind_address, self.port),
evt_handlers=self.handlers,
block=False)

def stop(self):
"""
Stop the SCP server.
This method shuts down the SCP server by terminating any active
association servers and thread of the AE.
"""
self.ae.shutdown()
50 changes: 26 additions & 24 deletions tdwii_plus_examples/tests/test_basescp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import subprocess
import time
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.basehandlers import handle_open
from pynetdicom import ALL_TRANSFER_SYNTAXES
from pynetdicom.sop_class import Verification


Expand All @@ -14,8 +12,9 @@ def __init__(self, bind_address, logger=None):
super().__init__(bind_address=bind_address, logger=logger)

def _add_contexts(self):
BaseSCP._add_contexts(self)
self.ae.add_supported_context(Verification, ALL_TRANSFER_SYNTAXES)
super()._add_contexts()
self.ae.add_supported_context(Verification, "1.2.840.10008.1.2")


class TestBaseSCP(unittest.TestCase):

Expand All @@ -24,51 +23,54 @@ def setUp(self):
# with a memory handler to store up to 100 log messages
self.scp_logger = logging.getLogger('basescp')
self.scp_logger.setLevel(logging.INFO)
self.memory_handler = MemoryHandler(100)
self.memory_handler = MemoryHandler(100)
self.scp_logger.addHandler(self.memory_handler)
# Set up the logger for this test to DEBUG level
# with a stream handler to print the log messages to the console

# Set up the logger for this test to DEBUG level
# with a stream handler to print the log messages to the console
self.test_logger = logging.getLogger('test_basescp')
self.test_logger.setLevel(logging.DEBUG)
self.stream_handler = logging.StreamHandler()
self.stream_handler = logging.StreamHandler()
self.test_logger.addHandler(self.stream_handler)

# Create the SCP
#self.scp = BaseSCP(bind_address="localhost", logger=self.scp_logger)
# Create a subclass of BaseSCP with only 1 presentation context:
# the required Verification presentation context with the
# default DICOM transfer syntax (Implicit VR Little Endian)
self.scp = EchoSCP(bind_address="localhost", logger=self.scp_logger)


def test_run_and_check_log(self):
# Run the SCP
self.scp.run()

# Send an echo request using pynetdicom's echoscu.py
subprocess.check_call(['python', '-m', 'pynetdicom', 'echoscu',
'localhost', '11112',
'-aet', 'ECHOSCU', '-aec', 'BASE_SCP'])
subprocess.check_call(['python', '-m', 'pynetdicom', 'echoscu',
'localhost', '11112',
'-aet', 'ECHOSCU', '-aec', 'BASE_SCP'])

# Wait for 1 second to ensure the logs are generated
time.sleep(1)

# Get the log messages
log_messages = [record.getMessage() for record
in self.memory_handler.buffer]
log_messages = [record.getMessage() for record
in self.memory_handler.buffer]

# Check the EVT_CONN_OPEN event log message
self.test_logger.info(f"Checking EVT_CONN_OPEN event log message: {log_messages[0]}")
self.assertRegex(log_messages[0],
self.test_logger.info(f"Checking EVT_CONN_OPEN event log message: "
f"{log_messages[0]}")
self.assertRegex(log_messages[0],
r"Succesful connection from " +
r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):([\w]{3,5})")
# Stop the SCP
self.scp.stop()

# Check the EVT_CONN_CLOSED event log message
self.test_logger.info(f"Checking EVT_CONN_CLOSE event log message: {log_messages[-1]}")
self.assertRegex(log_messages[-1],
self.test_logger.info(f"Checking EVT_CONN_CLOSE event log message: "
f"{log_messages[-1]}")
self.assertRegex(log_messages[-1],
r"Closed connection with " +
r"([\w]+)@"+
r"([\w]+)@" +
r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):([\w]{3,5})")


if __name__ == "__main__":
unittest.main()
unittest.main()

0 comments on commit d349790

Please sign in to comment.