DICOM Router - Examples and tutorials

Examples & Tutorials

Table of Contents

  1. Getting Started
  2. Basic DICOM Operations
  3. REST API Examples
  4. Advanced Integration Scenarios
  5. Custom Implementation Examples
  6. Performance Optimization
  7. Troubleshooting Common Issues

Getting Started

Tutorial 1: Basic Setup and First DICOM Echo

This tutorial walks you through setting up the DICOM Proxy and performing your first DICOM echo test.

Step 1: Installation

# Extract the application
unzip DicomProxyRTWindows.zip -d C:\DicomProxy

# Navigate to directory
cd C:\DicomProxy

Step 2: Basic Configuration Create appsettings.json:

{
  "DicomPort": 11112,
  "HttpPort": 8080,
  "StoragePath": "C:\\DicomStorage",
  "ThisModality": {
    "AE_Title": "DICOMPROXY",
    "Port": 11112,
    "IPAddress": "0.0.0.0"
  }
}

Step 3: Start the Service

DicomProxyRTWindows.exe

Step 4: Test DICOM Connectivity

# Using DCMTK echoscu tool
echoscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY

# Expected output:
# I: Requesting Association
# I: Association Accepted (Max Send PDV: 16372)
# I: Sending Echo Request (MsgID 1)
# I: Received Echo Response (Success)

Tutorial 2: Storing Your First DICOM Image

Step 1: Prepare a DICOM File Download a sample DICOM file or use your own medical image.

Step 2: Store via DICOM Protocol

# Using DCMTK storescu tool
storescu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY sample.dcm

# Expected output:
# I: Requesting Association
# I: Association Accepted (Max Send PDV: 16372)
# I: Sending file: sample.dcm
# I: Transfer Syntax: Little Endian Implicit
# I: Sending C-STORE Request (MsgID 1)
# I: Received C-STORE Response (Success)

Step 3: Verify Storage via HTTP API

curl http://localhost:8080/wado/studies

Basic DICOM Operations

Example 1: C-FIND Query for Studies

Using DCMTK findscu:

# Query all studies
findscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY -k QueryRetrieveLevel=STUDY -k StudyInstanceUID

# Query studies for specific patient
findscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY \
  -k QueryRetrieveLevel=STUDY \
  -k PatientName="DOE^JOHN" \
  -k StudyDate="" \
  -k StudyInstanceUID=""

# Query studies by date range
findscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY \
  -k QueryRetrieveLevel=STUDY \
  -k StudyDate="20240101-20241231" \
  -k StudyInstanceUID=""

Example 2: C-GET Retrieve Operation

# Retrieve entire study
getscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY \
  -k QueryRetrieveLevel=STUDY \
  -k StudyInstanceUID="1.2.3.4.5.6.7.8.9" \
  --output-directory ./retrieved

# Retrieve specific series
getscu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY \
  -k QueryRetrieveLevel=SERIES \
  -k StudyInstanceUID="1.2.3.4.5.6.7.8.9" \
  -k SeriesInstanceUID="1.2.3.4.5.6.7.8.9.10" \
  --output-directory ./retrieved

Example 3: C-MOVE Operation

# Move study to another PACS
movescu localhost 11112 -aet TESTCLIENT -aec DICOMPROXY \
  -aem DESTINATION_AE \
  -k QueryRetrieveLevel=STUDY \
  -k StudyInstanceUID="1.2.3.4.5.6.7.8.9"

REST API Examples

Example 1: DICOMweb QIDO-RS Queries

Basic Study Search:

# Search all studies
curl "http://localhost:8080/wado/studies"

# Search with patient name filter
curl "http://localhost:8080/wado/studies?PatientName=DOE*"

# Search with date range
curl "http://localhost:8080/wado/studies?StudyDate=20240101-20241231"

# Complex search with multiple filters
curl "http://localhost:8080/wado/studies?PatientName=DOE*&ModalitiesInStudy=CT&StudyDate=20240101-20241231&limit=10"

Series Search:

# Get all series for a study
curl "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/series"

# Filter series by modality
curl "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/series?Modality=CT"

Instance Search:

# Get all instances in a series
curl "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/series/1.2.3.4.5.6.7.8.9.10/instances"

Example 2: DICOMweb WADO-RS Retrieval

Retrieve Study Metadata:

curl -H "Accept: application/json" \
  "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/metadata"

Retrieve Instance as DICOM:

curl -H "Accept: application/dicom" \
  "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/series/1.2.3.4.5.6.7.8.9.10/instances/1.2.3.4.5.6.7.8.9.10.11" \
  -o image.dcm

Retrieve Rendered Image:

curl -H "Accept: image/jpeg" \
  "http://localhost:8080/wado/studies/1.2.3.4.5.6.7.8.9/series/1.2.3.4.5.6.7.8.9.10/instances/1.2.3.4.5.6.7.8.9.10.11/rendered?viewport=512,512" \
  -o image.jpg

Example 3: DICOMweb STOW-RS Storage

Store Single DICOM File:

curl -X POST \
  -H "Content-Type: multipart/related; type=\"application/dicom\"" \
  --data-binary @image.dcm \
  "http://localhost:8080/wado/studies"

Store Multiple Files:

# Using multipart form data
curl -X POST \
  -H "Content-Type: multipart/related; type=\"application/dicom\"" \
  -F "file1=@image1.dcm" \
  -F "file2=@image2.dcm" \
  "http://localhost:8080/wado/studies"

Advanced Integration Scenarios

Example 1: Multi-PACS Routing Configuration

servers.json Configuration:

{
  "servers": [
    {
      "id": "primary_pacs",
      "name": "Primary PACS Server",
      "ae_title": "PRIMARYPACS",
      "hostname": "192.168.1.100",
      "port": 11112,
      "enabled": true,
      "priority": 1,
      "max_connections": 20,
      "timeout_seconds": 30,
      "supported_services": ["C-STORE", "C-FIND", "C-GET", "C-MOVE", "C-ECHO"]
    },
    {
      "id": "archive_pacs",
      "name": "Archive PACS Server",
      "ae_title": "ARCHIVEPACS",
      "hostname": "192.168.1.101",
      "port": 11112,
      "enabled": true,
      "priority": 2,
      "max_connections": 10,
      "timeout_seconds": 60,
      "supported_services": ["C-STORE", "C-FIND", "C-GET", "C-ECHO"]
    },
    {
      "id": "cloud_pacs",
      "name": "Cloud PACS",
      "ae_title": "CLOUDPACS",
      "hostname": "cloud.pacs.example.com",
      "port": 11112,
      "enabled": true,
      "priority": 3,
      "max_connections": 5,
      "timeout_seconds": 120,
      "supported_services": ["C-STORE", "C-FIND", "C-GET"]
    }
  ]
}

Testing Multi-PACS Setup:

# Test connectivity to each PACS
curl -X POST "http://localhost:8080/proxy/echo/primary_pacs"
curl -X POST "http://localhost:8080/proxy/echo/archive_pacs"
curl -X POST "http://localhost:8080/proxy/echo/cloud_pacs"

# Query specific PACS
curl "http://localhost:8080/proxy/wado/primary_pacs/rs/studies?PatientName=DOE*"
curl "http://localhost:8080/proxy/wado/archive_pacs/rs/studies?StudyDate=20230101-20231231"

Example 2: Automated Workflow with Scripts

PowerShell Script for Batch Operations:

# batch_operations.ps1
param(
    [string]$ProxyUrl = "http://localhost:8080",
    [string]$InputFolder = "C:\DICOM\Input",
    [string]$LogFile = "C:\DICOM\Logs\batch.log"
)

function Write-Log {
    param($Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Add-Content -Path $LogFile
    Write-Host "$timestamp - $Message"
}

function Upload-DicomFile {
    param($FilePath)
    
    try {
        $response = Invoke-RestMethod -Uri "$ProxyUrl/wado/studies" `
                                    -Method Post `
                                    -ContentType "application/dicom" `
                                    -InFile $FilePath
        
        Write-Log "Successfully uploaded: $FilePath"
        return $true
    }
    catch {
        Write-Log "Failed to upload $FilePath : $($_.Exception.Message)"
        return $false
    }
}

# Main processing loop
Write-Log "Starting batch upload from $InputFolder"

$dicomFiles = Get-ChildItem -Path $InputFolder -Filter "*.dcm" -Recurse
$successCount = 0
$failureCount = 0

foreach ($file in $dicomFiles) {
    if (Upload-DicomFile -FilePath $file.FullName) {
        $successCount++
        # Move successful files to processed folder
        $processedFolder = Join-Path $InputFolder "Processed"
        if (!(Test-Path $processedFolder)) { New-Item -Path $processedFolder -ItemType Directory }
        Move-Item -Path $file.FullName -Destination $processedFolder
    }
    else {
        $failureCount++
    }
}

Write-Log "Batch upload completed. Success: $successCount, Failures: $failureCount"

Python Script for Monitoring:

# monitoring.py
import requests
import time
import json
import logging
from datetime import datetime

class DicomProxyMonitor:
    def __init__(self, proxy_url="http://localhost:8080"):
        self.proxy_url = proxy_url
        self.setup_logging()
    
    def setup_logging(self):
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('monitor.log'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)
    
    def check_system_status(self):
        """Check overall system status"""
        try:
            response = requests.get(f"{self.proxy_url}/api/status", timeout=10)
            if response.status_code == 200:
                status = response.json()
                self.logger.info(f"System Status OK - Studies: {status.get('studies_count', 'N/A')}")
                return True
            else:
                self.logger.error(f"System status check failed: {response.status_code}")
                return False
        except Exception as e:
            self.logger.error(f"System status check error: {str(e)}")
            return False
    
    def check_pacs_connectivity(self):
        """Check connectivity to all configured PACS servers"""
        try:
            response = requests.get(f"{self.proxy_url}/api/connections", timeout=10)
            if response.status_code == 200:
                connections = response.json()
                for server in connections.get('configured_servers', []):
                    server_id = server.get('id')
                    status = server.get('status')
                    
                    if status == 'Connected':
                        self.logger.info(f"PACS {server_id}: Connected")
                    else:
                        self.logger.warning(f"PACS {server_id}: {status}")
                        # Try to reconnect
                        self.test_pacs_echo(server_id)
        except Exception as e:
            self.logger.error(f"PACS connectivity check error: {str(e)}")
    
    def test_pacs_echo(self, server_id):
        """Test DICOM echo to specific PACS"""
        try:
            response = requests.post(f"{self.proxy_url}/proxy/echo/{server_id}", timeout=30)
            if response.status_code == 200:
                result = response.json()
                if result.get('success'):
                    self.logger.info(f"PACS {server_id} echo successful")
                else:
                    self.logger.error(f"PACS {server_id} echo failed")
        except Exception as e:
            self.logger.error(f"PACS {server_id} echo error: {str(e)}")
    
    def monitor_loop(self, interval_minutes=5):
        """Main monitoring loop"""
        self.logger.info("Starting DICOM Proxy monitoring")
        
        while True:
            self.logger.info("--- Monitoring Cycle ---")
            
            # Check system status
            if self.check_system_status():
                # If system is up, check PACS connectivity
                self.check_pacs_connectivity()
            
            # Wait for next cycle
            time.sleep(interval_minutes * 60)

if __name__ == "__main__":
    monitor = DicomProxyMonitor()
    monitor.monitor_loop(interval_minutes=5)

Example 3: Custom C# Integration

Complete C# Integration Example:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using DicomProxyCore;
using DicomProxyCore.Services;
using FellowOakDicom;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class DicomProxyIntegration
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<DicomProxyIntegration> _logger;
    private readonly string _proxyUrl;

    public DicomProxyIntegration(string proxyUrl = "http://localhost:8080")
    {
        _proxyUrl = proxyUrl;
        _httpClient = new HttpClient();
        
        // Setup logging
        var services = new ServiceCollection()
            .AddLogging(builder => builder.AddConsole())
            .BuildServiceProvider();
        
        _logger = services.GetService<ILogger<DicomProxyIntegration>>();
    }

    public async Task<List<StudyInfo>> SearchStudiesAsync(StudyQuery query)
    {
        var queryParams = new List<string>();
        
        if (!string.IsNullOrEmpty(query.PatientName))
            queryParams.Add($"PatientName={Uri.EscapeDataString(query.PatientName)}");
        
        if (!string.IsNullOrEmpty(query.StudyDate))
            queryParams.Add($"StudyDate={query.StudyDate}");
        
        if (!string.IsNullOrEmpty(query.Modality))
            queryParams.Add($"ModalitiesInStudy={query.Modality}");
        
        if (query.Limit.HasValue)
            queryParams.Add($"limit={query.Limit}");

        var url = $"{_proxyUrl}/wado/studies";
        if (queryParams.Count > 0)
            url += "?" + string.Join("&", queryParams);

        try
        {
            var response = await _httpClient.GetStringAsync(url);
            var jsonResponse = JsonDocument.Parse(response);
            
            var studies = new List<StudyInfo>();
            foreach (var element in jsonResponse.RootElement.EnumerateArray())
            {
                studies.Add(ParseStudyFromJson(element));
            }
            
            _logger.LogInformation($"Found {studies.Count} studies");
            return studies;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error searching studies");
            throw;
        }
    }

    public async Task<bool> StoreStudyAsync(string dicomFilePath)
    {
        try
        {
            var fileBytes = await File.ReadAllBytesAsync(dicomFilePath);
            var content = new ByteArrayContent(fileBytes);
            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/dicom");

            var response = await _httpClient.PostAsync($"{_proxyUrl}/wado/studies", content);
            
            if (response.IsSuccessStatusCode)
            {
                _logger.LogInformation($"Successfully stored: {dicomFilePath}");
                return true;
            }
            else
            {
                _logger.LogError($"Failed to store {dicomFilePath}: {response.StatusCode}");
                return false;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error storing study: {dicomFilePath}");
            return false;
        }
    }

    public async Task<byte[]> RetrieveInstanceAsync(string studyUID, string seriesUID, string instanceUID, string format = "application/dicom")
    {
        try
        {
            _httpClient.DefaultRequestHeaders.Accept.Clear();
            _httpClient.DefaultRequestHeaders.Accept.Add(
                new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(format));

            var url = $"{_proxyUrl}/wado/studies/{studyUID}/series/{seriesUID}/instances/{instanceUID}";
            
            if (format.StartsWith("image/"))
                url += "/rendered";

            var response = await _httpClient.GetByteArrayAsync(url);
            _logger.LogInformation($"Retrieved instance: {instanceUID}");
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error retrieving instance: {instanceUID}");
            throw;
        }
    }

    public async Task<SystemStatus> GetSystemStatusAsync()
    {
        try
        {
            var response = await _httpClient.GetStringAsync($"{_proxyUrl}/api/status");
            var json = JsonDocument.Parse(response);
            var root = json.RootElement;

            return new SystemStatus
            {
                ServiceName = root.GetProperty("service_name").GetString(),
                Version = root.GetProperty("version").GetString(),
                UptimeSeconds = root.GetProperty("uptime_seconds").GetInt32(),
                StudiesCount = root.GetProperty("studies_count").GetInt32(),
                StorageUsedGB = root.GetProperty("storage_used_gb").GetDouble(),
                StorageAvailableGB = root.GetProperty("storage_available_gb").GetDouble()
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting system status");
            throw;
        }
    }

    private StudyInfo ParseStudyFromJson(JsonElement element)
    {
        var study = new StudyInfo();
        
        if (element.TryGetProperty("0020000D", out var studyUidProp))
            study.StudyInstanceUID = studyUidProp.GetProperty("Value")[0].GetString();
        
        if (element.TryGetProperty("00100010", out var patientNameProp))
        {
            var nameValue = patientNameProp.GetProperty("Value")[0];
            if (nameValue.TryGetProperty("Alphabetic", out var alphabetic))
                study.PatientName = alphabetic.GetString();
        }
        
        if (element.TryGetProperty("00100020", out var patientIdProp))
            study.PatientID = patientIdProp.GetProperty("Value")[0].GetString();
        
        if (element.TryGetProperty("00080020", out var studyDateProp))
            study.StudyDate = studyDateProp.GetProperty("Value")[0].GetString();
        
        if (element.TryGetProperty("00080061", out var modalityProp))
            study.Modality = modalityProp.GetProperty("Value")[0].GetString();

        return study;
    }

    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

// Supporting classes
public class StudyQuery
{
    public string PatientName { get; set; }
    public string StudyDate { get; set; }
    public string Modality { get; set; }
    public int? Limit { get; set; }
}

public class StudyInfo
{
    public string StudyInstanceUID { get; set; }
    public string PatientName { get; set; }
    public string PatientID { get; set; }
    public string StudyDate { get; set; }
    public string Modality { get; set; }
}

public class SystemStatus
{
    public string ServiceName { get; set; }
    public string Version { get; set; }
    public int UptimeSeconds { get; set; }
    public int StudiesCount { get; set; }
    public double StorageUsedGB { get; set; }
    public double StorageAvailableGB { get; set; }
}

// Usage example
public class Program
{
    public static async Task Main(string[] args)
    {
        using var integration = new DicomProxyIntegration("http://localhost:8080");

        // Get system status
        var status = await integration.GetSystemStatusAsync();
        Console.WriteLine($"System: {status.ServiceName} v{status.Version}");
        Console.WriteLine($"Studies: {status.StudiesCount}");

        // Search for studies
        var query = new StudyQuery
        {
            PatientName = "DOE*",
            StudyDate = "20240101-20241231",
            Limit = 10
        };

        var studies = await integration.SearchStudiesAsync(query);
        foreach (var study in studies)
        {
            Console.WriteLine($"Study: {study.PatientName} - {study.StudyDate}");
        }

        // Store a DICOM file
        await integration.StoreStudyAsync("sample.dcm");
    }
}

Performance Optimization

Example 1: Bulk Operations

Bulk Upload Script (PowerShell):

# bulk_upload.ps1
param(
    [string]$SourceFolder,
    [string]$ProxyUrl = "http://localhost:8080",
    [int]$ConcurrentUploads = 5
)

function Upload-DicomFile {
    param($FilePath, $Url)
    
    try {
        Invoke-RestMethod -Uri "$Url/wado/studies" `
                         -Method Post `
                         -ContentType "application/dicom" `
                         -InFile $FilePath -TimeoutSec 300
        return @{ Success = $true; File = $FilePath }
    }
    catch {
        return @{ Success = $false; File = $FilePath; Error = $_.Exception.Message }
    }
}

# Get all DICOM files
$files = Get-ChildItem -Path $SourceFolder -Filter "*.dcm" -Recurse

# Process in parallel batches
$batches = @()
for ($i = 0; $i -lt $files.Count; $i += $ConcurrentUploads) {
    $batch = $files[$i..([Math]::Min($i + $ConcurrentUploads - 1, $files.Count - 1))]
    $batches += ,$batch
}

$totalSuccess = 0
$totalFailure = 0

foreach ($batch in $batches) {
    Write-Host "Processing batch of $($batch.Count) files..."
    
    $jobs = foreach ($file in $batch) {
        Start-Job -ScriptBlock ${function:Upload-DicomFile} -ArgumentList $file.FullName, $ProxyUrl
    }
    
    $results = $jobs | Wait-Job | Receive-Job
    $jobs | Remove-Job
    
    foreach ($result in $results) {
        if ($result.Success) {
            $totalSuccess++
            Write-Host "✓ $($result.File)" -ForegroundColor Green
        } else {
            $totalFailure++
            Write-Host "✗ $($result.File): $($result.Error)" -ForegroundColor Red
        }
    }
}

Write-Host "Upload completed. Success: $totalSuccess, Failures: $totalFailure"

Example 2: Connection Pooling Configuration

Optimized Configuration:

{
  "DicomPort": 11112,
  "HttpPort": 8080,
  "StoragePath": "D:\\DicomStorage",
  "Performance": {
    "MaxConcurrentConnections": 100,
    "ConnectionPoolSize": 50,
    "RequestTimeoutSeconds": 300,
    "MaxMemoryUsageMB": 4096,
    "EnableCompression": true,
    "CacheExpirationMinutes": 30
  },
  "Database": {
    "MaxConnections": 20,
    "CommandTimeoutSeconds": 60,
    "EnableWAL": true
  },
  "Storage": {
    "MaxConcurrentWrites": 10,
    "UseAsyncIO": true,
    "BufferSizeKB": 64
  }
}

Troubleshooting Common Issues

Issue 1: Connection Timeout

Problem: DICOM operations timing out

Diagnosis:

# Check system status
curl http://localhost:8080/api/status

# Check specific server connectivity
curl -X POST http://localhost:8080/proxy/echo/pacs1

# Review logs
tail -f C:\DicomStorage\logs\dicom-proxy-*.log

Solution:

{
  "servers": [
    {
      "id": "pacs1",
      "timeout_seconds": 120,
      "max_connections": 5,
      "retry_count": 3,
      "retry_delay_seconds": 5
    }
  ]
}

Issue 2: Storage Space Management

Automated Cleanup Script:

# cleanup_storage.ps1
param(
    [string]$StoragePath = "C:\DicomStorage",
    [int]$MaxStorageGB = 500,
    [int]$RetentionDays = 90
)

function Get-FolderSize {
    param($Path)
    return (Get-ChildItem -Path $Path -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1GB
}

function Remove-OldStudies {
    param($Path, $Days)
    
    $cutoffDate = (Get-Date).AddDays(-$Days)
    $studyFolders = Get-ChildItem -Path "$Path\studies" -Directory
    
    $removedCount = 0
    $freedSpace = 0
    
    foreach ($folder in $studyFolders) {
        if ($folder.CreationTime -lt $cutoffDate) {
            $folderSize = Get-FolderSize $folder.FullName
            Remove-Item -Path $folder.FullName -Recurse -Force
            $removedCount++
            $freedSpace += $folderSize
            Write-Host "Removed study: $($folder.Name) ($('{0:N2}' -f $folderSize) GB)"
        }
    }
    
    Write-Host "Cleanup complete: $removedCount studies removed, $('{0:N2}' -f $freedSpace) GB freed"
}

# Check current usage
$currentSize = Get-FolderSize $StoragePath
Write-Host "Current storage usage: $('{0:N2}' -f $currentSize) GB"

if ($currentSize -gt $MaxStorageGB) {
    Write-Host "Storage limit exceeded. Starting cleanup..."
    Remove-OldStudies -Path $StoragePath -Days $RetentionDays
}

Issue 3: Performance Monitoring

Real-time Monitoring Script:

# performance_monitor.py
import requests
import time
import psutil
import json
from datetime import datetime

class PerformanceMonitor:
    def __init__(self, proxy_url="http://localhost:8080"):
        self.proxy_url = proxy_url
        self.metrics_history = []
    
    def get_system_metrics(self):
        """Get system performance metrics"""
        return {
            'timestamp': datetime.now().isoformat(),
            'cpu_percent': psutil.cpu_percent(interval=1),
            'memory_percent': psutil.virtual_memory().percent,
            'disk_usage_percent': psutil.disk_usage('C:').percent,
            'network_io': dict(psutil.net_io_counters()._asdict())
        }
    
    def get_dicom_metrics(self):
        """Get DICOM proxy specific metrics"""
        try:
            response = requests.get(f"{self.proxy_url}/api/metrics", timeout=5)
            if response.status_code == 200:
                return response.json()
        except:
            pass
        return {}
    
    def monitor(self, duration_minutes=60, interval_seconds=30):
        """Monitor system for specified duration"""
        end_time = time.time() + (duration_minutes * 60)
        
        print(f"Starting {duration_minutes}-minute monitoring session...")
        print("Timestamp,CPU%,Memory%,Disk%,DICOM_Requests/min")
        
        while time.time() < end_time:
            system_metrics = self.get_system_metrics()
            dicom_metrics = self.get_dicom_metrics()
            
            # Combine metrics
            combined_metrics = {**system_metrics, **dicom_metrics}
            self.metrics_history.append(combined_metrics)
            
            # Print summary
            dicom_rpm = dicom_metrics.get('dicom_stats', {}).get('c_store_requests_per_minute', 0)
            print(f"{system_metrics['timestamp'][:19]},"
                  f"{system_metrics['cpu_percent']:.1f},"
                  f"{system_metrics['memory_percent']:.1f},"
                  f"{system_metrics['disk_usage_percent']:.1f},"
                  f"{dicom_rpm}")
            
            # Check for alerts
            if system_metrics['cpu_percent'] > 80:
                print(f"ALERT: High CPU usage: {system_metrics['cpu_percent']:.1f}%")
            
            if system_metrics['memory_percent'] > 85:
                print(f"ALERT: High memory usage: {system_metrics['memory_percent']:.1f}%")
            
            time.sleep(interval_seconds)
        
        # Save metrics to file
        with open(f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", 'w') as f:
            json.dump(self.metrics_history, f, indent=2)
        
        print("Monitoring session completed. Metrics saved to file.")

if __name__ == "__main__":
    monitor = PerformanceMonitor()
    monitor.monitor(duration_minutes=60, interval_seconds=30)

Issue 4: SSL Certificate Setup

Generate and Configure SSL Certificate:

# ssl_setup.ps1
param(
    [string]$CertPath = "C:\DicomProxy\certificate.pfx",
    [string]$Password = "DicomProxy2024!",
    [string]$DnsName = "localhost"
)

# Generate self-signed certificate
$cert = New-SelfSignedCertificate -DnsName $DnsName -CertStoreLocation "cert:\LocalMachine\My" -KeyUsage DigitalSignature,KeyEncipherment -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")

# Export to PFX file
$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath $CertPath -Password $securePassword

Write-Host "Certificate created: $CertPath"
Write-Host "Certificate thumbprint: $($cert.Thumbprint)"

# Update configuration
$configPath = "appsettings.json"
if (Test-Path $configPath) {
    $config = Get-Content $configPath | ConvertFrom-Json
    $config.Security.EnableTLS = $true
    $config.Security.CertificatePath = "certificate.pfx"
    $config.Security.CertificatePassword = $Password
    
    $config | ConvertTo-Json -Depth 10 | Set-Content $configPath
    Write-Host "Configuration updated with SSL settings"
}

This comprehensive examples and tutorials document provides practical, real-world scenarios for implementing and using the DICOM Proxy system across various integration patterns and use cases.