Skip to content

Commit

Permalink
Add convenience function Event.encoded_dataset() (#888)
Browse files Browse the repository at this point in the history
  • Loading branch information
scaramallion committed Nov 17, 2023
1 parent 37388d1 commit d38eb18
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 109 deletions.
11 changes: 7 additions & 4 deletions docs/changelog/v2.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Fixes
* Fixed reserved A-ASSOCIATE-AC parameters being tested (:issue:`746`)
* Fixed datasets not transferring correctly when using
:attr:`~pynetdicom._config.STORE_RECV_CHUNKED_DATASET` (:issue:`756`)
* Fixed maximum length of PatientID attribute in qrscp app (:issue:`785`)
* Sanitize filenames for received datasets for non-conformant SOP Instance UIDs
(:issue:`823`)
* Fixed maximum length of *Patient ID* attribute in ``qrscp`` app (:issue:`785`)
* Sanitise filenames for received datasets for non-conformant SOP Instance
UIDs (:issue:`823`)

Enhancements
............
Expand All @@ -23,8 +23,11 @@ Enhancements
:class:`~pynetdicom.service_class.QueryRetrieveServiceClass` (:issue:`878`)
* Added support for :class:`Inventory Query/Retrieve Service Class
<pynetdicom.service_class.InventoryQueryRetrieveServiceClass>` (:issue:`879`)
* Added :meth:`~pynetdicom.events.Event.encoded_dataset` to simplify accessing
the encoded dataset without first decoding it

Changes
.......

* Add preliminary support for Python 3.12
* Dropped support for Python 3.7, 3.8 and 3.9
* Added support for Python 3.12
15 changes: 5 additions & 10 deletions docs/examples/storage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,16 @@ multiple C-STORE requests, depending on the size of the datasets:

.. code-block:: python
from pydicom.filewriter import write_file_meta_info
import uuid
from pynetdicom import AE, evt, AllStoragePresentationContexts
# Implement a handler for evt.EVT_C_STORE
def handle_store(event):
"""Handle a C-STORE request event."""
with open(event.request.AffectedSOPInstanceUID, 'wb') as f:
# Write the preamble and prefix
f.write(b'\x00' * 128)
f.write(b'DICM')
# Encode and write the File Meta Information
write_file_meta_info(f, event.file_meta)
# Write the encoded dataset
f.write(event.request.DataSet.getvalue())
with open(f"{uuid.uuid4()}", 'wb') as f:
# Write the preamble, prefix, file meta information
# and encoded dataset to `f`
f.write(event.encoded_dataset())
# Return a 'Success' status
return 0x0000
Expand Down
33 changes: 14 additions & 19 deletions docs/tutorials/create_scp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,10 @@ complex code:

.. code-block:: python
:linenos:
:emphasize-lines: 1,3,12,14-18,21-28,32
:emphasize-lines: 1-2,11,13-17,19-23,27
import os
from pydicom.filewriter import write_file_meta_info
import uuid
from pathlib import Path
from pynetdicom import (
AE, debug_logger, evt, AllStoragePresentationContexts,
Expand All @@ -418,15 +417,11 @@ complex code:
# Unable to create output dir, return failure status
return 0xC001
# We rely on the UID from the C-STORE request instead of decoding
fname = os.path.join(storage_dir, event.request.AffectedSOPInstanceUID)
with open(fname, 'wb') as f:
# Write the preamble, prefix and file meta information elements
f.write(b'\x00' * 128)
f.write(b'DICM')
write_file_meta_info(f, event.file_meta)
# Write the raw encoded dataset
f.write(event.request.DataSet.getvalue())
path = Path(storage_dir) / f"{uuid.uuid4()}"
with path.open('wb') as f:
# Write the preamble, prefix, file meta information elements
# and the raw encoded dataset to `f`
f.write(event.encoded_dataset())
return 0x0000
Expand All @@ -441,12 +436,12 @@ complex code:
ae.start_server(("127.0.0.1", 11112), block=True, evt_handlers=handlers)
We've modified the handler to write the preamble and prefix to file,
encode and write the file meta information elements using *pydicom's*
:func:`~pydicom.filewriter.write_file_meta_info` function, then finally write
the encoded dataset using the :attr:`raw dataset
<dimse_primitives.C_STORE.DataSet>` received directly from the C-STORE
request via the ``event.request`` attribute.
We've modified the handler to use :meth:`~pynetdicom.events.Event.encoded_dataset`,
which writes the preamble, prefix, file meta information elements and the
:attr:`raw dataset<dimse_primitives.C_STORE.DataSet>` received in the C-STORE
request directly to file. If you need separate access to just the encoded dataset
then you can call :meth:`~pynetdicom.events.Event.encoded_dataset` with
`include_meta=False` instead.

The second change we've made is to demonstrate how extra parameters can be
passed to the handler by binding using a 3-tuple rather than a 2-tuple. The
Expand Down
8 changes: 1 addition & 7 deletions pynetdicom/apps/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@
from pydicom import dcmread
from pydicom.datadict import tag_for_keyword, repeater_has_keyword, get_entry
from pydicom.dataset import Dataset
from pydicom.filewriter import write_file_meta_info
from pydicom.tag import Tag
from pydicom.uid import DeflatedExplicitVRLittleEndian

from pynetdicom.dsutils import encode


def create_dataset(args, logger=None):
"""Return a new or updated dataset.
Expand Down Expand Up @@ -629,10 +626,7 @@ def handle_store(event, args, app_logger):
if event.context.transfer_syntax == DeflatedExplicitVRLittleEndian:
# Workaround for pydicom issue #1086
with open(filename, "wb") as f:
f.write(b"\x00" * 128)
f.write(b"DICM")
write_file_meta_info(f, event.file_meta)
f.write(encode(ds, False, True, True))
f.write(event.encoded_dataset())
else:
# We use `write_like_original=False` to ensure that a compliant
# File Meta Information Header is written
Expand Down
25 changes: 19 additions & 6 deletions pynetdicom/dsutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
from io import BytesIO
import logging
from pathlib import Path
from typing import Optional, List, Tuple
import zlib

from pydicom import Dataset
from pydicom.dataset import FileMetaDataset
from pydicom.dataelem import DataElement
from pydicom.filebase import DicomBytesIO
from pydicom.filereader import read_dataset, read_preamble
from pydicom.filewriter import write_dataset
from pydicom.filewriter import write_dataset, write_file_meta_info
from pydicom.tag import BaseTag
from pydicom.uid import UID

Expand Down Expand Up @@ -128,7 +127,7 @@ def decode(

def encode(
ds: Dataset, is_implicit_vr: bool, is_little_endian: bool, deflated: bool = False
) -> Optional[bytes]:
) -> bytes | None:
"""Encode a *pydicom* :class:`~pydicom.dataset.Dataset` `ds`.
.. versionchanged:: 1.5
Expand Down Expand Up @@ -182,7 +181,21 @@ def encode(
return bytestring


def pretty_dataset(ds: Dataset, indent: int = 0, indent_char: str = " ") -> List[str]:
def encode_file_meta(file_meta: FileMetaDataset) -> bytes:
"""Return the encoded File Meta Information elements in `file_meta`.
.. versionadded:: 2.1
"""

buffer = DicomBytesIO()
buffer.is_little_endian = True
buffer.is_implicit_VR = False
write_file_meta_info(buffer, file_meta)
return buffer.getvalue()


def pretty_dataset(ds: Dataset, indent: int = 0, indent_char: str = " ") -> list[str]:
"""Return a list of pretty dataset strings.
.. versionadded:: 1.5
Expand Down Expand Up @@ -270,7 +283,7 @@ def pretty_element(elem: DataElement) -> str:
)


def split_dataset(path: Path) -> Tuple[Dataset, int]:
def split_dataset(path: Path) -> tuple[Dataset, int]:
"""Return the file meta elements and the offset to the start of the dataset
.. versionadded:: 2.0
Expand All @@ -288,7 +301,7 @@ def split_dataset(path: Path) -> Tuple[Dataset, int]:
no File Meta is present.
"""

def _not_group_0002(tag: BaseTag, VR: Optional[str], length: int) -> bool:
def _not_group_0002(tag: BaseTag, VR: str | None, length: int) -> bool:
"""Return True if the tag is not in group 0x0002, False otherwise."""
return tag.group != 2

Expand Down
Loading

0 comments on commit d38eb18

Please sign in to comment.