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 Class | UID | Description |
|---|---|---|
| Basic Film Session | 1.2.840.10008.5.1.1.1 | Print job container |
| Basic Film Box | 1.2.840.10008.5.1.1.2 | Film sheet definition |
| Basic Grayscale Image Box | 1.2.840.10008.5.1.1.4 | Grayscale image position |
| Basic Color Image Box | 1.2.840.10008.5.1.1.4.2 | Color image position |
| Basic Grayscale Print Meta SOP | 1.2.840.10008.5.1.1.9 | Meta SOP for grayscale |
DICOM Print Workflow
The DICOM Print workflow consists of these steps:
- N-CREATE Film Session: Create a new print job
- N-CREATE Film Box: Define film sheets with layout
- N-SET Image Box: Set pixel data for each image position
- N-ACTION Print: Execute the print operation
- N-DELETE Film Session: Clean up after printing
Film Layout Formats
The Image Display Format attribute specifies how images are arranged on film:
| Format | Description |
|---|---|
| STANDARD\1,1 | 1 image per sheet |
| STANDARD\2,2 | 4 images (2x2 grid) |
| STANDARD\2,3 | 6 images (2x3 grid) |
| STANDARD\3,3 | 9 images (3x3 grid) |
| STANDARD\3,4 | 12 images (3x4 grid) |
| STANDARD\4,5 | 20 images (4x5 grid) |
Film Sizes
| Film Size ID | Description |
|---|---|
| 14INX17IN | Standard chest film (largest) |
| 11INX14IN | Medium format |
| 10INX12IN | Medium format |
| 8INX10IN | Small format |
| A3 | Paper size |
| A4 | Paper size |
Print Workflow Implementation
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.