Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refact echoscp #87

Merged
merged 8 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions tdwii_plus_examples/TDWII_PPVS_subscriber/basescp.py

This file was deleted.

33 changes: 0 additions & 33 deletions tdwii_plus_examples/TDWII_PPVS_subscriber/echoscp.py

This file was deleted.

12 changes: 6 additions & 6 deletions tdwii_plus_examples/TDWII_PPVS_subscriber/nevent_receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from pynetdicom import ALL_TRANSFER_SYNTAXES, UnifiedProcedurePresentationContexts, evt

from tdwii_plus_examples.TDWII_PPVS_subscriber.basescp import BaseSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.echoscp import EchoSCP
from tdwii_plus_examples.basescp import BaseSCP
from tdwii_plus_examples.cechoscp import CEchoSCP
from tdwii_plus_examples.TDWII_PPVS_subscriber.nevent_receiver_handlers import (
handle_nevent,
)
Expand Down Expand Up @@ -58,23 +58,23 @@ def nevent_cb(**kwargs):
logger.warning(f"Unknown Event Type ID: {event_type_id}")


class NEventReceiver(EchoSCP):
class NEventReceiver(CEchoSCP):
def __init__(
self, nevent_callback=None, ae_title: str = "NEVENT_RECEIVER", port: int = 11115, logger=None, bind_address: str = ""
):
if nevent_callback is None:
self.nevent_callback = nevent_cb # fallback to something that just logs incoming events
else:
self.nevent_callback = nevent_callback
EchoSCP.__init__(self, ae_title=ae_title, port=port, logger=logger, bind_address=bind_address)
CEchoSCP.__init__(self, ae_title=ae_title, port=port, logger=logger, bind_address=bind_address)

def _add_contexts(self):
EchoSCP._add_contexts(self)
CEchoSCP._add_contexts(self)
for cx in UnifiedProcedurePresentationContexts:
self.ae.add_supported_context(cx.abstract_syntax, ALL_TRANSFER_SYNTAXES, scp_role=True, scu_role=False)

def _add_handlers(self):
EchoSCP._add_handlers(self)
CEchoSCP._add_handlers(self)
self.handlers.append((evt.EVT_N_EVENT_REPORT, handle_nevent, [self.nevent_callback, None, self.logger]))

def run(self):
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
12 changes: 6 additions & 6 deletions tdwii_plus_examples/TDWII_PPVS_subscriber/storescp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

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
from tdwii_plus_examples.cechoscp import CEchoSCP


class StoreSCP(EchoSCP):
class StoreSCP(CEchoSCP):
def __init__(
self,
ae_title: str = "STORE_SCP",
Expand All @@ -28,15 +28,15 @@ def __init__(
else:
self.handle_cstore = custom_handler
self.store_directory = store_directory
EchoSCP.__init__(self, ae_title=ae_title, port=port, logger=logger, bind_address=bind_address)
CEchoSCP.__init__(self, ae_title=ae_title, port=port, logger=logger, bind_address=bind_address)

def _add_contexts(self):
EchoSCP._add_contexts(self)
CEchoSCP._add_contexts(self)
for context in self.storage_presentation_contexts:
self.ae.add_supported_context(context.abstract_syntax, self.transfer_syntaxes)

def _add_handlers(self):
EchoSCP._add_handlers(self)
CEchoSCP._add_handlers(self)
args = Namespace(ignore=False, output_directory=self.store_directory)

self.handlers.append((evt.EVT_C_STORE, self.handle_cstore, [args, self.logger]))
Expand Down
44 changes: 44 additions & 0 deletions tdwii_plus_examples/basehandlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
def handle_open(event, logger):
"""Handler for evt.EVT_CONN_OPEN

Parameters
----------
event : events.Event
The corresponding event.
logger : logging.Logger
The application's logger.

Returns
-------
int
The status of possible processing of opened connection parameters,
always ``0x0000`` (Success).
"""
requestor = event.assoc.requestor
addr, port = requestor.address, requestor.port
logger.info(f"Succesful connection from {addr}:{port}")

return 0x0000


def handle_close(event, logger):
"""Handler for evt.EVT_CONN_CLOSE

Parameters
----------
event : events.Event
The corresponding event.
logger : logging.Logger
The application's logger.

Returns
-------
int
The status of possible processing of closed connection parameters,
always ``0x0000`` (Success).
"""
requestor = event.assoc.requestor
aet, addr, port = requestor.ae_title, requestor.address, requestor.port
logger.info(f"Closed connection with {aet}@{addr}:{port}")

return 0x0000
143 changes: 143 additions & 0 deletions tdwii_plus_examples/basescp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from argparse import Namespace
import logging

from pynetdicom import AE, evt
from pynetdicom.apps.common import setup_logging

from tdwii_plus_examples.basehandlers import handle_open, handle_close


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
Application Entity (AE) and handling incoming associations.

Usage:
To use the BaseSCP class, create a subclass and override
the [_add_contexts] and [_add_handlers] methods
to add presentation contexts and handlers for specific DICOM services.

Attributes:
ae_title (str): The title of the Application Entity (AE)
bind_address (str): The IP address or hostname of the AE
port (int): The port number the AE will listen on
logger(logging.Logger): A logger instance

Methods:
_add_contexts(self)
Adds presentation contexts to the SCP instance.
_add_handlers(self)
Adds handlers for DICOM communication events to the SCP instance.
run(self)
Starts the SCP AE thread.
stop(self)
Stops the SCP AE.
"""
def __init__(self,
ae_title: str = "BASE_SCP",
bind_address: str = "",
port: int = 11112,
logger=None):

"""
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)
Optional, default: "BASE_SCP"

bind_address : str
A specific IP address or hostname of the AE
Optional, default: "" will bind to all interfaces.

port: int
The port number to listen on
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")
else:
self.logger = logger

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):
raise TypeError("port must be an integer")
self.port = port

if not isinstance(ae_title, str):
raise TypeError("ae_title must be a string")
self.ae_title = ae_title
self.ae = AE(self.ae_title)

self._add_contexts()

self.handlers = []
self._add_handlers()

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 TCP 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
try:
self.threaded_server = self.ae.start_server(
(self.bind_address, self.port),
evt_handlers=self.handlers,
block=False)
if self.threaded_server is not None:
self.logger.info("SCP server started successfully")
except Exception as e:
self.logger.error("SCP server failed to start: %s", e)

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()
22 changes: 22 additions & 0 deletions tdwii_plus_examples/cechohandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
def handle_cecho(event, logger):
"""Handler for evt.EVT_C_ECHO.
Parameters
----------
event : events.Event
The corresponding event.
logger : logging.Logger
The application's logger.

Returns
-------
int
The status of the C-ECHO operation, always ``0x0000`` (Success).
"""
requestor = event.assoc.requestor
timestamp = event.timestamp.strftime("%Y-%m-%d %H:%M:%S")
aet, addr, port = requestor.ae_title, requestor.address, requestor.port
logger.info(
f"Received C-ECHO request from {aet}@{addr}:{port} at {timestamp}"
)

return 0x0000
Loading
Loading