Best Practices and Performance

Section 21 of 27
78% complete

Implementing best practices ensures reliable, efficient DICOM applications.

Connection Pooling

Reuse DICOM associations for improved performance:

public class DicomConnectionPool
{
    private readonly ConcurrentQueue<DicomAssociation> availableConnections;
    private readonly SemaphoreSlim connectionSemaphore;
    private readonly string callingAE;
    private readonly string calledAE;
    private readonly string host;
    private readonly int port;
    private readonly int maxConnections;

    public DicomConnectionPool(
        string callingAE, string calledAE, string host, int port,
        int maxConnections = 5)
    {
        this.callingAE = callingAE;
        this.calledAE = calledAE;
        this.host = host;
        this.port = port;
        this.maxConnections = maxConnections;

        availableConnections = new ConcurrentQueue<DicomAssociation>();
        connectionSemaphore = new SemaphoreSlim(maxConnections, maxConnections);
    }

    public async Task<DicomAssociation> AcquireConnection()
    {
        await connectionSemaphore.WaitAsync();

        if (availableConnections.TryDequeue(out DicomAssociation association))
        {
            // Verify connection is still alive
            if (association.IsOpen)
            {
                return association;
            }
        }

        // Create new connection
        association = new DicomAssociation
        {
            CallingAETitle = callingAE,
            CalledAETitle = calledAE,
            Host = host,
            Port = port
        };

        association.AddPresentationContext(
            SOPClasses.CTImageStorage,
            DicomTransferSyntax.ExplicitVRLittleEndian
        );

        association.Open();
        return association;
    }

    public void ReleaseConnection(DicomAssociation association)
    {
        if (association != null && association.IsOpen)
        {
            availableConnections.Enqueue(association);
        }

        connectionSemaphore.Release();
    }

    public void CloseAll()
    {
        while (availableConnections.TryDequeue(out DicomAssociation association))
        {
            if (association.IsOpen)
            {
                association.Close();
            }
        }
    }
}

// Usage
var pool = new DicomConnectionPool("MY_SCU", "PACS_SCP", "192.168.1.100", 104);

foreach (string file in imageFiles)
{
    var association = await pool.AcquireConnection();
    try
    {
        SendImage(association, file);
    }
    finally
    {
        pool.ReleaseConnection(association);
    }
}

pool.CloseAll();

Memory Management for Large Files

Handle large DICOM files efficiently:

public class EfficientDicomHandler
{
    public void ProcessLargeFile(string filePath)
    {
        // Option 1: Stream pixel data instead of loading entire file
        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            DicomDataSet ds = new DicomDataSet();

            // Read only metadata, skip pixel data initially
            ds.Read(fs, ReadOptions.SkipPixelData);

            // Process metadata
            ProcessMetadata(ds);

            // Load pixel data only if needed
            if (NeedsPixelData())
            {
                fs.Seek(0, SeekOrigin.Begin);
                ds.Read(fs);
                ProcessPixelData(ds);
            }
        }
    }

    public void CompressPixelData(string inputFile, string outputFile)
    {
        DicomDataSet ds = new DicomDataSet();
        ds.Read(inputFile);

        // Check if already compressed
        if (ds.TransferSyntaxUID != DicomTransferSyntax.ExplicitVRLittleEndian &&
            ds.TransferSyntaxUID != DicomTransferSyntax.ImplicitVRLittleEndian)
        {
            Console.WriteLine("Already compressed");
            return;
        }

        // Re-encode with JPEG 2000 lossless
        ds.TransferSyntaxUID = DicomTransferSyntax.JPEG2000Lossless;
        ds.Write(outputFile);

        // Compare file sizes
        long originalSize = new FileInfo(inputFile).Length;
        long compressedSize = new FileInfo(outputFile).Length;
        double ratio = (double)originalSize / compressedSize;

        Console.WriteLine($"Original: {originalSize / (1024 * 1024)} MB");
        Console.WriteLine($"Compressed: {compressedSize / (1024 * 1024)} MB");
        Console.WriteLine($"Ratio: {ratio:F2}:1");
    }

    public byte[] ExtractThumbnail(DicomDataSet ds, int maxSize = 256)
    {
        int rows = int.Parse(ds[DicomTag.Rows]?.Value as string);
        int cols = int.Parse(ds[DicomTag.Columns]?.Value as string);

        double scale = Math.Min((double)maxSize / rows, (double)maxSize / cols);
        int newRows = (int)(rows * scale);
        int newCols = (int)(cols * scale);

        byte[] pixelData = ds[DicomTag.PixelData]?.Value as byte[];

        return ResizeImage(pixelData, rows, cols, newRows, newCols);
    }

    private void ProcessMetadata(DicomDataSet ds) { /* Implementation */ }
    private bool NeedsPixelData() { return true; }
    private void ProcessPixelData(DicomDataSet ds) { /* Implementation */ }
    private byte[] ResizeImage(byte[] pixels, int origRows, int origCols,
        int newRows, int newCols) { /* Implementation */ return null; }
}

Batch Processing

Efficiently process large numbers of files:

public class DicomBatchProcessor
{
    private readonly int batchSize = 100;
    private readonly int maxParallelism = 4;

    public async Task ProcessDirectory(
        string directoryPath,
        Func<DicomDataSet, Task> processor)
    {
        var files = Directory.GetFiles(directoryPath, "*.dcm",
            SearchOption.AllDirectories);

        Console.WriteLine($"Found {files.Length} DICOM files");

        var batches = files
            .Select((file, index) => new { file, index })
            .GroupBy(x => x.index / batchSize)
            .Select(g => g.Select(x => x.file).ToList());

        int batchNumber = 1;
        foreach (var batch in batches)
        {
            Console.WriteLine($"Processing batch {batchNumber}...");
            await ProcessBatch(batch, processor);
            batchNumber++;
        }

        Console.WriteLine("Batch processing complete");
    }

    private async Task ProcessBatch(
        List<string> files,
        Func<DicomDataSet, Task> processor)
    {
        var options = new ParallelOptions
        {
            MaxDegreeOfParallelism = maxParallelism
        };

        int successCount = 0;
        int errorCount = 0;

        await Parallel.ForEachAsync(files, options, async (file, ct) =>
        {
            try
            {
                DicomDataSet ds = new DicomDataSet();
                ds.Read(file);
                await processor(ds);
                Interlocked.Increment(ref successCount);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {Path.GetFileName(file)}: {ex.Message}");
                Interlocked.Increment(ref errorCount);
            }
        });

        Console.WriteLine($"  Success: {successCount}, Errors: {errorCount}");
    }
}

// Usage - Anonymization example
var processor = new DicomBatchProcessor();

await processor.ProcessDirectory(@"C:\DicomData", async (ds) =>
{
    ds[DicomTag.PatientName].Value = "ANONYMOUS";
    ds[DicomTag.PatientID].Value = "ANON" + Guid.NewGuid().ToString("N").Substring(0, 8);

    if (ds.Contains(DicomTag.PatientBirthDate))
        ds.Remove(DicomTag.PatientBirthDate);

    ds[DicomTag.StudyInstanceUID].Value = DicomUID.Generate();
    ds[DicomTag.SeriesInstanceUID].Value = DicomUID.Generate();
    ds[DicomTag.SOPInstanceUID].Value = DicomUID.Generate();

    string outputPath = Path.Combine(@"C:\AnonymizedDicom",
        Path.GetFileName(ds.SourceFileName));
    ds.Write(outputPath);
});

Error Handling and Retry Logic

Implement robust error handling:

public class RobustDicomClient
{
    private readonly int maxRetries = 3;
    private readonly int retryDelayMs = 1000;

    public async Task<bool> SendWithRetry(
        string callingAE, string calledAE, string host, int port,
        string filePath)
    {
        int attempt = 0;
        Exception lastException = null;

        while (attempt < maxRetries)
        {
            try
            {
                attempt++;
                Console.WriteLine($"Attempt {attempt} of {maxRetries}");

                DicomDataSet dataset = new DicomDataSet();
                dataset.Read(filePath);

                string sopClassUID = dataset[DicomTag.SOPClassUID]?.Value as string;

                DicomAssociation association = new DicomAssociation
                {
                    CallingAETitle = callingAE,
                    CalledAETitle = calledAE,
                    Host = host,
                    Port = port,
                    ConnectTimeout = 10000,
                    AssociationTimeout = 30000
                };

                association.AddPresentationContext(
                    sopClassUID,
                    DicomTransferSyntax.ExplicitVRLittleEndian
                );

                association.Open();

                DicomCommandSet request = new DicomCommandSet(DicomCommandType.C_STORE_RQ);
                request.AffectedSOPClassUID = sopClassUID;
                request.AffectedSOPInstanceUID =
                    dataset[DicomTag.SOPInstanceUID]?.Value as string;
                request.MessageID = 1;

                DicomCommandSet response = association.SendRequest(request, dataset);
                association.Close();

                if (response.Status == 0)
                {
                    Console.WriteLine("Send successful");
                    return true;
                }
                else
                {
                    throw new DicomException($"Status: 0x{response.Status:X4}");
                }
            }
            catch (Exception ex)
            {
                lastException = ex;
                Console.WriteLine($"Attempt {attempt} failed: {ex.Message}");

                if (attempt < maxRetries)
                {
                    // Exponential backoff
                    int delay = retryDelayMs * attempt;
                    Console.WriteLine($"Waiting {delay}ms before retry...");
                    await Task.Delay(delay);
                }
            }
        }

        Console.WriteLine($"Max retries reached. Last error: {lastException?.Message}");
        return false;
    }

    public async Task<List<string>> SendBatchWithRetry(
        string callingAE, string calledAE, string host, int port,
        List<string> filePaths)
    {
        var failedFiles = new List<string>();
        int successCount = 0;

        foreach (string filePath in filePaths)
        {
            Console.WriteLine($"Processing: {Path.GetFileName(filePath)}");

            bool success = await SendWithRetry(callingAE, calledAE, host, port, filePath);

            if (success)
                successCount++;
            else
                failedFiles.Add(filePath);
        }

        Console.WriteLine($"Total: {filePaths.Count}, Success: {successCount}, " +
            $"Failed: {failedFiles.Count}");

        return failedFiles;
    }
}

Best Practices Summary

DICOM Development Best Practices
================================

Connection Management:
  ✓ Use connection pooling for high-volume operations
  ✓ Implement proper timeout handling
  ✓ Close associations when done
  ✓ Handle network failures gracefully

Memory Management:
  ✓ Skip pixel data when only metadata is needed
  ✓ Use streaming for large files
  ✓ Implement proper disposal (using statements)
  ✓ Consider compression for storage

Error Handling:
  ✓ Implement retry logic with exponential backoff
  ✓ Log errors with sufficient detail
  ✓ Handle specific DICOM status codes
  ✓ Provide meaningful error messages

Performance:
  ✓ Use batch processing for multiple files
  ✓ Control parallelism to avoid resource exhaustion
  ✓ Cache frequently accessed data
  ✓ Monitor and optimize query performance

Security:
  ✓ Use TLS for network communications
  ✓ Validate AE Titles and IP addresses
  ✓ Implement proper authentication
  ✓ Log access and operations

Following these best practices ensures reliable, performant DICOM applications.

Quiz: Best Practices and Performance

Question 1 of 4

Why is connection pooling important for DICOM applications?