Skip to content

Commit

Permalink
Make registering new UIDs easier (#877)
Browse files Browse the repository at this point in the history
  • Loading branch information
scaramallion authored Nov 8, 2023
1 parent 07af4c8 commit 2a9b42c
Show file tree
Hide file tree
Showing 15 changed files with 719 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/python:3.10
- image: cimg/python:3.10
steps:
- checkout
- run:
Expand Down
1 change: 1 addition & 0 deletions docs/changelog/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release Notes
.. toctree::
:maxdepth: 1

v2.2.0
v2.1.0
v2.0.1
v2.0.0
Expand Down
18 changes: 18 additions & 0 deletions docs/changelog/v2.2.0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.. _v2.2.0:

2.2.0
=====

Fixes
.....


Enhancements
............

* Added :func:`~pynetdicom.sop_class.register_uid` to make registering new
private and public SOP Classes easier (:issue:`799`)


Changes
.......
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ If you're new to *pynetdicom* then start here:

* **Basics**: :doc:`Installation</tutorials/installation>` |
:doc:`Writing your first SCU</tutorials/create_scu>` |
:doc:`Writing your first SCP</tutorials/create_scp>`
:doc:`Writing your first SCP</tutorials/create_scp>` |
:doc:`Registering a new SOP Class</tutorials/register_sop_class>`


.. _index_guide:
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/service_classes.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.. _api_serviceclasses:

.. py:module:: pynetdicom.service_class
Service Classes (:mod:`pynetdicom.service_class`)
==================================================

Expand Down
1 change: 1 addition & 0 deletions docs/reference/sop_classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ SOP Class Utilities
.. autosummary::
:toctree: generated/

register_uid
SOPClass
uid_to_sop_class
uid_to_service_class
Expand Down
1 change: 1 addition & 0 deletions docs/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ New to *pynetdicom*? Then these tutorials should get you up and running.
installation
create_scu
create_scp
register_sop_class
58 changes: 58 additions & 0 deletions docs/tutorials/register_sop_class.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
===========================
Registering a new SOP Class
===========================

.. currentmodule:: pynetdicom

You may occasionally come across a private SOP Class UID you like to be able
to receive, or perhaps there's a public SOP Class from a recent release of the
DICOM Standard that hasn't yet been added to *pynetdicom*. In this short
tutorial you'll learn how to register your own UID so it can be used like
the SOP Classes included by *pynetdicom*.

To register new UIDs you use the :func:`~pynetdicom.sop_class.register_uid` function,
which takes the UID to be registered, a `keyword` that will be used as the
variable name for the new UID and the *pynetdicom*
:mod:`~pynetdicom.service_class` to register the UID with::

from pynetdicom import register_uid
from pynetdicom.service_class import StorageServiceClass

register_uid(
"1.2.246.352.70.1.70",
PrivateRTPlanStorage,
StorageServiceClass,
)

The UID can then be imported from the :mod:`~pynetdicom.sop_class` module and
used like other UIDs::

from pynetdicom import AE, evt
from pynetdicom.sop_class import PrivateRTPlanStorage

def handle_store(evt):
ds = event.dataset
ds.file_meta = event.file_meta
ds.save_as(ds.SOPInstanceUID)

return 0x0000

ae = AE()
# or ae.add_supported_context("1.2.246.352.70.1.70")
ae.add_supported_context(PrivateRTPlanStorage)
ae.start_server(("localhost", 11112), evt_handlers=[(evt.EVT_C_STORE, handle_store)])


When registering a new UID with the
:class:`~pynetdicom.service_class.QueryRetrieveServiceClass`, you must also
specify which of the three DIMSE-C message types the UID is to be used with::

from pynetdicom import register_uid
from pynetdicom.service_class import QueryRetrieveServiceClass

register_uid(
"1.2.246.352.70.1.70",
PrivateQueryFind,
QueryRetrieveServiceClass,
dimse_msg_type="C-FIND" # or "C-GET" or "C-MOVE"
)
1 change: 1 addition & 0 deletions pynetdicom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
UnifiedProcedurePresentationContexts,
VerificationPresentationContexts,
)
from pynetdicom.sop_class import register_uid


# Setup default logging
Expand Down
64 changes: 41 additions & 23 deletions pynetdicom/service_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -1538,18 +1538,8 @@ class QueryRetrieveServiceClass(ServiceClass):
"SpectroscopyData",
"EncapsulatedDocument",
]

def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"""The SCP implementation for the Query/Retrieve Service Class.
Parameters
----------
req : dimse_primitives.C_FIND or C_GET or C_MOVE
The request primitive received from the peer.
context : presentation.PresentationContext
The presentation context that the SCP is operating under.
"""
_find_uids = [
_SUPPORTED_UIDS = {
"C-FIND": [
"1.2.840.10008.5.1.4.1.2.1.1",
"1.2.840.10008.5.1.4.1.2.2.1",
"1.2.840.10008.5.1.4.1.2.3.1",
Expand All @@ -1560,8 +1550,8 @@ def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"1.2.840.10008.5.1.4.44.2",
"1.2.840.10008.5.1.4.45.2",
"1.2.840.10008.5.1.4.1.1.200.4",
]
_get_uids = [
],
"C-GET": [
"1.2.840.10008.5.1.4.1.2.1.3",
"1.2.840.10008.5.1.4.1.2.2.3",
"1.2.840.10008.5.1.4.1.2.3.3",
Expand All @@ -1574,8 +1564,8 @@ def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"1.2.840.10008.5.1.4.44.4",
"1.2.840.10008.5.1.4.45.4",
"1.2.840.10008.5.1.4.1.1.200.6",
]
_move_uids = [
],
"C-MOVE": [
"1.2.840.10008.5.1.4.1.2.1.2",
"1.2.840.10008.5.1.4.1.2.2.2",
"1.2.840.10008.5.1.4.1.2.3.2",
Expand All @@ -1587,15 +1577,35 @@ def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"1.2.840.10008.5.1.4.44.3",
"1.2.840.10008.5.1.4.45.3",
"1.2.840.10008.5.1.4.1.1.200.5",
]
],
}

if isinstance(req, C_FIND) and context.abstract_syntax in _find_uids:
def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"""The SCP implementation for the Query/Retrieve Service Class.
Parameters
----------
req : dimse_primitives.C_FIND or C_GET or C_MOVE
The request primitive received from the peer.
context : presentation.PresentationContext
The presentation context that the SCP is operating under.
"""
if (
isinstance(req, C_FIND)
and context.abstract_syntax in self._SUPPORTED_UIDS["C-FIND"]
):
self.statuses = QR_FIND_SERVICE_CLASS_STATUS
self._c_find_scp(req, context)
elif isinstance(req, C_GET) and context.abstract_syntax in _get_uids:
elif (
isinstance(req, C_GET)
and context.abstract_syntax in self._SUPPORTED_UIDS["C-GET"]
):
self.statuses = QR_GET_SERVICE_CLASS_STATUS
self._get_scp(req, context)
elif isinstance(req, C_MOVE) and context.abstract_syntax in _move_uids:
elif (
isinstance(req, C_MOVE)
and context.abstract_syntax in self._SUPPORTED_UIDS["C-MOVE"]
):
self.statuses = QR_MOVE_SERVICE_CLASS_STATUS
self._move_scp(req, context)
else:
Expand Down Expand Up @@ -2379,6 +2389,9 @@ class BasicWorklistManagementServiceClass(QueryRetrieveServiceClass):
"""Implementation of the Basic Worklist Management Service Class."""

statuses = QR_FIND_SERVICE_CLASS_STATUS
_SUPPORTED_UIDS = {
"C-FIND": ["1.2.840.10008.5.1.4.31"],
}

def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"""The SCP implementation for Basic Worklist Management.
Expand All @@ -2392,7 +2405,7 @@ def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"""
if (
isinstance(req, C_FIND)
and context.abstract_syntax == "1.2.840.10008.5.1.4.31"
and context.abstract_syntax in self._SUPPORTED_UIDS["C-FIND"]
):
self._c_find_scp(req, context)
else:
Expand Down Expand Up @@ -2585,6 +2598,9 @@ class SubstanceAdministrationQueryServiceClass(QueryRetrieveServiceClass):
"""Implementation of the Substance Administration Query Service"""

statuses = SUBSTANCE_ADMINISTRATION_SERVICE_CLASS_STATUS
_SUPPORTED_UIDS = {
"C-FIND": ["1.2.840.10008.5.1.4.41", "1.2.840.10008.5.1.4.42"],
}

def SCP(self, req: "_QR", context: "PresentationContext") -> None:
"""The SCP implementation for the Relevant Patient Information Query
Expand All @@ -2597,8 +2613,10 @@ def SCP(self, req: "_QR", context: "PresentationContext") -> None:
context : presentation.PresentationContext
The presentation context that the SCP is operating under.
"""
uids = ["1.2.840.10008.5.1.4.41", "1.2.840.10008.5.1.4.42"]
if isinstance(req, C_FIND) and context.abstract_syntax in uids:
if (
isinstance(req, C_FIND)
and context.abstract_syntax in self._SUPPORTED_UIDS["C-FIND"]
):
self._c_find_scp(req, context)
else:
raise ValueError(
Expand Down
Loading

0 comments on commit 2a9b42c

Please sign in to comment.