DICOM Basics using .NET and C# - Understanding Print Operations

Introduction

This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM Print services, which provide standardized printing of medical images to film printers or digital print destinations.

While softcopy viewing on PACS workstations has largely replaced film-based workflows, DICOM Print remains relevant for certain use cases such as patient CDs with printed reports, operating room displays, and legal/archival purposes.

Prerequisites

Before you begin, ensure you have the following:

  • A .NET development environment (Visual Studio or Visual Studio Code)
  • The Fellow Oak DICOM library (fo-dicom) installed via NuGet
  • A DICOM Print SCP (film printer or print server) for testing
  • You can find all the code demonstrated in this tutorial on GitHub here

β€œThe printing press is either the greatest blessing or the greatest curse of modern times.” ~ James M. Barrie

The Theory Behind DICOM Print

DICOM Print emerged from a fundamental challenge in medical imaging: how to produce hardcopy output that faithfully represents diagnostic quality images. Film printers from different vendors had incompatible interfaces and varying capabilities. DICOM Print created a vendor-neutral abstraction layer that separates the concept of "what to print" from "how to print it" on specific hardware.

The hierarchical model of Film Session β†’ Film Box β†’ Image Box reflects physical reality mapped to software abstractions. A Film Session represents a print job (with properties like number of copies). A Film Box represents a physical sheet of film (with size, orientation, and layout). An Image Box represents one cell in the layout grid. This hierarchy lets you compose complex multi-image sheets while maintaining logical organization.

The Grayscale Standard Display Function (GSDF) defined in DICOM Part 14 is theoretically crucial for print. GSDF ensures that the human eye perceives approximately equal changes in lightness across the grayscale range. Without standardized calibration, dark regions might lose detail while bright regions become washed out. For diagnostic quality, printers must be calibrated to GSDF, and images must be prepared accordingly.

The print session model uses N-CREATE to instantiate objects (Film Session, Film Box), N-SET to modify them (setting pixel data in Image Box), N-ACTION to trigger operations (print), and N-DELETE for cleanup. This object-lifecycle approach mirrors object-oriented programming concepts, making the print queue a collection of managed objects with defined behaviors.

Despite softcopy dominance, film persists for specific reasons: portability (patients can carry films to other facilities), legal requirements (some jurisdictions require physical copies), operating room displays (where electronic displays may not be sterile or available), and backup (film requires no power to view). Understanding DICOM Print remains valuable for these edge cases.

DICOM Print Concepts

DICOM Print uses a hierarchical model to organize print jobs:

  • Film Session: Represents one print job (number of copies, priority, medium type)
  • Film Box: Represents one sheet of film (size, orientation, layout)
  • Image Box: Represents one image position on a film sheet

DICOM Print SOP Classes

SOP ClassUIDDescription
Basic Film Session1.2.840.10008.5.1.1.1Print job container
Basic Film Box1.2.840.10008.5.1.1.2Film sheet definition
Basic Grayscale Image Box1.2.840.10008.5.1.1.4Grayscale image position
Basic Color Image Box1.2.840.10008.5.1.1.4.2Color image position
Basic Grayscale Print Meta SOP1.2.840.10008.5.1.1.9Meta SOP for grayscale

DICOM Print Workflow

The DICOM Print workflow consists of these steps:

  1. N-CREATE Film Session: Create a new print job
  2. N-CREATE Film Box: Define film sheets with layout
  3. N-SET Image Box: Set pixel data for each image position
  4. N-ACTION Print: Execute the print operation
  5. N-DELETE Film Session: Clean up after printing

Film Layout Formats

The Image Display Format attribute specifies how images are arranged on film:

FormatDescription
STANDARD\1,11 image per sheet
STANDARD\2,24 images (2x2 grid)
STANDARD\2,36 images (2x3 grid)
STANDARD\3,39 images (3x3 grid)
STANDARD\3,412 images (3x4 grid)
STANDARD\4,520 images (4x5 grid)

Film Sizes

Film Size IDDescription
14INX17INStandard chest film (largest)
11INX14INMedium format
10INX12INMedium format
8INX10INSmall format
A3Paper size
A4Paper size

Here's a conceptual overview of implementing DICOM Print:

using System;
using FellowOakDicom;
using FellowOakDicom.Network;
using System.Threading;
using System.Threading.Tasks;
using FellowOakDicom.Network.Client;

namespace DicomPrintExample
{
    public class DicomPrintClient
    {
        private readonly string _printServerHost = "localhost";
        private readonly int _printServerPort = 11112;
        private readonly string _remoteAeTitle = "PRINT_SCP";
        private readonly string _localAeTitle = "FODICOM_PRINT";

        /// <summary>
        /// Demonstrates the DICOM Print workflow.
        /// </summary>
        public void PrintDicomImage(string dicomFilePath)
        {
            Console.WriteLine("=== DICOM Print Workflow ===");

            // Step 1: Create Film Session
            Console.WriteLine("Step 1: Creating Film Session...");
            CreateFilmSession();

            // Step 2: Create Film Box
            Console.WriteLine("Step 2: Creating Film Box...");
            CreateFilmBox();

            // Step 3: Set Image Box Content
            Console.WriteLine("Step 3: Setting Image Box content...");
            SetImageBoxContent(dicomFilePath);

            // Step 4: Print
            Console.WriteLine("Step 4: Printing Film Box...");
            PrintFilmBox();

            // Step 5: Delete Film Session
            Console.WriteLine("Step 5: Cleaning up Film Session...");
            DeleteFilmSession();

            Console.WriteLine("Print workflow completed.");
        }
    }
}

Step 1: Creating the Film Session

private DicomUID _filmSessionUid;

private void CreateFilmSession()
{
    // Create N-CREATE request for Film Session
    var filmSession = new DicomNCreateRequest(
        DicomUID.BasicFilmSessionSOPClass,
        DicomUID.Generate());

    filmSession.Dataset = new DicomDataset();

    // Number of copies to print
    filmSession.Dataset.Add(DicomTag.NumberOfCopies, "1");

    // Print priority: HIGH, MED, LOW
    filmSession.Dataset.Add(DicomTag.PrintPriority, "MED");

    // Medium type: PAPER, CLEAR FILM, BLUE FILM
    filmSession.Dataset.Add(DicomTag.MediumType, "PAPER");

    // Film destination: MAGAZINE, PROCESSOR
    filmSession.Dataset.Add(DicomTag.FilmDestination, "PROCESSOR");

    filmSession.OnResponseReceived += (request, response) =>
    {
        if (response.Status == DicomStatus.Success)
        {
            _filmSessionUid = response.AffectedSOPInstanceUID;
            Console.WriteLine($"  Film Session created: {_filmSessionUid}");
        }
        else
        {
            Console.WriteLine($"  Film Session creation failed: {response.Status}");
        }
    };

    // Send the request
    // await client.AddRequestAsync(filmSession);
}

Step 2: Creating the Film Box

private DicomUID _filmBoxUid;

private void CreateFilmBox()
{
    var filmBox = new DicomNCreateRequest(
        DicomUID.BasicFilmBoxSOPClass,
        DicomUID.Generate());

    filmBox.Dataset = new DicomDataset();

    // Image Display Format: layout of images on film
    filmBox.Dataset.Add(DicomTag.ImageDisplayFormat, "STANDARD\\1,1");

    // Film Size ID: 14INX17IN, 8INX10IN, A4, etc.
    filmBox.Dataset.Add(DicomTag.FilmSizeID, "14INX17IN");

    // Film Orientation: PORTRAIT or LANDSCAPE
    filmBox.Dataset.Add(DicomTag.FilmOrientation, "PORTRAIT");

    // Magnification Type: REPLICATE, BILINEAR, CUBIC
    filmBox.Dataset.Add(DicomTag.MagnificationType, "CUBIC");

    // Border Density: BLACK or WHITE
    filmBox.Dataset.Add(DicomTag.BorderDensity, "BLACK");

    // Empty Image Density: BLACK or WHITE
    filmBox.Dataset.Add(DicomTag.EmptyImageDensity, "BLACK");

    // Link to Film Session
    var refFilmSessionSeq = new DicomSequence(DicomTag.ReferencedFilmSessionSequence);
    var refFilmSessionItem = new DicomDataset();
    refFilmSessionItem.Add(DicomTag.ReferencedSOPClassUID, DicomUID.BasicFilmSessionSOPClass);
    refFilmSessionItem.Add(DicomTag.ReferencedSOPInstanceUID, _filmSessionUid);
    refFilmSessionSeq.Items.Add(refFilmSessionItem);
    filmBox.Dataset.Add(refFilmSessionSeq);

    filmBox.OnResponseReceived += (request, response) =>
    {
        if (response.Status == DicomStatus.Success)
        {
            _filmBoxUid = response.AffectedSOPInstanceUID;
            Console.WriteLine($"  Film Box created: {_filmBoxUid}");
        }
    };
}

Step 3: Setting Image Box Content

private void SetImageBoxContent(string dicomFilePath)
{
    // Load the DICOM image
    var dicomFile = DicomFile.Open(dicomFilePath);
    var pixelData = dicomFile.Dataset.GetDicomItem<DicomPixelData>(DicomTag.PixelData);

    // Create N-SET request for Image Box
    var imageBox = new DicomNSetRequest(
        DicomUID.BasicGrayscaleImageBoxSOPClass,
        _imageBoxUid);

    imageBox.Dataset = new DicomDataset();

    // Image Box Position
    imageBox.Dataset.Add(DicomTag.ImageBoxPosition, "1");

    // Polarity: NORMAL or REVERSE
    imageBox.Dataset.Add(DicomTag.Polarity, "NORMAL");

    // Preformatted Grayscale Image Sequence
    var preformattedSeq = new DicomSequence(DicomTag.BasicGrayscaleImageSequence);
    var imageDataset = new DicomDataset();
    imageDataset.Add(DicomTag.SamplesPerPixel, (ushort)1);
    imageDataset.Add(DicomTag.PhotometricInterpretation, "MONOCHROME2");
    // Add pixel data...
    preformattedSeq.Items.Add(imageDataset);
    imageBox.Dataset.Add(preformattedSeq);

    Console.WriteLine("  Image Box content set");
}

Step 4: Printing the Film Box

private void PrintFilmBox()
{
    // N-ACTION to print the Film Box
    var printAction = new DicomNActionRequest(
        DicomUID.BasicFilmBoxSOPClass,
        _filmBoxUid,
        1);  // Action Type ID = 1 (Print)

    printAction.OnResponseReceived += (request, response) =>
    {
        if (response.Status == DicomStatus.Success)
        {
            Console.WriteLine("  Print job submitted successfully");
        }
        else
        {
            Console.WriteLine($"  Print failed: {response.Status}");
        }
    };
}

Step 5: Cleanup

private void DeleteFilmSession()
{
    var deleteSession = new DicomNDeleteRequest(
        DicomUID.BasicFilmSessionSOPClass,
        _filmSessionUid);

    deleteSession.OnResponseReceived += (request, response) =>
    {
        if (response.Status == DicomStatus.Success)
        {
            Console.WriteLine("  Film Session deleted");
        }
    };
}

Important Considerations

  • Preformatted Images: Pixel data must be converted to print-ready format (8-bit, scaled)
  • Window/Level: Apply appropriate window/level before printing
  • Annotations: Burn in any required text overlays before printing
  • Printer Calibration: Ensure printer is calibrated for diagnostic quality

Modern Alternatives

DICOM Print is less commonly used today due to:

  • PACS workstations providing high-quality softcopy viewing
  • Cost of film and maintenance of film printers
  • Environmental considerations
  • PDF export for reports and sharing

Conclusion

DICOM Print provides a standardized way to print medical images to film or paper. While softcopy viewing has largely replaced film-based workflows, understanding DICOM Print remains valuable for specialized use cases and legacy system integration.

The hierarchical model of Film Session, Film Box, and Image Box provides flexibility in organizing print jobs, and the standard ensures interoperability between imaging systems and print devices from different vendors.

Please check out the next tutorial in this series where we cover DICOMweb - the RESTful interface for DICOM.