diff --git a/.github/workflows/merge-pytest.yml b/.github/workflows/merge-pytest.yml index bdffcb4f5..6ec8a5e7e 100644 --- a/.github/workflows/merge-pytest.yml +++ b/.github/workflows/merge-pytest.yml @@ -69,7 +69,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 @@ -113,7 +114,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 @@ -156,7 +158,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 diff --git a/.github/workflows/pr-pytest.yml b/.github/workflows/pr-pytest.yml index 19a730b63..3e5311c9d 100644 --- a/.github/workflows/pr-pytest.yml +++ b/.github/workflows/pr-pytest.yml @@ -69,7 +69,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 @@ -113,7 +114,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 @@ -156,7 +158,8 @@ jobs: env: PYTHON_VERSION: ${{ matrix.python-version }} run: | - poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps + poetry run pytest --cov pynetdicom --ignore=pynetdicom/apps && + poetry run coverage xml - name: Send coverage results if: ${{ success() }} uses: codecov/codecov-action@v3 diff --git a/docs/changelog/v2.1.0.rst b/docs/changelog/v2.1.0.rst index 34120218c..61229ca7a 100644 --- a/docs/changelog/v2.1.0.rst +++ b/docs/changelog/v2.1.0.rst @@ -21,6 +21,8 @@ Enhancements * Added support for *Repository Query* to :meth:`~pynetdicom.association.Association.send_c_find` and :class:`~pynetdicom.service_class.QueryRetrieveServiceClass` (:issue:`878`) +* Added support for :class:`Inventory Query/Retrieve Service Class + ` (:issue:`879`) Changes ....... diff --git a/docs/index.rst b/docs/index.rst index 528953632..5e8dba473 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -77,6 +77,7 @@ Supported Service Classes * :doc:`Hanging Protocol Query/Retrieve ` * :doc:`Implant Template Query/Retrieve ` * :doc:`Instance Availability Notification ` +* :doc:`Inventory Query/Retrieve ` * :doc:`Media Creation Management ` * :doc:`Non-Patient Object Storage ` * :doc:`Print Management ` diff --git a/docs/reference/service_classes.rst b/docs/reference/service_classes.rst index 2d9588416..0d8a092a0 100644 --- a/docs/reference/service_classes.rst +++ b/docs/reference/service_classes.rst @@ -17,6 +17,7 @@ pynetdicom supports the following Service Classes DefinedProcedureProtocolQueryRetrieveServiceClass HangingProtocolQueryRetrieveServiceClass ImplantTemplateQueryRetrieveServiceClass + InventoryQueryRetrieveServiceClass NonPatientObjectStorageServiceClass ProtocolApprovalQueryRetrieveServiceClass QueryRetrieveServiceClass diff --git a/docs/service_classes/basic_worklist_service_class.rst b/docs/service_classes/basic_worklist_service_class.rst index 81ff53bb1..b84152561 100644 --- a/docs/service_classes/basic_worklist_service_class.rst +++ b/docs/service_classes/basic_worklist_service_class.rst @@ -54,7 +54,7 @@ Basic Worklist Management Service Statuses +==================+==========+==============================================+ | 0xA700 | Failure | Out of resources | +------------------+----------+----------------------------------------------+ -| 0xA900 | Failure | Identifier does not match SOP Class | +| 0xA900 | Failure | Data Set does not match SOP Class | +------------------+----------+----------------------------------------------+ | 0xC000 to 0xCFFF | Failure | Unable to process | +------------------+----------+----------------------------------------------+ diff --git a/docs/service_classes/index.rst b/docs/service_classes/index.rst index c173ba517..072e34f1a 100644 --- a/docs/service_classes/index.rst +++ b/docs/service_classes/index.rst @@ -13,6 +13,7 @@ Supported Service Classes display_system_service_class hanging_protocol_service_class implant_template_service_class + inventory_service_class instance_availability media_creation modality_performed_procedure_step diff --git a/docs/service_classes/inventory_service_class.rst b/docs/service_classes/inventory_service_class.rst new file mode 100644 index 000000000..9b1fe8148 --- /dev/null +++ b/docs/service_classes/inventory_service_class.rst @@ -0,0 +1,289 @@ +.. _service_inventory: + +Inventory Query/Retrieve Service Class +====================================== +The :dcm:`Inventory Query/Retrieve Service Class` +defines a service that facilitates discovery of and access to Inventory composite +objects. + +Supported SOP Classes +--------------------- + +.. _inventory_sops: + ++-------------------------------+-------------------+ +| UID | SOP Class | ++===============================+===================+ +| 1.2.840.10008.5.1.4.1.1.201.2 | Inventory FIND | ++-------------------------------+-------------------+ +| 1.2.840.10008.5.1.4.1.1.201.3 | Inventory MOVE | ++-------------------------------+-------------------+ +| 1.2.840.10008.5.1.4.1.1.201.4 | Inventory GET | ++-------------------------------+-------------------+ + + +DIMSE Services +-------------- + ++-----------------+-----------------------------------------+ +| DIMSE Service | Usage SCU/SCP | ++=================+=========================================+ +| *Inventory FIND* | ++-----------------+-----------------------------------------+ +| C-FIND | Mandatory/Mandatory | ++-----------------+-----------------------------------------+ + ++-----------------+-----------------------------------------+ +| DIMSE Service | Usage SCU/SCP | ++=================+=========================================+ +| *Inventory MOVE* | ++-----------------+-----------------------------------------+ +| C-MOVE | Mandatory/Mandatory | ++-----------------+-----------------------------------------+ + ++-----------------+-----------------------------------------+ +| DIMSE Service | Usage SCU/SCP | ++=================+=========================================+ +| *Inventory GET* | ++-----------------+-----------------------------------------+ +| C-GET | Mandatory/Mandatory | ++-----------------+-----------------------------------------+ + +.. _inventory_statuses: + +Statuses +-------- + +.. _inventory_find_statuses: + +C-FIND Statuses +~~~~~~~~~~~~~~~ + ++------------+----------+----------------------------------+ +| Code (hex) | Category | Description | ++============+==========+==================================+ +| 0x0000 | Success | Success | ++------------+----------+----------------------------------+ +| 0x0122 | Failure | SOP Class not supported | ++------------+----------+----------------------------------+ +| 0xFE00 | Cancel | Processing has been terminated | ++------------+----------+----------------------------------+ + +Inventory Query/Retrieve (Find) Service Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++------------------+----------+----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+==============================================+ +| 0xA700 | Failure | Out of resources | ++------------------+----------+----------------------------------------------+ +| 0xA900 | Failure | Data Set does not match SOP Class | ++------------------+----------+----------------------------------------------+ +| 0xC000 to 0xCFFF | Failure | Unable to process | ++------------------+----------+----------------------------------------------+ +| 0xFF00 | Pending | Matches are continuing | ++------------------+----------+----------------------------------------------+ +| 0xFF01 | Pending | Matches are continuing; one or more Optional | +| | | keys was not supported | ++------------------+----------+----------------------------------------------+ + +pynetdicom Inventory Query/Retrieve (Find) Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When pynetdicom is acting as an Inventory Query/Retrieve (Find) +SCP it uses the following status codes values to indicate the corresponding +issue has occurred to help aid in debugging. + ++------------------+----------+-----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+===============================================+ +| 0xC001 | Failure | Handler bound to ``evt.EVT_C_FIND`` yielded a | +| | | status Dataset with no (0000,0900) *Status* | +| | | element | ++------------------+----------+-----------------------------------------------+ +| 0xC002 | Failure | Handler bound to ``evt.EVT_C_FIND`` yielded an| +| | | invalid status object (not a pydicom Dataset | +| | | or an int) | ++------------------+----------+-----------------------------------------------+ +| 0xC310 | Failure | Failed to decode the dataset received from | +| | | the peer | ++------------------+----------+-----------------------------------------------+ +| 0xC311 | Failure | Unhandled exception raised by the handler | +| | | bound to ``evt.EVT_C_FIND`` | ++------------------+----------+-----------------------------------------------+ +| 0xC312 | Failure | Failed to encode the dataset received from | +| | | the handler bound to ``evt.EVT_C_FIND`` | ++------------------+----------+-----------------------------------------------+ + + +.. _inventory_get_statuses: + +C-GET Statuses +~~~~~~~~~~~~~~ + ++------------+----------+----------------------------------+ +| Code (hex) | Category | Description | ++============+==========+==================================+ +| 0x0000 | Success | Success | ++------------+----------+----------------------------------+ +| 0x0122 | Failure | SOP Class not supported | ++------------+----------+----------------------------------+ +| 0x0124 | Failure | Not authorised | ++------------+----------+----------------------------------+ +| 0x0210 | Failure | Duplicate invocation | ++------------+----------+----------------------------------+ +| 0x0212 | Failure | Mistyped argument | ++------------+----------+----------------------------------+ +| 0xFE00 | Cancel | Sub-operations terminated | ++------------+----------+----------------------------------+ + +Inventory Query/Retrieve (Get) Service Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++------------------+----------+----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+==============================================+ +| 0xA701 | Failure | Out of resources; unable to calculate number | +| | | of matches | ++------------------+----------+----------------------------------------------+ +| 0xA702 | Failure | Out of resources; unable to perform | +| | | sub-operations | ++------------------+----------+----------------------------------------------+ +| 0xA900 | Failure | Dataset does not match SOP Class | ++------------------+----------+----------------------------------------------+ +| 0xAA00 | Failure | None of the frames requested were found in | +| | | the SOP Instance | ++------------------+----------+----------------------------------------------+ +| 0xAA01 | Failure | Unable to create new object for this SOP | +| | | class | ++------------------+----------+----------------------------------------------+ +| 0xAA02 | Failure | Unable to extract frames | ++------------------+----------+----------------------------------------------+ +| 0xAA03 | Failure | Time-based request received for a | +| | | non-time-based original SOP Instance | ++------------------+----------+----------------------------------------------+ +| 0xAA04 | Failure | Invalid request | ++------------------+----------+----------------------------------------------+ +| 0xB000 | Warning | Sub-operations complete, one or more | +| | | or warnings | ++------------------+----------+----------------------------------------------+ +| 0xC000 to 0xCFFF | Failure | Unable to process | ++------------------+----------+----------------------------------------------+ +| 0xFF00 | Pending | Sub-operations are continuing | ++------------------+----------+----------------------------------------------+ + +pynetdicom Inventory Query/Retrieve (Get) Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++------------------+----------+-----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+===============================================+ +| 0xC001 | Failure | Handler bound to ``evt.EVT_C_GET`` yielded a | +| | | status Dataset with no (0000,0900) *Status* | +| | | element | ++------------------+----------+-----------------------------------------------+ +| 0xC002 | Failure | Handler bound to ``evt.EVT_C_GET`` yielded an | +| | | invalid status object (not a pydicom Dataset | +| | | or an int) | ++------------------+----------+-----------------------------------------------+ +| 0xC410 | Failure | Failed to decode the dataset received from | +| | | the peer | ++------------------+----------+-----------------------------------------------+ +| 0xC411 | Failure | Unhandled exception raised by the handler | +| | | bound to ``evt.EVT_C_GET`` | ++------------------+----------+-----------------------------------------------+ +| 0xC413 | Failure | The handler bound to ``evt.EVT_C_GET`` | +| | | yielded an invalid number of sub-operations | ++------------------+----------+-----------------------------------------------+ + + +.. _inventory_move_statuses: + +C-MOVE Statuses +~~~~~~~~~~~~~~~ + ++------------+----------+----------------------------------+ +| Code (hex) | Category | Description | ++============+==========+==================================+ +| 0x0000 | Success | Success | ++------------+----------+----------------------------------+ +| 0x0122 | Failure | SOP Class not supported | ++------------+----------+----------------------------------+ +| 0x0124 | Failure | Not authorised | ++------------+----------+----------------------------------+ +| 0x0210 | Failure | Duplicate invocation | ++------------+----------+----------------------------------+ +| 0x0211 | Failure | Unrecognised operation | ++------------+----------+----------------------------------+ +| 0x0212 | Failure | Mistyped argument | ++------------+----------+----------------------------------+ +| 0xFE00 | Cancel | Sub-operations terminated | ++------------+----------+----------------------------------+ + +Inventory Query/Retrieve (Move) Service Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++------------------+----------+----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+==============================================+ +| 0xA701 | Failure | Out of resources; unable to calculate number | +| | | of matches | ++------------------+----------+----------------------------------------------+ +| 0xA702 | Failure | Out of resources; unable to perform | +| | | sub-operations | ++------------------+----------+----------------------------------------------+ +| 0xA801 | Failure | Move destination unknown | ++------------------+----------+----------------------------------------------+ +| 0xA900 | Failure | Dataset does not match SOP Class | ++------------------+----------+----------------------------------------------+ +| 0xAA00 | Failure | None of the frames requested were found in | +| | | the SOP Instance | ++------------------+----------+----------------------------------------------+ +| 0xAA01 | Failure | Unable to create new object for this SOP | +| | | class | ++------------------+----------+----------------------------------------------+ +| 0xAA02 | Failure | Unable to extract frames | ++------------------+----------+----------------------------------------------+ +| 0xAA03 | Failure | Time-based request received for a | +| | | non-time-based original SOP Instance | ++------------------+----------+----------------------------------------------+ +| 0xAA04 | Failure | Invalid request | ++------------------+----------+----------------------------------------------+ +| 0xB000 | Warning | Sub-operations complete, one or more | +| | | or warnings | ++------------------+----------+----------------------------------------------+ +| 0xC000 to 0xCFFF | Failure | Unable to process | ++------------------+----------+----------------------------------------------+ +| 0xFF00 | Pending | Sub-operations are continuing | ++------------------+----------+----------------------------------------------+ + +pynetdicom Inventory Query/Retrieve (Move) Statuses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++------------------+----------+-----------------------------------------------+ +| Code (hex) | Category | Description | ++==================+==========+===============================================+ +| 0xC001 | Failure | Handler bound to ``evt.EVT_C_MOVE`` yielded a | +| | | status Dataset with no (0000,0900) *Status* | +| | | element | ++------------------+----------+-----------------------------------------------+ +| 0xC002 | Failure | Handler bound to ``evt.EVT_C_MOVE`` yielded an| +| | | invalid status object (not a pydicom Dataset | +| | | or an int) | ++------------------+----------+-----------------------------------------------+ +| 0xC510 | Failure | Failed to decode the dataset received from | +| | | the peer | ++------------------+----------+-----------------------------------------------+ +| 0xC511 | Failure | Unhandled exception raised by the handler | +| | | bound to ``evt.EVT_C_MOVE`` | ++------------------+----------+-----------------------------------------------+ +| 0xC513 | Failure | The handler bound to ``evt.EVT_C_MOVE`` | +| | | yielded an invalid number of sub-operations | ++------------------+----------+-----------------------------------------------+ +| 0xC514 | Failure | The handler bound to ``evt.EVT_C_MOVE`` | +| | | failed to yield the (address, port) | +| | | and/or the number of sub-operations | ++------------------+----------+-----------------------------------------------+ +| 0xC515 | Failure | The handler bound to ``evt.EVT_C_MOVE`` | +| | | failed to yield a valid (address, port) pair | ++------------------+----------+-----------------------------------------------+ diff --git a/poetry.lock b/poetry.lock index 4cedfe274..aa9d60959 100644 --- a/poetry.lock +++ b/poetry.lock @@ -761,13 +761,13 @@ docs = ["matplotlib", "numpy", "numpydoc", "pillow", "sphinx", "sphinx-copybutto [[package]] name = "pyfakefs" -version = "5.3.0" +version = "5.3.1" description = "pyfakefs implements a fake file system that mocks the Python file system modules." optional = true python-versions = ">=3.7" files = [ - {file = "pyfakefs-5.3.0-py3-none-any.whl", hash = "sha256:33c1f891078c727beec465e75cb314120635e2298456493cc2cc0539e2130cbb"}, - {file = "pyfakefs-5.3.0.tar.gz", hash = "sha256:e3e35f65ce55ee8ecc5e243d55cfdbb5d0aa24938f6e04e19f0fab062f255020"}, + {file = "pyfakefs-5.3.1-py3-none-any.whl", hash = "sha256:dbe268b70da64f1506baf7d7a2a2248b96b56d28d61a68859272b5fdc321c39e"}, + {file = "pyfakefs-5.3.1.tar.gz", hash = "sha256:dd1fb374039fadccf35d3f3df7aa5d239482e0650dcd240e053d3b9e78740918"}, ] [[package]] @@ -1282,18 +1282,17 @@ files = [ [[package]] name = "urllib3" -version = "2.0.7" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1301,9 +1300,9 @@ zstd = ["zstandard (>=0.18.0)"] apps = ["sqlalchemy"] dev = ["asv", "black", "codespell", "coverage", "mypy", "pyfakefs", "pytest", "pytest-cov", "ruff", "sqlalchemy"] docs = ["numpydoc", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -tests = ["pyfakefs", "pytest", "pytest-cov", "sqlalchemy"] +tests = ["coverage", "pyfakefs", "pytest", "pytest-cov", "sqlalchemy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "27294d999c54b6f92dad17f40e730be3f2c7ce4b4de62ea73ad6cd22905ca49d" +content-hash = "be1caef77a4472b4ec8937c89a7be1ba8409743cc4b9e555bf68f03b2c58c6c8" diff --git a/pynetdicom/association.py b/pynetdicom/association.py index 11329e107..6aa0fc1ed 100644 --- a/pynetdicom/association.py +++ b/pynetdicom/association.py @@ -1112,10 +1112,10 @@ def send_c_find( Hanging Protocol Query/Retrieve Service, Defined Procedure Protocol Query/Retrieve Service, Substance Administration Query Service, Color Palette Query/Retrieve Service*, *Implant Template - Query/Retrieve Service*, *Protocol Approval Query/Retrieve - Service* and *Unified Protocol Step Service* specific - (DICOM Standard, Part 4, Annexes C.4.1, K.4.1.1.4, U.4.1, HH, - V.4.1.1.4, X, BB, II and CC): + Query/Retrieve Service*, *Inventory Query/Retrieve Service*, + *Protocol Approval Query/Retrieve Service* and *Unified Protocol + Step Service* specific (DICOM Standard, Part 4, Annexes C.4.1, + K.4.1.1.4, U.4.1, HH, V.4.1.1.4, X, BB, II, JJ and CC): Failure | ``0xA700`` - Out of resources @@ -1179,6 +1179,7 @@ def send_c_find( :class:`~pynetdicom.service_class.DefinedProcedureProtocolQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.HangingProtocolQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ImplantTemplateQueryRetrieveServiceClass` + :class:`~pynetdicom.service_class.InventoryQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ProtocolApprovalQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.QueryRetrieveServiceClass` :class:`~pynetdicom.service_class.RelevantPatientInformationQueryServiceClass` @@ -1198,6 +1199,7 @@ def send_c_find( * DICOM Standard, Part 4, :dcm:`Annex CC` * DICOM Standard, Part 4, :dcm:`Annex HH` * DICOM Standard, Part 4, :dcm:`Annex II` + * DICOM Standard, Part 4, :dcm:`Annex JJ` * DICOM Standard, Part 7, Sections :dcm:`9.1.2`, :dcm:`9.3.2` and @@ -1344,9 +1346,9 @@ def send_c_get( *Query/Retrieve Service, Hanging Protocol Query/Retrieve Service, Defined Procedure Protocol Query/Retrieve Service, Color Palette Query/Retrieve Service*, *Implant Template Query/Retrieve - Service* and *Protocol Approval Query/Retrieve Service* specific - (DICOM Standard, Part 4, Annexes C.4.3, - Y.C.4.2.1.4, Z.4.2.1.4, U.4.3, X, BB, HH and II): + Service*, *Inventory Query/Retrieve Service* and *Protocol Approval + Query/Retrieve Service* specific (DICOM Standard, Part 4, Annexes + C.4.3, Y.C.4.2.1.4, Z.4.2.1.4, U.4.3, X, BB, HH, II and JJ): Pending | ``0xFF00`` - Sub-operations are continuing @@ -1397,6 +1399,7 @@ def send_c_get( :class:`~pynetdicom.service_class.DefinedProcedureProtocolQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ColorPaletteQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ImplantTemplateQueryRetrieveServiceClass` + :class:`~pynetdicom.service_class.InventoryQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ProtocolApprovalQueryRetrieveServiceClass` References @@ -1410,6 +1413,7 @@ def send_c_get( * DICOM Standard, Part 4, :dcm:`Annex BB` * DICOM Standard, Part 4, :dcm:`Annex HH` * DICOM Standard, Part 4, :dcm:`Annex II` + * DICOM Standard, Part 4, :dcm:`Annex JJ` * DICOM Standard, Part 7, Sections :dcm:`9.1.3`, :dcm:`9.3.3` and @@ -1555,8 +1559,9 @@ def send_c_move( *Query/Retrieve Service, Hanging Protocol Query/Retrieve Service, Defined Procedure Protocol Query/Retrieve Service, Color Palette Query/Retrieve Service* , *Implant Template Query/Retrieve - Service* and *Protocol Approval Query/Retrieve Service* - specific (DICOM Standard, Part 4, Annexes C, U, Y, X, BB and HH): + Service*, *Inventory Query/Retrieve Service* and *Protocol Approval + Query/Retrieve Service* specific (DICOM Standard, Part 4, Annexes + C, U, Y, X, BB, HH and JJ): Failure | ``0xA701`` - Out of resources: unable to calculate number of @@ -1603,6 +1608,7 @@ def send_c_move( :class:`~pynetdicom.service_class.HangingProtocolQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ColorPaletteQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ImplantTemplateQueryRetrieveServiceClass` + :class:`~pynetdicom.service_class.InventoryQueryRetrieveServiceClass` :class:`~pynetdicom.service_class.ProtocolApprovalQueryRetrieveServiceClass` References @@ -1615,6 +1621,7 @@ def send_c_move( * DICOM Standard, Part 4, :dcm:`Annex BB` * DICOM Standard, Part 4, :dcm:`Annex HH` * DICOM Standard, Part 4, :dcm:`Annex II` + * DICOM Standard, Part 4, :dcm:`Annex JJ` * DICOM Standard, Part 7, Sections :dcm:`9.1.4`, :dcm:`9.3.4` and diff --git a/pynetdicom/service_class.py b/pynetdicom/service_class.py index 8b0a4a02e..bb0a032b4 100644 --- a/pynetdicom/service_class.py +++ b/pynetdicom/service_class.py @@ -1566,6 +1566,7 @@ class QueryRetrieveServiceClass(ServiceClass): "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", + "1.2.840.10008.5.1.4.1.1.201.2", "1.2.840.10008.5.1.4.1.1.201.6", ], "C-GET": [ @@ -1581,6 +1582,7 @@ class QueryRetrieveServiceClass(ServiceClass): "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", + "1.2.840.10008.5.1.4.1.1.201.4", ], "C-MOVE": [ "1.2.840.10008.5.1.4.1.2.1.2", @@ -1594,6 +1596,7 @@ class QueryRetrieveServiceClass(ServiceClass): "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", + "1.2.840.10008.5.1.4.1.1.201.3", ], } @@ -2456,6 +2459,12 @@ class ImplantTemplateQueryRetrieveServiceClass(QueryRetrieveServiceClass): pass +class InventoryQueryRetrieveServiceClass(QueryRetrieveServiceClass): + """Implementation of the Inventory QR Service.""" + + pass + + class NonPatientObjectStorageServiceClass(StorageServiceClass): """Implementation of the Non-Patient Object Storage Service""" diff --git a/pynetdicom/sop_class.py b/pynetdicom/sop_class.py index a787dac03..de1a2c736 100644 --- a/pynetdicom/sop_class.py +++ b/pynetdicom/sop_class.py @@ -14,6 +14,7 @@ DefinedProcedureProtocolQueryRetrieveServiceClass, HangingProtocolQueryRetrieveServiceClass, ImplantTemplateQueryRetrieveServiceClass, + InventoryQueryRetrieveServiceClass, NonPatientObjectStorageServiceClass, ProtocolApprovalQueryRetrieveServiceClass, QueryRetrieveServiceClass, @@ -91,6 +92,9 @@ def uid_to_service_class(uid: str) -> Type[ServiceClass]: if uid in _INSTANCE_AVAILABILITY_CLASSES.values(): return InstanceAvailabilityNotificationServiceClass + if uid in _INVENTORY_CLASSES.values(): + return InventoryQueryRetrieveServiceClass + if uid in _MEDIA_CREATION_CLASSES.values(): return MediaCreationManagementServiceClass diff --git a/pynetdicom/tests/test_sop.py b/pynetdicom/tests/test_sop.py index f9af2f201..632895de2 100644 --- a/pynetdicom/tests/test_sop.py +++ b/pynetdicom/tests/test_sop.py @@ -79,6 +79,7 @@ DefinedProcedureProtocolQueryRetrieveServiceClass, HangingProtocolQueryRetrieveServiceClass, ImplantTemplateQueryRetrieveServiceClass, + InventoryQueryRetrieveServiceClass, NonPatientObjectStorageServiceClass, ProtocolApprovalQueryRetrieveServiceClass, QueryRetrieveServiceClass, @@ -250,6 +251,11 @@ def test_instance_uids(self): == InstanceAvailabilityNotificationServiceClass ) + def test_inventory_uids(self): + """Test that the Inventory QR SOP Class UIDs work correctly.""" + for uid in _INVENTORY_CLASSES.values(): + assert uid_to_service_class(uid) == InventoryQueryRetrieveServiceClass + def test_media_creation_uids(self): """Test that the Media Creation SOP Class UIDs work correctly.""" for uid in _MEDIA_CREATION_CLASSES.values(): @@ -397,6 +403,10 @@ def test_instance_sop(self): == InstanceAvailabilityNotificationServiceClass ) + def test_instance_sop(self): + assert InventoryFind == "1.2.840.10008.5.1.4.1.1.201.2" + assert InventoryFind.service_class == InventoryQueryRetrieveServiceClass + def test_media_creation_sop(self): assert MediaCreationManagement == "1.2.840.10008.5.1.1.33" assert ( diff --git a/pyproject.toml b/pyproject.toml index 3706ed011..b81760db8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ sqlalchemy = { version = "^2.0", optional = true } apps = ["sqlalchemy"] dev = ["asv", "black", "codespell", "coverage", "mypy", "pyfakefs", "pytest", "pytest-cov", "ruff", "sqlalchemy"] docs = ["numpydoc", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -tests = ["pyfakefs", "pytest", "pytest-cov", "sqlalchemy"] +tests = ["coverage", "pyfakefs", "pytest", "pytest-cov", "sqlalchemy"] [tool.poetry.scripts] echoscp = "pynetdicom.apps.echoscp.echoscp:main"