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

Is Print Management Service Examples working? #777

Open
BJladika opened this issue Jun 9, 2022 · 3 comments
Open

Is Print Management Service Examples working? #777

BJladika opened this issue Jun 9, 2022 · 3 comments

Comments

@BJladika
Copy link

BJladika commented Jun 9, 2022

https://pydicom.github.io/pynetdicom/stable/examples/print.html
I tried to follow this example, but encountered different obstacles.

First 1st step (checking status) originally had no successfull outcome. It would always get fail. But its easily fixable. Okay...

Secondly, 2nd step (getting film session) somehow gets empty dataset (film session) so when I try generate film box it crashes because it can't get ReferencedSOPClassUID and ReferencedSOPInstanceUID (and fails to get film box).

Am I doing something wrong?

Tried on real printer DryStar 5302 and on virtual printer (Print SCP) from CharruaSoft (their Print Server). Pynetdicom version 2.0.2 and pydicom is 2.3.0 with Windows 10 (Superior OS)

@BJladika
Copy link
Author

After minor adjusment of example (because of not getting affected sop instance, and also somehow generated uid could be rejected) it started to work, kinda, although I have encountered with another issue

import sys

from pydicom import dcmread
from pydicom.dataset import Dataset
from pydicom.uid import generate_uid

from pynetdicom import AE, evt, debug_logger
from pynetdicom.sop_class import (
    BasicGrayscalePrintManagementMeta,
    BasicFilmSession,
    BasicFilmBox,
    BasicGrayscaleImageBox,
    Printer,
    PrinterInstance
)

debug_logger()

# The SOP Instance containing the grayscale image data to be printed
DATASET = dcmread('path/to/file.dcm')


def build_session():
    """Return an N-CREATE *Attribute List* for creating a Basic Film Session

    Returns
    -------
    pydicom.dataset.Dataset
        An N-CREATE *Attribute List* dataset that can be used to create a
        *Basic Film Session SOP Class* instance.
    """
    attr_list = Dataset()
    attr_list.NumberOfCopies = '1'  # IS
    attr_list.PrintPriority = 'LOW'  # CS
    attr_list.MediumType = 'PAPER'  # CS
    attr_list.FilmDestination = 'SOMEWHERE'  # CS
    attr_list.FilmSessionLabel = 'TEST JOB'  # LO
    attr_list.MemoryAllocation = ''  # IS
    attr_list.OwnerID = 'PYNETDICOM'  # SH

    return attr_list


def build_film_box(session):
    """Return an N-CREATE *Attribute List* for creating a Basic Film Box.

    In this example we just have a single Image Box.

    Parameters
    ----------
    session : pydicom.dataset.Dataset
        The *Basic Film Session SOP Class* instance returned by SCP in
        response to the N-CREATE request that created it.

    Returns
    -------
    pydicom.dataset.Dataset
        An N-CREATE *Attribute List* dataset that can be used to create a
        *Basic Film Box SOP Class* instance.
    """
    # The "film" consists of a single Image Box
    attr_list = Dataset()
    attr_list.ImageDisplayFormat = 'STANDARD\1,1'
    attr_list.FilmOrientation = 'PORTRAIT'
    attr_list.FilmSizeID = 'A4'

    # Can only contain a single item, is a reference to the *Film Session*
    attr_list.ReferencedFilmSessionSequence = [Dataset()]
    item = attr_list.ReferencedFilmSessionSequence[0]
    item.ReferencedSOPClassUID = session.SOPClassUID
    item.ReferencedSOPInstanceUID = session.SOPInstanceUID

    return attr_list


def build_image_box(im):
    """Return an N-SET *Attribute List* for updating a Basic Grayscale Image Box

    Parameters
    ----------
    im : pydicom.dataset.Dataset
        The SOP Instance containing the pixel data that is to be printed.

    Returns
    -------
    pydicom.dataset.Dataset
        An N-SET *Attribute List* dataset that can be used to update the
        *Basic Grayscale Image Box SOP Class* instance.
    """
    attr_list = Dataset()
    attr_list.ImageBoxPosition = 1  # US

    # Zero or one item only
    attr_list.ReferencedImageBoxSequence = [Dataset()]
    item = attr_list.ReferencedImageBoxSequence[0]
    item.SamplesPerPixel = im.SamplesPerPixel
    item.PhotometricInterpretation = im.PhotometricInterpretation
    item.Rows = im.Rows
    item.Columns = im.Columns
    item.BitsAllocated = im.BitsAllocated
    item.BitsStored = im.BitsStored
    item.HighBit = im.HighBit
    item.PixelRepresentation = im.PixelRepresentation
    item.PixelData = im.PixelData

    return attr_list

def handle_n_er(event):
    """Ignore the N-EVENT-REPORT notification"""
    return 0x0000

handlers = [(evt.EVT_N_EVENT_REPORT, handle_n_er)]

ae = AE()
ae.add_requested_context(BasicGrayscalePrintManagementMeta)
assoc = ae.associate("127.0.0.1", 11112, evt_handlers=handlers)

if assoc.is_established:
    # Step 1: Check the status of the printer
    # (2110,0010) Printer Status
    # (2110,0020) Printer Status Info
    # Because the association was negotiated using a presentation context
    #   with a Meta SOP Class we need to use the `meta_uid` keyword
    #   parameter to ensure we use the correct context
    status, attr_list = assoc.send_n_get(
        [0x21100010, 0x21100020],  # Attribute Identifier List
        Printer,  # Affected SOP Class UID
        PrinterInstance,  # Well-known Printer SOP Instance
        meta_uid=BasicGrayscalePrintManagementMeta
    )
    if status and status.Status == 0x0000:
        if getattr(attr_list, 'PrinterStatus', None) != "NORMAL":
            print("Printer status is not 'NORMAL'")
            assoc.release()
            sys.exit()
        else:
            print("Failed to get the printer status")
            assoc.release()
            sys.exit()
    else:
        print("Failed to get the printer status")
        assoc.release()
        sys.exit()

    print('Printer ready')
    affected_sop_instance_uid = generate_uid(prefix=None) # Affected SOP Instance UID

    # Step 2: Create *Film Session* instance
    status, film_session = assoc.send_n_create(
        build_session(),  # Attribute List
        BasicFilmSession,  # Affected SOP Class UID
        affected_sop_instance_uid,  # Affected SOP Instance UID
        meta_uid=BasicGrayscalePrintManagementMeta
    )
    film_session.SOPInstanceUID = affected_sop_instance_uid
    film_session.SOPClassUID = BasicFilmSession

    if not status or status.Status != 0x0000:
        print('Creation of Film Session failed, releasing association')
        assoc.release()
        sys.exit()

    print('Film Session created')
    
    affected_sop_instance_uid_2 = generate_uid(prefix=None)
    # Step 3: Create *Film Box* and *Image Box(es)*
    status, film_box = assoc.send_n_create(
        build_film_box(film_session),
        BasicFilmBox,
        affected_sop_instance_uid_2,
        meta_uid=BasicGrayscalePrintManagementMeta
    )
    if not status or status.Status != 0x0000:
        print('Creation of the Film Box failed, releasing association')
        assoc.release()
        sys.exit()

    print('Film Box created')

    film_box.SOPInstanceUID = affected_sop_instance_uid_2
    film_box.SOPClassUID = BasicFilmBox
    # Step 4: Update the *Image Box* with the image data
    # In this example we only have one *Image Box* per *Film Box*
    # Get the Image Box's SOP Class and SOP Instance UIDs
    item = film_box.ReferencedImageBoxSequence[0]
    status, image_box = assoc.send_n_set(
        build_image_box(DATASET),
        item.ReferencedSOPClassUID,
        item.ReferencedSOPInstanceUID,
        meta_uid=BasicGrayscalePrintManagementMeta
    )
    if not status or status.Status != 0x0000:
        print('Updating the Image Box failed, releasing association')
        assoc.release()
        sys.exit()

    print('Updated the Image Box with the image data')

    # Step 5: Print the *Film Box*
    status, action_reply = assoc.send_n_action(
        None,  # No *Action Information* needed
        1,  # Print the Film Box
        film_box.SOPClassUID,
        film_box.SOPInstanceUID,
        meta_uid=BasicGrayscalePrintManagementMeta
    )
    if not status or status.Status != 0x0000:
        print('Printing the Film Box failed, releasing association')
        assoc.release()
        sys.exit()

    # The actual printing may occur after association release/abort
    print('Print command sent successfully')

    # Optional - Delete the Film Box
    status = assoc.send_n_delete(
        film_box.SOPClassUID,
        film_box.SOPInstanceUID
    )

    # Release the association
    assoc.release()

Maybe update example?

@scaramallion
Copy link
Member

Thanks, I don't actually have anything to test the DIMSE-N services against, so I'll update the example using your code

@proj-eng
Copy link

proj-eng commented Nov 15, 2024

Even I tried with DryStar 5302 with the same script, the Image box creation failed. I added the attributes according to the Drystar conformance statement. But still my image box is not creating. It returns None.

Updating the Image Box failed. Status: 290. How to fix it. I am using pynetdicom=2.0.2, pydicom= 2.4.3 and python = ">=3.9,<3.13"

def build_session(im):
    attr_list = Dataset()
    attr_list.NumberOfCopies = '1'
    attr_list.PrintPriority = 'HIGH'
    attr_list.MediumType = 'BLUE FILM'
    attr_list.FilmDestination = 'PROCESSOR'
    attr_list.FilmSessionLabel = 'TEST JOB'
    attr_list.OwnerID = 'SUNVIEW'
    attr_list.ProposedStudySequence = [Dataset()]
    attr_list.ProposedStudySequence[0].PatientID = im.PatientID
    return attr_list

def build_film_box(session):
    attr_list = Dataset()
    attr_list.ImageDisplayFormat = "STANDARD\\1,1"
    attr_list.FilmOrientation = "PORTRAIT"
    attr_list.FilmSizeID = "8INX10IN"
    attr_list.BorderDensity = "BLACK"
    attr_list.EmptyImageDensity = "BLACK"
    attr_list.MagnificationType = "NONE"
    attr_list.SmoothingType = "CUBIC"
    attr_list.MinDensity = 0
    attr_list.MaxDensity = 255
    attr_list.Trim = "NO"
    attr_list.Illumination = 2000
    attr_list.ReflectedAmbientLight = 10
    attr_list.ReferencedFilmSessionSequence = [Dataset()]
    attr_list.ReferencedFilmSessionSequence[0].ReferencedSOPClassUID = session.SOPClassUID
    attr_list.ReferencedFilmSessionSequence[0].ReferencedSOPInstanceUID = session.SOPInstanceUID
    return attr_list

def build_image_box(im):
    attr_list = Dataset()
    attr_list.MagnificationType = "NONE"
    attr_list.Polarity = "NORMAL"
    attr_list.ImageBoxPosition = 1
    attr_list.RequestedImageSize = 250
    attr_list.BasicGrayscaleImageSequence = [Dataset()]
    item = attr_list.BasicGrayscaleImageSequence[0]
    item.SamplesPerPixel = im.SamplesPerPixel
    item.PhotometricInterpretation = im.PhotometricInterpretation
    item.Rows = im.Rows
    item.Columns = im.Columns
    item.BitsAllocated = im.BitsAllocated
    item.BitsStored = im.BitsStored
    item.HighBit = im.HighBit
    item.PixelRepresentation = im.PixelRepresentation
    item.PixelData = im.PixelData
    return attr_list

def handle_n_er(event):
    return 0x0000

ae = AE()
ae.add_requested_context(BasicGrayscalePrintManagementMeta)
handlers = [(evt.EVT_N_EVENT_REPORT, handle_n_er)]
assoc = ae.associate("120.0.0.1", 104, ae_title="PRINTER", evt_handlers=handlers)

if assoc.is_established:
    print("Association established.")

    status, attr_list = assoc.send_n_get(
        [0x21100010, 0x21100020],
        Printer,
        "1.2.840.10008.5.1.1.16",
        meta_uid=BasicGrayscalePrintManagementMeta,
    )

    if not status or status.Status != 0x0000:
        print("Failed to get printer status.")
        assoc.release()
        sys.exit()

    film_session_uid = generate_uid()
    status, film_session = assoc.send_n_create(
        build_session(DATASET),
        BasicFilmSession,
        film_session_uid,
        meta_uid=BasicGrayscalePrintManagementMeta,
    )

    if not status or status.Status != 0x0000:
        print(f"Film Session creation failed: {status}")
        assoc.release()
        sys.exit()

    print("Film Session created.")

    film_box_uid = generate_uid()
    status, film_box = assoc.send_n_create(
        build_film_box(film_session),
        BasicFilmBox,
        film_box_uid,
        meta_uid=BasicGrayscalePrintManagementMeta,
    )

    if not status or status.Status != 0x0000:
        print(f"Film Box creation failed: {status}")
        assoc.release()
        sys.exit()

    print("Film Box created.")

    status, image_box = assoc.send_n_set(
        build_image_box(DATASET),
        film_box.SOPClassUID,
        film_box.SOPInstanceUID,
        meta_uid=BasicGrayscalePrintManagementMeta,
    )

    if not status or status.Status != 0x0000:
        print(f"Updating Image Box failed. Status: {status.Status}")
        assoc.release()
        sys.exit()

    print("Image Box updated.")

    status = assoc.send_n_action(
        None,
        1,
        film_box.SOPClassUID,
        film_box.SOPInstanceUID,
        meta_uid=BasicGrayscalePrintManagementMeta,
    )

    if not status or status.Status != 0x0000:
        print("Print command failed.")
        assoc.release()
        sys.exit()

    print("Print command sent.")

    status = assoc.send_n_delete(film_box.SOPClassUID, film_box.SOPInstanceUID)
    assoc.release()
    print("Film Box deleted and association released.")
else:
    print("Failed to establish association.")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants